Skip to content

Commit

Permalink
Handle inherited __annotations__ (#292)
Browse files Browse the repository at this point in the history
* Handle inherited __annotations__

Fixes #291

* Add pr fragment
  • Loading branch information
hynek committed Nov 14, 2017
1 parent a84a36d commit f5b59a0
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.d/291.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Subclasses of ``auto_attribs=True`` can be empty now.
1 change: 1 addition & 0 deletions changelog.d/292.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Subclasses of ``auto_attribs=True`` can be empty now.
36 changes: 26 additions & 10 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,22 @@ def _is_class_var(annot):
return str(annot).startswith("typing.ClassVar")


def _get_annotations(cls):
"""
Get annotations for *cls*.
"""
anns = getattr(cls, "__annotations__", None)
if anns is None:
return {}

# Verify that the annotations aren't merely inherited.
for super_cls in cls.__mro__[1:]:
if anns is getattr(super_cls, "__annotations__", None):
return {}

return anns


def _transform_attrs(cls, these, auto_attribs):
"""
Transform all `_CountingAttr`s on a class into `Attribute`s.
Expand All @@ -210,16 +226,15 @@ def _transform_attrs(cls, these, auto_attribs):
Return an `_Attributes`.
"""
cd = cls.__dict__
anns = getattr(cls, "__annotations__", {})
anns = _get_annotations(cls)

if these is None and auto_attribs is False:
if these is not None:
ca_list = sorted((
(name, attr)
for name, attr
in cd.items()
if isinstance(attr, _CountingAttr)
(name, ca)
for name, ca
in iteritems(these)
), key=lambda e: e[1].counter)
elif these is None and auto_attribs is True:
elif auto_attribs is True:
ca_names = {
name
for name, attr
Expand Down Expand Up @@ -251,9 +266,10 @@ def _transform_attrs(cls, these, auto_attribs):
)
else:
ca_list = sorted((
(name, ca)
for name, ca
in iteritems(these)
(name, attr)
for name, attr
in cd.items()
if isinstance(attr, _CountingAttr)
), key=lambda e: e[1].counter)

non_super_attrs = [
Expand Down
23 changes: 23 additions & 0 deletions tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,26 @@ class C:
assert (
"The following `attr.ib`s lack a type annotation: v, y.",
) == e.value.args

@pytest.mark.parametrize("slots", [True, False])
def test_auto_attribs_subclassing(self, slots):
"""
Attributes from super classes are inherited, it doesn't matter if the
subclass has annotations or not.
Ref #291
"""
@attr.s(slots=slots, auto_attribs=True)
class A:
a: int = 1

@attr.s(slots=slots, auto_attribs=True)
class B(A):
b: int = 2

@attr.s(slots=slots, auto_attribs=True)
class C(A):
pass

assert "B(a=1, b=2)" == repr(B())
assert "C(a=1)" == repr(C())

0 comments on commit f5b59a0

Please sign in to comment.