|
5 | 5 | from datetime import timedelta |
6 | 6 |
|
7 | 7 | import six |
| 8 | +from six.moves import map # pylint:disable=redefined-builtin |
8 | 9 |
|
9 | 10 |
|
10 | 11 | if not hasattr(timedelta, 'total_seconds'): |
@@ -59,19 +60,66 @@ class Subclass(temporary_class): |
59 | 60 | ``bases``, then errors might occur. For example, this was a problem when |
60 | 61 | used with ``enum.EnumMeta`` in Python 3.6. Here we make sure that |
61 | 62 | ``__prepare__()`` is defined on the temporary metaclass, and pass ``bases`` |
62 | | - to ``meta.__prepare__()``. |
| 63 | + to ``meta.__prepare__()``. This is fixed in six>=1.11.0 by PR #178 [1]. |
63 | 64 |
|
64 | 65 | Since ``temporary_class`` doesn't have the correct bases, in theory this |
65 | 66 | could cause other problems, besides the previous one, in certain edge |
66 | 67 | cases. To make sure that doesn't become a problem, we make sure that |
67 | 68 | ``temporary_class`` has ``bases`` as its bases, just like the final class. |
| 69 | +
|
| 70 | + [1] <https://github.com/benjaminp/six/pull/178> |
68 | 71 | """ |
69 | 72 | temporary_class = six.with_metaclass(meta, *bases, **with_metaclass_kwargs) |
70 | 73 | temporary_metaclass = type(temporary_class) |
71 | 74 |
|
72 | | - class TemporaryMetaSubclass(temporary_metaclass): |
73 | | - @classmethod |
74 | | - def __prepare__(cls, name, this_bases, **kwds): # pylint:disable=unused-argument |
75 | | - return meta.__prepare__(name, bases, **kwds) |
| 75 | + class TemporaryMetaSubclass(temporary_metaclass, _most_derived_metaclass(meta, bases)): |
| 76 | + |
| 77 | + if '__prepare__' not in temporary_metaclass.__dict__: |
| 78 | + # six<1.11.0, __prepare__ is not defined on the temporary metaclass. |
| 79 | + |
| 80 | + @classmethod |
| 81 | + def __prepare__(mcs, name, this_bases, **kwds): # pylint:disable=unused-argument,arguments-differ,bad-classmethod-argument |
| 82 | + return meta.__prepare__(name, bases, **kwds) |
76 | 83 |
|
77 | 84 | return type.__new__(TemporaryMetaSubclass, str('temporary_class'), bases, {}) |
| 85 | + |
| 86 | + |
| 87 | +def raise_from(value, _): # pylint:disable=unused-argument |
| 88 | + """Fallback for six.raise_from(), when using six<1.9.0.""" |
| 89 | + raise value |
| 90 | + |
| 91 | + |
| 92 | +raise_from = getattr(six, 'raise_from', raise_from) # pylint:disable=invalid-name |
| 93 | + |
| 94 | + |
| 95 | +def _most_derived_metaclass(meta, bases): |
| 96 | + """Selects the most derived metaclass of all the given metaclasses. |
| 97 | +
|
| 98 | + This will be the same metaclass that is selected by |
| 99 | +
|
| 100 | + .. code-block:: python |
| 101 | +
|
| 102 | + class temporary_class(*bases, metaclass=meta): pass |
| 103 | +
|
| 104 | + or equivalently by |
| 105 | +
|
| 106 | + .. code-block:: python |
| 107 | +
|
| 108 | + types.prepare_class('temporary_class', bases, metaclass=meta) |
| 109 | +
|
| 110 | + "Most derived" means the item in {meta, type(bases[0]), type(bases[1]), ...} |
| 111 | + which is a non-strict subclass of every item in that set. |
| 112 | +
|
| 113 | + If no such item exists, then :exc:`TypeError` is raised. |
| 114 | +
|
| 115 | + :type meta: `type` |
| 116 | + :type bases: :class:`Iterable` of `type` |
| 117 | + """ |
| 118 | + most_derived_metaclass = meta |
| 119 | + for base_type in map(type, bases): |
| 120 | + if issubclass(base_type, most_derived_metaclass): |
| 121 | + most_derived_metaclass = base_type |
| 122 | + elif not issubclass(most_derived_metaclass, base_type): |
| 123 | + # Raises TypeError('metaclass conflict: ...') |
| 124 | + return type.__new__(meta, str('temporary_class'), bases, {}) |
| 125 | + return most_derived_metaclass |
0 commit comments