Skip to content

Commit

Permalink
Convert transformed attrs to AttrsClass (#824)
Browse files Browse the repository at this point in the history
* Convert transformed attrs to AttrsClass

Fixes: #821

* Add cangelog entry

* Only call AttrsClass once

* Calm mypy by inline the AttrsClass call

* Defer AttrsClass creation as long as possible

Co-authored-by: Hynek Schlawack <hs@ox.cx>
  • Loading branch information
sscherfke and hynek committed Sep 22, 2021
1 parent f31bb28 commit f57b6a6
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 6 deletions.
1 change: 1 addition & 0 deletions changelog.d/824.changes.rst
@@ -0,0 +1 @@
Attributes transformed via ``field_transformer`` are wrapped with ``AttrsClass`` again.
14 changes: 8 additions & 6 deletions src/attr/_make.py
Expand Up @@ -581,15 +581,11 @@ def _transform_attrs(
cls, {a.name for a in own_attrs}
)

attr_names = [a.name for a in base_attrs + own_attrs]

AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)

if kw_only:
own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
base_attrs = [a.evolve(kw_only=True) for a in base_attrs]

attrs = AttrsClass(base_attrs + own_attrs)
attrs = base_attrs + own_attrs

# Mandatory vs non-mandatory attr order only matters when they are part of
# the __init__ signature and when they aren't kw_only (which are moved to
Expand All @@ -608,7 +604,13 @@ def _transform_attrs(

if field_transformer is not None:
attrs = field_transformer(cls, attrs)
return _Attributes((attrs, base_attrs, base_attr_map))

# Create AttrsClass *after* applying the field_transformer since it may
# add or remove attributes!
attr_names = [a.name for a in attrs]
AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)

return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map))


if PYPY:
Expand Down
16 changes: 16 additions & 0 deletions tests/test_hooks.py
Expand Up @@ -117,6 +117,22 @@ class Sub(Base):

assert attr.asdict(Sub(2)) == {"y": 2}

def test_attrs_attrclass(self):
"""
The list of attrs returned by a field_transformer is converted to
"AttrsClass" again.
Regression test for #821.
"""

@attr.s(auto_attribs=True, field_transformer=lambda c, a: list(a))
class C:
x: int

fields_type = type(attr.fields(C))
assert fields_type.__name__ == "CAttributes"
assert issubclass(fields_type, tuple)


class TestAsDictHook:
def test_asdict(self):
Expand Down

0 comments on commit f57b6a6

Please sign in to comment.