Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelog.d/704.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``attr.asdict()`` and ``attr.astuple()`` now treat ``frozenset``\ s like ``set``\ s with regards to the *retain_collection_types* argument.
10 changes: 7 additions & 3 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ def asdict(
v = getattr(inst, a.name)
if filter is not None and not filter(a, v):
continue

if value_serializer is not None:
v = value_serializer(inst, a, v)

if recurse is True:
if has(v.__class__):
rv[a.name] = asdict(
Expand All @@ -65,7 +67,7 @@ def asdict(
retain_collection_types,
value_serializer,
)
elif isinstance(v, (tuple, list, set)):
elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain_collection_types is True else list
rv[a.name] = cf(
[
Expand Down Expand Up @@ -127,7 +129,7 @@ def _asdict_anything(
retain_collection_types,
value_serializer,
)
elif isinstance(val, (tuple, list, set)):
elif isinstance(val, (tuple, list, set, frozenset)):
cf = val.__class__ if retain_collection_types is True else list
rv = cf(
[
Expand Down Expand Up @@ -158,6 +160,7 @@ def _asdict_anything(
rv = val
if value_serializer is not None:
rv = value_serializer(None, None, rv)

return rv


Expand Down Expand Up @@ -212,7 +215,7 @@ def astuple(
retain_collection_types=retain,
)
)
elif isinstance(v, (tuple, list, set)):
elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain is True else list
rv.append(
cf(
Expand Down Expand Up @@ -257,6 +260,7 @@ def astuple(
rv.append(v)
else:
rv.append(v)

return rv if tuple_factory is list else tuple_factory(rv)


Expand Down
27 changes: 27 additions & 0 deletions tests/test_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,26 @@ def test_lists_tuples_retain_type(self, container, C):
retain_collection_types=True,
)

@given(set_type=st.sampled_from((set, frozenset)))
def test_sets_no_retain(self, C, set_type):
"""
Set types are converted to lists if retain_collection_types=False.
"""
d = asdict(
C(1, set_type((1, 2, 3))),
retain_collection_types=False,
recurse=True,
)

assert {"x": 1, "y": [1, 2, 3]} == d

@given(st.sampled_from(MAPPING_TYPES))
def test_dicts(self, C, dict_factory):
"""
If recurse is True, also recurse into dicts.
"""
res = asdict(C(1, {"a": C(4, 5)}), dict_factory=dict_factory)

assert {"x": 1, "y": {"a": {"x": 4, "y": 5}}} == res
assert isinstance(res, dict_factory)

Expand Down Expand Up @@ -330,6 +344,19 @@ def test_roundtrip(self, cls, tuple_class):

assert instance == roundtrip_instance

@given(set_type=st.sampled_from((set, frozenset)))
def test_sets_no_retain(self, C, set_type):
"""
Set types are converted to lists if retain_collection_types=False.
"""
d = astuple(
C(1, set_type((1, 2, 3))),
retain_collection_types=False,
recurse=True,
)

assert (1, [1, 2, 3]) == d


class TestHas(object):
"""
Expand Down