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

View File

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

View File

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