Skip to content

Commit

Permalink
Fix bug in resolve_types with subclasses (#843)
Browse files Browse the repository at this point in the history
* Fix bug in resolve_types with subclasses

* lint

* changelog

* lint again, like we did last summer

* Moar coverage

* lint ... why

Co-authored-by: Hynek Schlawack <[email protected]>
  • Loading branch information
euresti and hynek authored Sep 19, 2021
1 parent 7c99a49 commit f31bb28
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 6 deletions.
2 changes: 2 additions & 0 deletions changelog.d/843.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``attr.resolve_types()`` now resolves types of subclasses after the parents are resolved.
`#842 <https://github.com/python-attrs/attrs/issues/842>`_
12 changes: 6 additions & 6 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,19 @@ class and you didn't pass any attribs.
.. versionadded:: 21.1.0 *attribs*
"""
try:
# Since calling get_type_hints is expensive we cache whether we've
# done it already.
cls.__attrs_types_resolved__
except AttributeError:
# Since calling get_type_hints is expensive we cache whether we've
# done it already.
if getattr(cls, "__attrs_types_resolved__", None) != cls:
import typing

hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
for field in fields(cls) if attribs is None else attribs:
if field.name in hints:
# Since fields have been frozen we must work around it.
_obj_setattr(field, "type", hints[field.name])
cls.__attrs_types_resolved__ = True
# We store the class we resolved so that subclasses know they haven't
# been resolved.
cls.__attrs_types_resolved__ = cls

# Return the class so you can use it as a decorator too.
return cls
34 changes: 34 additions & 0 deletions tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,40 @@ class C:
with pytest.raises(NameError):
typing.get_type_hints(C.__init__)

def test_inheritance(self):
"""
Subclasses can be resolved after the parent is resolved.
"""

@attr.define()
class A:
n: "int"

@attr.define()
class B(A):
pass

attr.resolve_types(A)
attr.resolve_types(B)

assert int == attr.fields(A).n.type
assert int == attr.fields(B).n.type

def test_resolve_twice(self):
"""
You can call resolve_types as many times as you like.
This test is here mostly for coverage.
"""

@attr.define()
class A:
n: "int"

attr.resolve_types(A)
assert int == attr.fields(A).n.type
attr.resolve_types(A)
assert int == attr.fields(A).n.type


@pytest.mark.parametrize(
"annot",
Expand Down

0 comments on commit f31bb28

Please sign in to comment.