From c1d38f741bafd4c27fd525aff79e787e78e594f2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 23 Oct 2017 15:36:32 +0200 Subject: [PATCH] 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 --- ospurge/resources/base.py | 66 ++++++++++++++------------ ospurge/resources/base.pyi | 4 +- ospurge/tests/resources/test_base.py | 70 +++++++++++++++------------- requirements.txt | 1 + 4 files changed, 78 insertions(+), 63 deletions(-) diff --git a/ospurge/resources/base.py b/ospurge/resources/base.py index 845ba23..f548e1c 100644 --- a/ospurge/resources/base.py +++ b/ospurge/resources/base.py @@ -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( diff --git a/ospurge/resources/base.pyi b/ospurge/resources/base.pyi index 68d38f5..87d1d0b 100644 --- a/ospurge/resources/base.pyi +++ b/ospurge/resources/base.pyi @@ -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: ... diff --git a/ospurge/tests/resources/test_base.py b/ospurge/tests/resources/test_base.py index f568cec..d6dbdc4 100644 --- a/ospurge/tests/resources/test_base.py +++ b/ospurge/tests/resources/test_base.py @@ -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): diff --git a/requirements.txt b/requirements.txt index ce9948f..43c20d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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