]> granicus.if.org Git - python/commitdiff
Don't use metaclasses when class decorators can do the job.
authorR David Murray <rdmurray@bitdance.com>
Thu, 31 May 2012 22:00:45 +0000 (18:00 -0400)
committerR David Murray <rdmurray@bitdance.com>
Thu, 31 May 2012 22:00:45 +0000 (18:00 -0400)
Thanks to Nick Coghlan for pointing out that I'd forgotten about class
decorators.

Lib/email/_policybase.py
Lib/email/policy.py
Lib/test/test_email/__init__.py
Lib/test/test_email/test_generator.py
Lib/test/test_email/test_headerregistry.py
Lib/test/test_email/test_pickleable.py

index d5e8df99404c050f9d940da7b7b2ece2b3ec6776..81061149c3cd0f1f37de1d5cb81b08f9f760547f 100644 (file)
@@ -91,31 +91,25 @@ class _PolicyBase:
         return self.clone(**other.__dict__)
 
 
-# Conceptually this isn't a subclass of ABCMeta, but since we want Policy to
-# use ABCMeta as a metaclass *and* we want it to use this one as well, we have
-# to make this one a subclas of ABCMeta.
-class _DocstringExtenderMetaclass(abc.ABCMeta):
-
-    def __new__(meta, classname, bases, classdict):
-        if classdict.get('__doc__') and classdict['__doc__'].startswith('+'):
-            classdict['__doc__'] = meta._append_doc(bases[0].__doc__,
-                                                    classdict['__doc__'])
-        for name, attr in classdict.items():
-            if attr.__doc__ and attr.__doc__.startswith('+'):
-                for cls in (cls for base in bases for cls in base.mro()):
-                    doc = getattr(getattr(cls, name), '__doc__')
-                    if doc:
-                        attr.__doc__ = meta._append_doc(doc, attr.__doc__)
-                        break
-        return super().__new__(meta, classname, bases, classdict)
-
-    @staticmethod
-    def _append_doc(doc, added_doc):
-        added_doc = added_doc.split('\n', 1)[1]
-        return doc + '\n' + added_doc
-
-
-class Policy(_PolicyBase, metaclass=_DocstringExtenderMetaclass):
+def _append_doc(doc, added_doc):
+    doc = doc.rsplit('\n', 1)[0]
+    added_doc = added_doc.split('\n', 1)[1]
+    return doc + '\n' + added_doc
+
+def _extend_docstrings(cls):
+    if cls.__doc__ and cls.__doc__.startswith('+'):
+        cls.__doc__ = _append_doc(cls.__bases__[0].__doc__, cls.__doc__)
+    for name, attr in cls.__dict__.items():
+        if attr.__doc__ and attr.__doc__.startswith('+'):
+            for c in (c for base in cls.__bases__ for c in base.mro()):
+                doc = getattr(getattr(c, name), '__doc__')
+                if doc:
+                    attr.__doc__ = _append_doc(doc, attr.__doc__)
+                    break
+    return cls
+
+
+class Policy(_PolicyBase, metaclass=abc.ABCMeta):
 
     r"""Controls for how messages are interpreted and formatted.
 
@@ -264,6 +258,7 @@ class Policy(_PolicyBase, metaclass=_DocstringExtenderMetaclass):
         raise NotImplementedError
 
 
+@_extend_docstrings
 class Compat32(Policy):
 
     """+
index bfffb457dcd60145e25fc5469163fcf1d1cad772..32cad0d505f987a614824ae00f40ef90a36df374 100644 (file)
@@ -2,7 +2,7 @@
 code that adds all the email6 features.
 """
 
-from email._policybase import Policy, Compat32, compat32
+from email._policybase import Policy, Compat32, compat32, _extend_docstrings
 from email.utils import _has_surrogates
 from email.headerregistry import HeaderRegistry as HeaderRegistry
 
@@ -17,6 +17,7 @@ __all__ = [
     'HTTP',
     ]
 
+@_extend_docstrings
 class EmailPolicy(Policy):
 
     """+
index bd9d52c3c5ad0f9cf72097b77d74b94816356aba..f206ace10c1b53fae27c5372895914904ccaca66 100644 (file)
@@ -73,10 +73,8 @@ class TestEmailBase(unittest.TestCase):
                                     'item {}'.format(i))
 
 
-# Metaclass to allow for parameterized tests
-class Parameterized(type):
-
-    """Provide a test method parameterization facility.
+def parameterize(cls):
+    """A test method parameterization class decorator.
 
     Parameters are specified as the value of a class attribute that ends with
     the string '_params'.  Call the portion before '_params' the prefix.  Then
@@ -92,9 +90,10 @@ class Parameterized(type):
     In a _params dictioanry, the keys become part of the name of the generated
     tests.  In a _params list, the values in the list are converted into a
     string by joining the string values of the elements of the tuple by '_' and
-    converting any blanks into '_'s, and this become part of the name.  The
-    full name of a generated test is the portion of the _params name before the
-    '_params' portion, plus an '_', plus the name derived as explained above.
+    converting any blanks into '_'s, and this become part of the name.
+    The  full name of a generated test is a 'test_' prefix, the portion of the
+    test function name after the  '_as_' separator, plus an '_', plus the name
+    derived as explained above.
 
     For example, if we have:
 
@@ -123,30 +122,29 @@ class Parameterized(type):
     be used to select the test individually from the unittest command line.
 
     """
-
-    def __new__(meta, classname, bases, classdict):
-        paramdicts = {}
-        for name, attr in classdict.items():
-            if name.endswith('_params'):
-                if not hasattr(attr, 'keys'):
-                    d = {}
-                    for x in attr:
-                        if not hasattr(x, '__iter__'):
-                            x = (x,)
-                        n = '_'.join(str(v) for v in x).replace(' ', '_')
-                        d[n] = x
-                    attr = d
-                paramdicts[name[:-7] + '_as_'] = attr
-        testfuncs = {}
-        for name, attr in classdict.items():
-            for paramsname, paramsdict in paramdicts.items():
-                if name.startswith(paramsname):
-                    testnameroot = 'test_' + name[len(paramsname):]
-                    for paramname, params in paramsdict.items():
-                        test = (lambda self, name=name, params=params:
-                                        getattr(self, name)(*params))
-                        testname = testnameroot + '_' + paramname
-                        test.__name__ = testname
-                        testfuncs[testname] = test
-        classdict.update(testfuncs)
-        return super().__new__(meta, classname, bases, classdict)
+    paramdicts = {}
+    for name, attr in cls.__dict__.items():
+        if name.endswith('_params'):
+            if not hasattr(attr, 'keys'):
+                d = {}
+                for x in attr:
+                    if not hasattr(x, '__iter__'):
+                        x = (x,)
+                    n = '_'.join(str(v) for v in x).replace(' ', '_')
+                    d[n] = x
+                attr = d
+            paramdicts[name[:-7] + '_as_'] = attr
+    testfuncs = {}
+    for name, attr in cls.__dict__.items():
+        for paramsname, paramsdict in paramdicts.items():
+            if name.startswith(paramsname):
+                testnameroot = 'test_' + name[len(paramsname):]
+                for paramname, params in paramsdict.items():
+                    test = (lambda self, name=name, params=params:
+                                    getattr(self, name)(*params))
+                    testname = testnameroot + '_' + paramname
+                    test.__name__ = testname
+                    testfuncs[testname] = test
+    for key, value in testfuncs.items():
+        setattr(cls, key, value)
+    return cls
index 7e1529be381dad26deafe3c2398892afeb68bb63..89174081719238e491cca8e015c0059cf455ba93 100644 (file)
@@ -4,10 +4,11 @@ import unittest
 from email import message_from_string, message_from_bytes
 from email.generator import Generator, BytesGenerator
 from email import policy
-from test.test_email import TestEmailBase, Parameterized
+from test.test_email import TestEmailBase, parameterize
 
 
-class TestGeneratorBase(metaclass=Parameterized):
+@parameterize
+class TestGeneratorBase:
 
     policy = policy.default
 
index 4a57ff14a4f8602cafb12833bfc549a68fe700e8..fc11fbaf98be11ef1d150623e6082b7b404b928c 100644 (file)
@@ -4,7 +4,7 @@ import unittest
 from email import errors
 from email import policy
 from email.message import Message
-from test.test_email import TestEmailBase, Parameterized
+from test.test_email import TestEmailBase, parameterize
 from email import headerregistry
 from email.headerregistry import Address, Group
 
@@ -175,7 +175,8 @@ class TestDateHeader(TestHeaderBase):
         self.assertEqual(m['Date'].datetime, self.dt)
 
 
-class TestAddressHeader(TestHeaderBase, metaclass=Parameterized):
+@parameterize
+class TestAddressHeader(TestHeaderBase):
 
     example_params = {
 
index 09477e042172d9476604ff8ea5d2847691784644..daa8d25053dc4637d53c204d696be8fa9b343b91 100644 (file)
@@ -6,9 +6,11 @@ import email
 import email.message
 from email import policy
 from email.headerregistry import HeaderRegistry
-from test.test_email import TestEmailBase, Parameterized
+from test.test_email import TestEmailBase, parameterize
 
-class TestPickleCopyHeader(TestEmailBase, metaclass=Parameterized):
+
+@parameterize
+class TestPickleCopyHeader(TestEmailBase):
 
     header_factory = HeaderRegistry()
 
@@ -33,7 +35,8 @@ class TestPickleCopyHeader(TestEmailBase, metaclass=Parameterized):
         self.assertEqual(str(h), str(header))
 
 
-class TestPickleCopyMessage(TestEmailBase, metaclass=Parameterized):
+@parameterize
+class TestPickleCopyMessage(TestEmailBase):
 
     # Message objects are a sequence, so we have to make them a one-tuple in
     # msg_params so they get passed to the parameterized test method as a