summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorR David Murray <rdmurray@bitdance.com>2012-05-31 22:00:45 (GMT)
committerR David Murray <rdmurray@bitdance.com>2012-05-31 22:00:45 (GMT)
commit1be413e366be7bed676074b7f395b2a20a10ac47 (patch)
tree1fb22c928ffb044049b606de24ab68035a941b1f
parent8e0ed333b9d52477a90ed36fdf0eb31c3d432e91 (diff)
downloadcpython-1be413e366be7bed676074b7f395b2a20a10ac47.zip
cpython-1be413e366be7bed676074b7f395b2a20a10ac47.tar.gz
cpython-1be413e366be7bed676074b7f395b2a20a10ac47.tar.bz2
Don't use metaclasses when class decorators can do the job.
Thanks to Nick Coghlan for pointing out that I'd forgotten about class decorators.
-rw-r--r--Lib/email/_policybase.py45
-rw-r--r--Lib/email/policy.py3
-rw-r--r--Lib/test/test_email/__init__.py66
-rw-r--r--Lib/test/test_email/test_generator.py5
-rw-r--r--Lib/test/test_email/test_headerregistry.py5
-rw-r--r--Lib/test/test_email/test_pickleable.py9
6 files changed, 66 insertions, 67 deletions
diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py
index d5e8df9..8106114 100644
--- a/Lib/email/_policybase.py
+++ b/Lib/email/_policybase.py
@@ -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):
"""+
diff --git a/Lib/email/policy.py b/Lib/email/policy.py
index bfffb45..32cad0d 100644
--- a/Lib/email/policy.py
+++ b/Lib/email/policy.py
@@ -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):
"""+
diff --git a/Lib/test/test_email/__init__.py b/Lib/test/test_email/__init__.py
index bd9d52c..f206ace 100644
--- a/Lib/test/test_email/__init__.py
+++ b/Lib/test/test_email/__init__.py
@@ -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
diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py
index 7e1529b..8917408 100644
--- a/Lib/test/test_email/test_generator.py
+++ b/Lib/test/test_email/test_generator.py
@@ -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
diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py
index 4a57ff1..fc11fba 100644
--- a/Lib/test/test_email/test_headerregistry.py
+++ b/Lib/test/test_email/test_headerregistry.py
@@ -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 = {
diff --git a/Lib/test/test_email/test_pickleable.py b/Lib/test/test_email/test_pickleable.py
index 09477e0..daa8d25 100644
--- a/Lib/test/test_email/test_pickleable.py
+++ b/Lib/test/test_email/test_pickleable.py
@@ -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