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.
This commit is contained in:
parent
8e0ed333b9
commit
1be413e366
|
@ -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 _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 __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
|
||||
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=_DocstringExtenderMetaclass):
|
||||
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):
|
||||
|
||||
"""+
|
||||
|
|
|
@ -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):
|
||||
|
||||
"""+
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue