Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Changes:
- 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.
`#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>`_


----
Expand Down
13 changes: 10 additions & 3 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
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
recurse into other ``attrs``-decorated classes.
Expand All @@ -25,6 +26,11 @@ def asdict(inst, recurse=True, filter=None, dict_factory=dict):
example, to produce ordered dictionaries instead of normal Python
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`

.. versionadded:: 16.0.0
Expand All @@ -41,12 +47,13 @@ def asdict(inst, recurse=True, filter=None, dict_factory=dict):
rv[a.name] = asdict(v, recurse=True, filter=filter,
dict_factory=dict_factory)
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,
dict_factory=dict_factory)
if has(i.__class__) else i
for i in v
]
])
elif isinstance(v, dict):
df = dict_factory
rv[a.name] = df((
Expand Down
12 changes: 12 additions & 0 deletions tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ def test_lists_tuples(self, container, C):
"y": [{"x": 2, "y": 3}, {"x": 4, "y": 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))
def test_dicts(self, C, dict_factory):
"""
Expand Down