diff options
author | Jacob Magnusson <m@jacobian.se> | 2016-03-04 16:06:12 +0100 |
---|---|---|
committer | Jacob Magnusson <m@jacobian.se> | 2016-03-04 16:20:33 +0100 |
commit | 4e55ffae0710741e158ba97e7a27ce2f44bd9f16 (patch) | |
tree | eba643462146762db5ace138d4d9b59eb3309432 | |
parent | 0e7904e730c3d2b0d3a394ad60010158ee29050c (diff) | |
download | sqlalchemy-pr/245.tar.gz |
Support recursive operation on _asdict and allow passing in dictionary implementationpr/245
-rw-r--r-- | lib/sqlalchemy/util/_collections.py | 66 | ||||
-rw-r--r-- | test/base/test_utils.py | 29 |
2 files changed, 86 insertions, 9 deletions
diff --git a/lib/sqlalchemy/util/_collections.py b/lib/sqlalchemy/util/_collections.py index c29b81f6a..ffd3107fa 100644 --- a/lib/sqlalchemy/util/_collections.py +++ b/lib/sqlalchemy/util/_collections.py @@ -98,16 +98,42 @@ class KeyedTuple(AbstractKeyedTuple): def __setattr__(self, key, value): raise AttributeError("Can't set attribute: %s" % key) - def _asdict(self): + def _asdict(self, impl=dict, recursive=False): """Return the contents of this :class:`.KeyedTuple` as a dictionary. This method provides compatibility with ``collections.namedtuple()``, - with the exception that the dictionary returned is **not** ordered. + with the exception that the dictionary returned is **not** ordered by + default. + + :param impl: + The implementation to use. Defaults to :class:`dict`. + + :param recursive: + Recursively call :meth:`._asdict` on values found that inherit from + AbstractKeyedTuple. Defaults to False. + .. versionadded:: 0.8 """ - return dict((key, self.__dict__[key]) for key in self.keys()) + + if recursive: + return impl( + ( + key, + ( + self.__dict__[key]._asdict( + impl=impl, + recursive=recursive + ) + if isinstance(self.__dict__[key], AbstractKeyedTuple) + else self.__dict__[key] + ) + ) + for key in self.keys() + ) + else: + return impl((key, self.__dict__[key]) for key in self.keys()) class _LW(AbstractKeyedTuple): @@ -122,10 +148,34 @@ class _LW(AbstractKeyedTuple): # difficulties return KeyedTuple, (list(self), self._real_fields) - def _asdict(self): - """Return the contents of this :class:`.KeyedTuple` as a dictionary.""" + def _asdict(self, impl=dict, recursive=False): + """Return the contents of this :class:`.KeyedTuple` as a dictionary. - d = dict(zip(self._real_fields, self)) + :param impl: + The implementation to use. Defaults to :class:`dict`. + + :param recursive: + Recursively call :meth:`._asdict` on values found that inherit from + AbstractKeyedTuple. Defaults to False. + + """ + + if recursive: + d = impl( + zip( + self._real_fields, + ( + ( + v._asdict(impl=impl, recursive=recursive) + if isinstance(v, AbstractKeyedTuple) + else v + ) + for v in self + ) + ) + ) + else: + d = impl(zip(self._real_fields, self)) d.pop(None, None) return d @@ -931,7 +981,7 @@ class LRUCache(dict): _lw_tuples = LRUCache(100) -def lightweight_named_tuple(name, fields): +def lightweight_named_tuple(name, fields, **kw): hash_ = (name, ) + tuple(fields) tp_cls = _lw_tuples.get(hash_) if tp_cls: @@ -942,7 +992,7 @@ def lightweight_named_tuple(name, fields): dict([ (field, _property_getters[idx]) for idx, field in enumerate(fields) if field is not None - ] + [('__slots__', ())]) + ] + [('__slots__', ())], **kw) ) tp_cls._real_fields = fields diff --git a/test/base/test_utils.py b/test/base/test_utils.py index fcb9a59a3..0dafdb63a 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -7,7 +7,7 @@ from sqlalchemy.testing import eq_, is_, ne_, fails_if, mock, expect_warnings from sqlalchemy.testing.util import picklers, gc_collect from sqlalchemy.util import classproperty, WeakSequence, get_callable_argspec from sqlalchemy.sql import column -from sqlalchemy.util import langhelpers, compat +from sqlalchemy.util import OrderedDict, langhelpers, compat import inspect @@ -121,6 +121,33 @@ class _KeyedTupleTest(object): eq_(kt._fields, ('a', 'b')) eq_(kt._asdict(), {'a': 1, 'b': 3}) + def test_asdict_impl(self): + keyed_tuple = self._fixture([1, 2, 3, 4], ['a', 'b', 'c', 'd']) + from sqlalchemy.util import OrderedDict + assert keyed_tuple._asdict(impl=OrderedDict) == OrderedDict( + [['a', 1], ['b', 2], ['c', 3], ['d', 4]] + ) + + def test_asdict_recursive(self): + keyed_tuple = self._fixture( + [1, 2, self._fixture([3, 4], ['d', 'e'])], + ['a', 'b', 'c'] + ) + eq_( + keyed_tuple._asdict(recursive=True), + {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}} + ) + + def test_asdict_impl_recursive(self): + keyed_tuple = self._fixture( + [1, 2, self._fixture([3, 4], ['d', 'e'])], + ['a', 'b', 'c'] + ) + dct = keyed_tuple._asdict(impl=OrderedDict, recursive=True) + assert dct == OrderedDict( + [['a', 1], ['b', 2], ['c', OrderedDict([['d', 3], ['e', 4]])]] + ) + class KeyedTupleTest(_KeyedTupleTest, fixtures.TestBase): def _fixture(self, values, labels): |