Optionally retain collection types (fixes #69)

This commit is contained in:
Fabian Kochem 2016-08-23 13:29:17 +02:00 committed by Hynek Schlawack
parent dd4140ebcd
commit c6fbec9637
3 changed files with 30 additions and 12 deletions

View File

@ -41,6 +41,9 @@ Changes:
- Add ``attr.attrs`` and ``attr.attrib`` as a more consistent aliases for ``attr.s`` and ``attr.ib``. - Add ``attr.attrs`` and ``attr.attrib`` as a more consistent aliases for ``attr.s`` and ``attr.ib``.
- Add ``frozen`` option to ``attr.s`` that will make instances best-effort immutable. - Add ``frozen`` option to ``attr.s`` that will make instances best-effort immutable.
`#60 <https://github.com/hynek/attrs/issues/60>`_ `#60 <https://github.com/hynek/attrs/issues/60>`_
- ``attr.asdict`` now takes ``retain_collection_types`` as an argument.
If ``True``, it does not convert attributes of type ``tuple`` or ``set`` to ``list``.
`#69 <https://github.com/hynek/attrs/issues/69>`_
---- ----

View File

@ -6,29 +6,31 @@ from ._compat import iteritems
from ._make import Attribute, NOTHING, fields from ._make import Attribute, NOTHING, fields
def asdict(inst, recurse=True, filter=None, dict_factory=dict): def asdict(inst, recurse=True, filter=None, dict_factory=dict,
retain_collection_types=False):
""" """
Return the ``attrs`` attribute values of *inst* as a dict. Optionally Return the ``attrs`` attribute values of *inst* as a dict.
recurse into other ``attrs``-decorated classes.
Optionally recurse into other ``attrs``-decorated classes.
:param inst: Instance of an ``attrs``-decorated class. :param inst: Instance of an ``attrs``-decorated class.
:param bool recurse: Recurse into classes that are also :param bool recurse: Recurse into classes that are also
``attrs``-decorated. ``attrs``-decorated.
:param callable filter: A callable whose return code deteremines whether an :param callable filter: A callable whose return code deteremines whether an
attribute or element is included (``True``) or dropped (``False``). Is attribute or element is included (``True``) or dropped (``False``). Is
called with the :class:`attr.Attribute` as the first argument and the called with the :class:`attr.Attribute` as the first argument and the
value as the second argument. value as the second argument.
:param callable dict_factory: A callable to produce dictionaries from. For
:param callable dict_factory: A callable to produce dictionaries from. For
example, to produce ordered dictionaries instead of normal Python example, to produce ordered dictionaries instead of normal Python
dictionaries, pass in ``collections.OrderedDict``. dictionaries, pass in ``collections.OrderedDict``.
:param bool retain_collection_types: Do not convert to ``list`` when
encountering an attribute which is type ``tuple`` or ``set``. Only
meaningful if ``recurse`` is ``True``.
:rtype: :class:`dict` :rtype: return type of *dict_factory*
.. versionadded:: 16.0.0 .. versionadded:: 16.0.0 *dict_factory*
*dict_factory* .. versionadded:: 16.1.0 *retain_collection_types*
""" """
attrs = fields(inst.__class__) attrs = fields(inst.__class__)
rv = dict_factory() rv = dict_factory()
@ -41,12 +43,13 @@ def asdict(inst, recurse=True, filter=None, dict_factory=dict):
rv[a.name] = asdict(v, recurse=True, filter=filter, rv[a.name] = asdict(v, recurse=True, filter=filter,
dict_factory=dict_factory) dict_factory=dict_factory)
elif isinstance(v, (tuple, list, set)): elif isinstance(v, (tuple, list, set)):
rv[a.name] = [ cf = v.__class__ if retain_collection_types is True else list
rv[a.name] = cf([
asdict(i, recurse=True, filter=filter, asdict(i, recurse=True, filter=filter,
dict_factory=dict_factory) dict_factory=dict_factory)
if has(i.__class__) else i if has(i.__class__) else i
for i in v for i in v
] ])
elif isinstance(v, dict): elif isinstance(v, dict):
df = dict_factory df = dict_factory
rv[a.name] = df(( rv[a.name] = df((

View File

@ -105,6 +105,18 @@ class TestAsDict(object):
"y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"], "y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"],
} == asdict(C(1, container([C(2, 3), C(4, 5), "a"]))) } == asdict(C(1, container([C(2, 3), C(4, 5), "a"])))
@given(container=st.sampled_from(SEQUENCE_TYPES))
def test_lists_tuples_retain_type(self, container, C):
"""
If recurse and retain_collection_types are True, also recurse
into lists and do not convert them into list.
"""
assert {
"x": 1,
"y": container([{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"]),
} == asdict(C(1, container([C(2, 3), C(4, 5), "a"])),
retain_collection_types=True)
@given(st.sampled_from(MAPPING_TYPES)) @given(st.sampled_from(MAPPING_TYPES))
def test_dicts(self, C, dict_factory): def test_dicts(self, C, dict_factory):
""" """