summaryrefslogtreecommitdiff
path: root/test_six.py
diff options
context:
space:
mode:
authorJordan Moldow <jmoldow@alum.mit.edu>2017-09-17 11:13:15 -0700
committerBenjamin Peterson <benjamin@python.org>2017-09-17 11:13:15 -0700
commit96b93280fc4c70d65be0af5949f80f46f5c679ca (patch)
tree47543ef125bcf7f7b0bd3669b21e6cd9ed4ccac5 /test_six.py
parent024fcbb9c6f18a79b1e65f7bb9233c49cdd7fe88 (diff)
downloadsix-git-96b93280fc4c70d65be0af5949f80f46f5c679ca.tar.gz
Define __prepare__() in with_metaclass() (#178)
Define `__prepare__()` in `with_metaclass()`'s temporary metaclass, and make sure that it passes the correct bases to the real metaclass's `__prepare__()`. The temporary metaclass previously didn't extend the `__prepare__()` method, which meant that if the real metaclass had a `__prepare__()`, it wouldn't get called correctly. This could lead to bugs in Python 3 code. The temporary metaclass's `__prepare__()` gets called with ```bases=(temporary_class,)```. Since there was no proxy in the middle, that was getting passed directly to the real metaclass's `__prepare__()`. But then, if the real class's `__prepare__()` method depended on the bases, the logic would be incorrect. This was a problem in projects that use `enum` / `enum34` and try to use `with_metaclass(EnumMeta)`. `enum34.EnumMeta` doesn't define `__prepare__()`, since it is a Python 2 backport. Python 3's `enum.EnumMeta` does define `__prepare__()`, but originally didn't depend at all on the bases. But starting in Python 3.6, `enum.EnumMeta.__prepare__()` will raise `TypeError` if the bases aren't valid for an enum subclass. Thus, a codebase that was successfully using `enum` / `enum34` and `with_metaclass(EnumMeta)` could break on Python 3.6.
Diffstat (limited to 'test_six.py')
-rw-r--r--test_six.py28
1 files changed, 28 insertions, 0 deletions
diff --git a/test_six.py b/test_six.py
index da6fa57..f390b13 100644
--- a/test_six.py
+++ b/test_six.py
@@ -736,6 +736,34 @@ def test_with_metaclass():
assert X.__mro__ == (X, Base, Base2, object)
+@py.test.mark.skipif("sys.version_info[:2] < (3, 0)")
+def test_with_metaclass_prepare():
+ """Test that with_metaclass causes Meta.__prepare__ to be called with the correct arguments."""
+
+ class MyDict(dict):
+ pass
+
+ class Meta(type):
+
+ @classmethod
+ def __prepare__(cls, name, bases):
+ namespace = MyDict(super().__prepare__(name, bases), cls=cls, bases=bases)
+ namespace['namespace'] = namespace
+ return namespace
+
+ class Base(object):
+ pass
+
+ bases = (Base,)
+
+ class X(six.with_metaclass(Meta, *bases)):
+ pass
+
+ assert getattr(X, 'cls', type) is Meta
+ assert getattr(X, 'bases', ()) == bases
+ assert isinstance(getattr(X, 'namespace', {}), MyDict)
+
+
def test_wraps():
def f(g):
@six.wraps(g)