Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for attrs v21.3.0+ #1331

Merged
merged 9 commits into from Jan 20, 2022
8 changes: 7 additions & 1 deletion astroid/brain/brain_attrs.py
Expand Up @@ -10,16 +10,22 @@
from astroid.nodes.node_classes import AnnAssign, Assign, AssignName, Call, Unknown
from astroid.nodes.scoped_nodes import ClassDef

ATTRIB_NAMES = frozenset(("attr.ib", "attrib", "attr.attrib", "attr.field", "field"))
ATTRIB_NAMES = frozenset(
("attr.ib", "attrib", "attr.attrib", "attr.field", "attrs.field", "field")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could discuss if attrib and field should be in this list but that ship has sailed I think.

Thanks for removing define! 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right, it's also a problem.

Suggested change
("attr.ib", "attrib", "attr.attrib", "attr.field", "attrs.field", "field")
("attr.ib", "attr.attrib", "attr.field", "attrs.field")

Suggestion done in order to check if it's causing test fails. Imo we can most probably not infer the value as aliasing of attrs must be rare considering it's a small name, but we also should infer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like it breaks the existing test_attr_transform as well as the newly added test_attrs_transform.

        @attrs
        class Bah:
            d = attrib(attr.Factory(dict))

Also there's a regression test in pylint that also relies on the existing attrib; https://github.com/PyCQA/pylint/blob/main/tests/functional/r/regression/regression_4439.py

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Pierre-Sassoulas Let's revert then. Thanks @jacobbogdanov for checking this out.

)
ATTRS_NAMES = frozenset(
(
"attr.s",
"attrs",
"define",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add this.

Sadly this brain does not infer the actual function behind the decorator but only looks at the name. However, if anybody uses define as a decorator name in non-attr code they will now also get picked up by this brain. Fort some reason we allowed attrib and field here, see #1187 (review).

Do we want to add define as well @Pierre-Sassoulas? I can't imagine it being a very often used name for decorators, but it might create troubles down the line..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I don't like assuming that something is part of a lib only because of its name especially since we have inference capability in astroid. I know we're doing it for np in some place. But inference would permit to infer the real name of the origin lib. I don't know enough about brains to suggest something proper instead. What do you think @hippo91 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Pierre-Sassoulas i agree with you and @DanielNoord as well.
IMHO define should not be part of this set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I've removed @define from the list of supported decorator names.

"attr.attrs",
"attr.attributes",
"attr.define",
"attr.mutable",
"attr.frozen",
"attrs.define",
"attrs.mutable",
"attrs.frozen",
)
)

Expand Down
62 changes: 62 additions & 0 deletions tests/unittest_brain.py
Expand Up @@ -2211,6 +2211,68 @@ class Eggs:
should_be_unknown = next(module.getattr(name)[0].infer()).getattr("d")[0]
self.assertIsInstance(should_be_unknown, astroid.Unknown)

def test_attrs_transform(self) -> None:
jacobbogdanov marked this conversation as resolved.
Show resolved Hide resolved
module = astroid.parse(
"""
import attrs
from attrs import define, field, mutable, frozen

@attrs.define
class Foo:

d = attrs.field(attrs.Factory(dict))

f = Foo()
f.d['answer'] = 42

@define(slots=True)
class Bar:
d = field(attrs.Factory(dict))

g = Bar()
g.d['answer'] = 42

@attrs.mutable
class Bah:
d = field(attrs.Factory(dict))

h = Bah()
h.d['answer'] = 42

@attrs.frozen
class Bai:
d = attrs.field(attrs.Factory(dict))

i = Bai()
i.d['answer'] = 42

@attrs.define
class Spam:
d = field(default=attrs.Factory(dict))

j = Spam(d=1)
j.d['answer'] = 42

@attrs.mutable
class Eggs:
d = attrs.field(default=attrs.Factory(dict))

k = Eggs(d=1)
k.d['answer'] = 42

@attrs.frozen
class Eggs:
d = attrs.field(default=attrs.Factory(dict))

l = Eggs(d=1)
l.d['answer'] = 42
"""
)

for name in ("f", "g", "h", "i", "j", "k", "l"):
should_be_unknown = next(module.getattr(name)[0].infer()).getattr("d")[0]
self.assertIsInstance(should_be_unknown, astroid.Unknown)

def test_special_attributes(self) -> None:
"""Make sure special attrs attributes exist"""

Expand Down