Optionally retain collection types (fixes #69)
This commit is contained in:
parent
dd4140ebcd
commit
c6fbec9637
|
@ -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>`_
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
|
@ -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((
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue