Python2: Fix metaclass, super() and OrderedMeta

* Add six dependency
* Replace "metaclass=" syntax with six.with_metaclass()
* Add parameters to super()
* Only define OrderedMeta on Python 3

Related-Bug: 1726399
Change-Id: I21800320ef23cc95fc07278864e73b64bb7d6d08
This commit is contained in:
Victor Stinner 2017-10-23 15:36:32 +02:00
parent b73c938a93
commit c1d38f741b
4 changed files with 78 additions and 63 deletions

View File

@ -19,6 +19,7 @@ try:
import funcsigs as inspect # Python 2.7
except ImportError:
import inspect
import six
from ospurge import exceptions
@ -30,7 +31,7 @@ if TYPE_CHECKING: # pragma: no cover
class MatchSignaturesMeta(type):
def __init__(self, clsname, bases, clsdict):
super().__init__(clsname, bases, clsdict)
super(MatchSignaturesMeta, self).__init__(clsname, bases, clsdict)
sup = super(self, self) # type: ignore # See python/mypy #857
for name, value in clsdict.items():
if name.startswith('_') or not callable(value):
@ -47,37 +48,43 @@ class MatchSignaturesMeta(type):
value_name, prev_sig, val_sig)
class OrderedMeta(type):
def __new__(cls, clsname, bases, clsdict):
ordered_methods = cls.ordered_methods
allowed_next_methods = list(ordered_methods)
for name, value in clsdict.items():
if name not in ordered_methods:
continue
if six.PY3:
class OrderedMeta(type):
def __new__(cls, clsname, bases, clsdict):
ordered_methods = cls.ordered_methods
allowed_next_methods = list(ordered_methods)
for name, value in clsdict.items():
if name not in ordered_methods:
continue
if name not in allowed_next_methods:
value_name = getattr(value, '__qualname__', value.__name__)
logging.warning(
"Method %s not defined at the correct location. Methods "
"in class %s must be defined in the following order %r",
value_name, clsname, ordered_methods
)
continue # pragma: no cover
if name not in allowed_next_methods:
value_name = value.__qualname__
logging.warning(
"Method %s not defined at the correct location."
" Methods in class %s must be defined in the following"
" order %r",
value_name, clsname, ordered_methods
)
continue # pragma: no cover
_slice = slice(allowed_next_methods.index(name) + 1, None)
allowed_next_methods = allowed_next_methods[_slice]
_slice = slice(allowed_next_methods.index(name) + 1, None)
allowed_next_methods = allowed_next_methods[_slice]
# Cast to dict is required. We can't pass an OrderedDict here.
return super().__new__(cls, clsname, bases, dict(clsdict))
# Cast to dict is required. We can't pass an OrderedDict here.
return super().__new__(cls, clsname, bases, dict(clsdict))
@classmethod
def __prepare__(cls, clsname, bases):
return collections.OrderedDict()
@classmethod
def __prepare__(cls, clsname, bases):
return collections.OrderedDict()
class CodingStyleMixin(OrderedMeta, MatchSignaturesMeta, abc.ABCMeta):
ordered_methods = ['order', 'check_prerequisite', 'list', 'should_delete',
'delete', 'to_string']
class CodingStyleMixin(OrderedMeta, MatchSignaturesMeta, abc.ABCMeta):
ordered_methods = ['order', 'check_prerequisite', 'list',
'should_delete', 'delete', 'to_string']
else: # pragma: no cover here
# OrderedMeta is not supported on Python 2. Class members are unordered in
# Python 2 and __prepare__() was introduced in Python 3.
class CodingStyleMixin(MatchSignaturesMeta, abc.ABCMeta):
pass
class BaseServiceResource(object):
@ -87,11 +94,12 @@ class BaseServiceResource(object):
self.options = None # type: Optional[argparse.Namespace]
class ServiceResource(BaseServiceResource, metaclass=CodingStyleMixin):
class ServiceResource(six.with_metaclass(CodingStyleMixin,
BaseServiceResource)):
ORDER = None # type: int
def __init__(self, creds_manager):
super().__init__()
super(ServiceResource, self).__init__()
if self.ORDER is None:
raise ValueError(
'Class {}.{} must override the "ORDER" class attribute'.format(

View File

@ -16,6 +16,8 @@ from typing import Dict
from typing import Iterable
from typing import Optional
import six
from ospurge.main import CredentialsManager # noqa: F401
@ -48,7 +50,7 @@ class BaseServiceResource(object):
...
class ServiceResource(BaseServiceResource, metaclass=CodingStyleMixin):
class ServiceResource(six.with_metaclass(CodingStyleMixin, BaseServiceResource)):
def __init__(self, creds_manager: 'CredentialsManager') -> None:
...

View File

@ -11,6 +11,8 @@
# under the License.
import time
import six
from ospurge import exceptions
from ospurge.resources import base
from ospurge.tests import mock
@ -41,7 +43,7 @@ class WrongMethodDefOrder(Exception):
@mock.patch('logging.warning', mock.Mock(side_effect=SignatureMismatch))
class TestMatchSignaturesMeta(unittest.TestCase):
class Test(metaclass=base.MatchSignaturesMeta):
class Test(six.with_metaclass(base.MatchSignaturesMeta)):
def a(self, arg1):
pass
@ -94,46 +96,48 @@ class TestMatchSignaturesMeta(unittest.TestCase):
pass
@mock.patch('logging.warning', mock.Mock(side_effect=WrongMethodDefOrder))
class TestOrderedMeta(unittest.TestCase):
class Test(base.OrderedMeta):
ordered_methods = ['a', 'b']
# OrderedMeta requires Python 3
if six.PY3:
@mock.patch('logging.warning', mock.Mock(side_effect=WrongMethodDefOrder))
class TestOrderedMeta(unittest.TestCase):
class Test(base.OrderedMeta):
ordered_methods = ['a', 'b']
def test_nominal(self):
class Foo1(metaclass=self.Test):
def a(self):
pass
def test_nominal(self):
class Foo1(six.with_metaclass(self.Test)):
def a(self):
pass
class Foo2(metaclass=self.Test):
def b(self):
pass
class Foo3(metaclass=self.Test):
def a(self):
pass
def b(self):
pass
class Foo4(metaclass=self.Test):
def a(self):
pass
def other(self):
pass
def b(self):
pass
def test_wrong_order(self):
with self.assertRaises(WrongMethodDefOrder):
class Foo(metaclass=self.Test):
class Foo2(six.with_metaclass(self.Test)):
def b(self):
pass
class Foo3(six.with_metaclass(self.Test)):
def a(self):
pass
def b(self):
pass
class Foo4(six.with_metaclass(self.Test)):
def a(self):
pass
def other(self):
pass
def b(self):
pass
def test_wrong_order(self):
with self.assertRaises(WrongMethodDefOrder):
class Foo(six.with_metaclass(self.Test)):
def b(self):
pass
def a(self):
pass
class TestServiceResource(unittest.TestCase):
def test_init_without_order_attr(self):

View File

@ -1,5 +1,6 @@
os-client-config>=1.22.0 # Apache-2.0
pbr>=1.8 # Apache-2.0
six
shade>=1.13.1
typing>=3.5.2.2 # PSF