Bug 1132771 - Implement strongly typed named tuples; r=glandium

An upcoming patch introduces a use case for a strongly typed named
tuple. So, we introduce a generic factory function that can produce these
types.

--HG--
extra : rebase_source : 7f4d17ff28925fbe8d850c036605aa03a38f0ef2
extra : source : acdd5491f10ecf8ea4e1a14150f9a2e282e2cf5d
This commit is contained in:
Gregory Szorc 2015-02-26 09:38:43 -08:00
parent 0950ccbf67
commit d4f1a46062
2 changed files with 78 additions and 0 deletions

View File

@ -32,6 +32,7 @@ from mozbuild.util import (
StrictOrderingOnAppendList,
StrictOrderingOnAppendListWithFlagsFactory,
TypedList,
TypedNamedTuple,
UnsortedError,
)
@ -663,6 +664,33 @@ class TypedTestStrictOrderingOnAppendList(unittest.TestCase):
self.assertEqual(len(l), 3)
class TestTypedNamedTuple(unittest.TestCase):
def test_simple(self):
FooBar = TypedNamedTuple('FooBar', [('foo', unicode), ('bar', int)])
t = FooBar(foo='foo', bar=2)
self.assertEquals(type(t), FooBar)
self.assertEquals(t.foo, 'foo')
self.assertEquals(t.bar, 2)
self.assertEquals(t[0], 'foo')
self.assertEquals(t[1], 2)
FooBar('foo', 2)
with self.assertRaises(TypeError):
FooBar('foo', 'not integer')
with self.assertRaises(TypeError):
FooBar(2, 4)
# Passing a tuple as the first argument is the same as passing multiple
# arguments.
t1 = ('foo', 3)
t2 = FooBar(t1)
self.assertEquals(type(t2), FooBar)
self.assertEqual(FooBar(t1), FooBar('foo', 3))
class TestGroupUnifiedFiles(unittest.TestCase):
FILES = ['%s.cpp' % letter for letter in string.ascii_lowercase]

View File

@ -817,6 +817,56 @@ class memoized_property(object):
return getattr(instance, name)
def TypedNamedTuple(name, fields):
"""Factory for named tuple types with strong typing.
Arguments are an iterable of 2-tuples. The first member is the
the field name. The second member is a type the field will be validated
to be.
Construction of instances varies from ``collections.namedtuple``.
First, if a single tuple argument is given to the constructor, this is
treated as the equivalent of passing each tuple value as a separate
argument into __init__. e.g.::
t = (1, 2)
TypedTuple(t) == TypedTuple(1, 2)
This behavior is meant for moz.build files, so vanilla tuples are
automatically cast to typed tuple instances.
Second, fields in the tuple are validated to be instances of the specified
type. This is done via an ``isinstance()`` check. To allow multiple types,
pass a tuple as the allowed types field.
"""
cls = collections.namedtuple(name, (name for name, typ in fields))
class TypedTuple(cls):
__slots__ = ()
def __new__(klass, *args, **kwargs):
if len(args) == 1 and not kwargs and isinstance(args[0], tuple):
args = args[0]
return super(TypedTuple, klass).__new__(klass, *args, **kwargs)
def __init__(self, *args, **kwargs):
for i, (fname, ftype) in enumerate(self._fields):
value = self[i]
if not isinstance(value, ftype):
raise TypeError('field in tuple not of proper type: %s; '
'got %s, expected %s' % (fname,
type(value), ftype))
super(TypedTuple, self).__init__(*args, **kwargs)
TypedTuple._fields = fields
return TypedTuple
class TypedListMixin(object):
'''Mixin for a list with type coercion. See TypedList.'''