ENH: adds with_metaclasses and tests for metautils

This commit is contained in:
Joe Jevnik
2016-05-12 15:58:19 -04:00
parent 2297ace20c
commit 9b76731143
3 changed files with 154 additions and 2 deletions
+91
View File
@@ -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)
+28
View File
@@ -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.
+35 -2
View File
@@ -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)