diff --git a/changelog.d/1641.change.md b/changelog.d/1641.change.md new file mode 100644 index 000000000..7caf59646 --- /dev/null +++ b/changelog.d/1641.change.md @@ -0,0 +1 @@ +`attrs.astuple()` and `attr.astuple()` now apply the *filter* to attrs instances used as dictionary keys and values, consistently with other recursive conversions. diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py index d213efc22..ffa7a7a86 100644 --- a/src/attr/_funcs.py +++ b/src/attr/_funcs.py @@ -238,6 +238,8 @@ def astuple( Return the *attrs* attribute values of *inst* as a tuple. Optionally recurse into other *attrs*-decorated classes. + When recursing, *filter* also applies to *attrs* instances used as dictionary + keys and values. Args: inst: Instance of an *attrs*-decorated class. @@ -268,6 +270,8 @@ def astuple( If *cls* is not an *attrs* class. .. versionadded:: 16.2.0 + .. versionchanged:: 26.2.0 + Apply *filter* to attrs instances in dictionary keys and values. """ attrs = fields(inst.__class__) rv = [] @@ -322,6 +326,7 @@ def astuple( ( astuple( kk, + filter=filter, tuple_factory=tuple_factory, retain_collection_types=retain, ) @@ -331,6 +336,7 @@ def astuple( ( astuple( vv, + filter=filter, tuple_factory=tuple_factory, retain_collection_types=retain, ) diff --git a/tests/test_funcs.py b/tests/test_funcs.py index f35a9a564..9c60dcfab 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -409,6 +409,29 @@ def test_dicts(self, C, tuple_factory): assert tuple_factory([1, {"a": tuple_factory([4, 5])}]) == res assert isinstance(res, tuple_factory) + @pytest.mark.parametrize("retain", [False, True]) + @pytest.mark.parametrize("container", MAPPING_TYPES) + def test_dicts_filter(self, C, retain, container): + """ + Filters apply to attrs instances used as dictionary keys and values. + """ + + @attr.s(frozen=True) + class Key: + x = attr.ib() + y = attr.ib() + + inst = C(container({Key(1, 2): C(3, 4)}), 5) + + result = astuple( + inst, + filter=lambda a, v: a.name != "y", + retain_collection_types=retain, + ) + + assert ({(1,): (3,)},) == result + assert (container if retain else dict) is type(result[0]) + @given(container=st.sampled_from(SEQUENCE_TYPES)) def test_lists_tuples_retain_type(self, container, C): """