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:
parent
b73c938a93
commit
c1d38f741b
@ -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(
|
||||||
|
@ -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:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user