mirror of
https://github.com/wassname/catalyst.git
synced 2026-08-10 12:00:15 +08:00
ENH: adds with_metaclasses and tests for metautils
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
from zipline.testing.fixtures import ZiplineTestCase
|
||||
from zipline.testing.predicates import (
|
||||
assert_equal,
|
||||
assert_is,
|
||||
assert_is_instance,
|
||||
assert_is_subclass,
|
||||
assert_true,
|
||||
)
|
||||
from zipline.utils.metautils import compose_types, with_metaclasses
|
||||
|
||||
|
||||
class C(object):
|
||||
@staticmethod
|
||||
def f():
|
||||
return 'C.f'
|
||||
|
||||
def delegate(self):
|
||||
return 'C.delegate', super(C, self).delegate()
|
||||
|
||||
|
||||
class D(object):
|
||||
@staticmethod
|
||||
def f():
|
||||
return 'D.f'
|
||||
|
||||
@staticmethod
|
||||
def g():
|
||||
return 'D.g'
|
||||
|
||||
def delegate(self):
|
||||
return 'D.delegate'
|
||||
|
||||
|
||||
class ComposeTypesTestCase(ZiplineTestCase):
|
||||
|
||||
def test_identity(self):
|
||||
assert_is(
|
||||
compose_types(C),
|
||||
C,
|
||||
msg='compose_types of a single class should be identity',
|
||||
)
|
||||
|
||||
def test_compose(self):
|
||||
composed = compose_types(C, D)
|
||||
|
||||
assert_is_subclass(composed, C)
|
||||
assert_is_subclass(composed, D)
|
||||
|
||||
def test_compose_mro(self):
|
||||
composed = compose_types(C, D)
|
||||
|
||||
assert_equal(composed.f(), C.f())
|
||||
assert_equal(composed.g(), D.g())
|
||||
|
||||
assert_equal(composed().delegate(), ('C.delegate', 'D.delegate'))
|
||||
|
||||
|
||||
class M(type):
|
||||
def __new__(mcls, name, bases, dict_):
|
||||
dict_['M'] = True
|
||||
return super(M, mcls).__new__(mcls, name, bases, dict_)
|
||||
|
||||
|
||||
class N(type):
|
||||
def __new__(mcls, name, bases, dict_):
|
||||
dict_['N'] = True
|
||||
return super(N, mcls).__new__(mcls, name, bases, dict_)
|
||||
|
||||
|
||||
class WithMetaclassesTestCase(ZiplineTestCase):
|
||||
def test_with_metaclasses_no_subclasses(self):
|
||||
class E(with_metaclasses((M, N))):
|
||||
pass
|
||||
|
||||
assert_true(E.M)
|
||||
assert_true(E.N)
|
||||
|
||||
assert_is_instance(E, M)
|
||||
assert_is_instance(E, N)
|
||||
|
||||
def test_with_metaclasses_with_subclasses(self):
|
||||
class E(with_metaclasses((M, N), C, D)):
|
||||
pass
|
||||
|
||||
assert_true(E.M)
|
||||
assert_true(E.N)
|
||||
|
||||
assert_is_instance(E, M)
|
||||
assert_is_instance(E, N)
|
||||
assert_is_subclass(E, C)
|
||||
assert_is_subclass(E, D)
|
||||
@@ -141,6 +141,34 @@ def _fmt_msg(msg):
|
||||
return msg + '\n'
|
||||
|
||||
|
||||
def _safe_cls_name(cls):
|
||||
try:
|
||||
return cls.__name__
|
||||
except AttributeError:
|
||||
return repr(cls)
|
||||
|
||||
|
||||
def assert_is_subclass(subcls, cls, msg=''):
|
||||
"""Assert that ``subcls`` is a subclass of ``cls``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subcls : type
|
||||
The type to check.
|
||||
cls : type
|
||||
The type to check ``subcls`` against.
|
||||
msg : str, optional
|
||||
An extra assertion message to print if this fails.
|
||||
"""
|
||||
assert issubclass(subcls, cls), (
|
||||
'%s is not a subclass of %s\n%s' % (
|
||||
_safe_cls_name(subcls),
|
||||
_safe_cls_name(cls),
|
||||
msg,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@dispatch(object, object)
|
||||
def assert_equal(result, expected, path=(), msg='', **kwargs):
|
||||
"""Assert that two objects are equal using the ``==`` operator.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from operator import attrgetter
|
||||
|
||||
import six
|
||||
|
||||
def compose_types(a, b, *cs):
|
||||
|
||||
def compose_types(a, *cs):
|
||||
"""Compose multiple classes together.
|
||||
|
||||
Parameters
|
||||
@@ -66,9 +68,40 @@ def compose_types(a, b, *cs):
|
||||
Always using ``super()`` to dispatch to your superclass is best practices
|
||||
anyways so most classes should compose without much special considerations.
|
||||
"""
|
||||
mcls = (a, b) + cs
|
||||
if not cs:
|
||||
# if there are no types to compose then just return the single type
|
||||
return a
|
||||
|
||||
mcls = (a,) + cs
|
||||
return type(
|
||||
'compose_types(%s)' % ', '.join(map(attrgetter('__name__'), mcls)),
|
||||
mcls,
|
||||
{},
|
||||
)
|
||||
|
||||
|
||||
def with_metaclasses(metaclasses, *bases):
|
||||
"""Make a class inheriting from ``bases`` whose metaclass inherits from
|
||||
all of ``metaclasses``.
|
||||
|
||||
Like :func:`six.with_metaclass`, but allows multiple metaclasses.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
metaclasses : iterable[type]
|
||||
A tuple of types to use as metaclasses.
|
||||
*bases : tuple[type]
|
||||
A tuple of types to use as bases.
|
||||
|
||||
Returns
|
||||
-------
|
||||
base : type
|
||||
A subtype of ``bases`` whose metaclass is a subtype of ``metaclasses``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The metaclasses must be written to support cooperative multiple
|
||||
inheritance. This means that they must delegate all calls to ``super()``
|
||||
instead of inlining their super class by name.
|
||||
"""
|
||||
return six.with_metaclass(compose_types(*metaclasses), *bases)
|
||||
|
||||
Reference in New Issue
Block a user