Skip to content

Commit 6f1eb89

Browse files
authored
Add the __annotations__ attribute to the ClassDef object model. (#2431)
* Add the ``__annotations__`` attribute to the ``ClassDef`` object model. Pylint now does not emit a ``no-member`` error when accessing ``__annotations`` in the following cases: ``` class Test: print(__annotations__) ``` ``` from typing import TypedDict OtherTypedDict = TypedDict('OtherTypedDict', {'a': int, 'b': str}) print(OtherTypedDict.__annotations__) ``` Closes pylint-dev/pylint#7126
1 parent e68c016 commit 6f1eb89

File tree

5 files changed

+55
-2
lines changed

5 files changed

+55
-2
lines changed

astroid/interpreter/objectmodel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,10 @@ def __init__(self):
492492

493493
super().__init__()
494494

495+
@property
496+
def attr___annotations__(self) -> node_classes.Unkown:
497+
return node_classes.Unknown()
498+
495499
@property
496500
def attr___module__(self):
497501
return node_classes.Const(self._instance.root().qname())

astroid/nodes/scoped_nodes/scoped_nodes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1946,7 +1946,10 @@ def implicit_locals(self):
19461946
"""
19471947
locals_ = (("__module__", self.special_attributes.attr___module__),)
19481948
# __qualname__ is defined in PEP3155
1949-
locals_ += (("__qualname__", self.special_attributes.attr___qualname__),)
1949+
locals_ += (
1950+
("__qualname__", self.special_attributes.attr___qualname__),
1951+
("__annotations__", self.special_attributes.attr___annotations__),
1952+
)
19501953
return locals_
19511954

19521955
# pylint: disable=redefined-outer-name

tests/test_builder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,12 +841,13 @@ def test_class_locals(self) -> None:
841841
klass1 = module["YO"]
842842
locals1 = klass1.locals
843843
keys = sorted(locals1.keys())
844-
assert_keys = ["__init__", "__module__", "__qualname__", "a"]
844+
assert_keys = ["__annotations__", "__init__", "__module__", "__qualname__", "a"]
845845
self.assertEqual(keys, assert_keys)
846846
klass2 = module["YOUPI"]
847847
locals2 = klass2.locals
848848
keys = locals2.keys()
849849
assert_keys = [
850+
"__annotations__",
850851
"__init__",
851852
"__module__",
852853
"__qualname__",

tests/test_object_model.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,3 +855,47 @@ def foo():
855855
assert wrapped.name == "foo"
856856
cache_info = next(ast_nodes[2].infer())
857857
assert isinstance(cache_info, astroid.Instance)
858+
859+
860+
def test_class_annotations() -> None:
861+
"""Test that the `__annotations__` attribute is avaiable in the class scope"""
862+
annotations, klass_attribute = builder.extract_node(
863+
"""
864+
class Test:
865+
__annotations__ #@
866+
Test.__annotations__ #@
867+
"""
868+
)
869+
# Test that `__annotations__` attribute is available in the class scope:
870+
assert isinstance(annotations, nodes.Name)
871+
# The `__annotations__` attribute is `Uninferable`:
872+
assert next(annotations.infer()) is astroid.Uninferable
873+
874+
# Test that we can access the class annotations:
875+
assert isinstance(klass_attribute, nodes.Attribute)
876+
877+
878+
def test_class_annotations_typed_dict() -> None:
879+
"""Test that we can access class annotations on various TypedDicts"""
880+
apple, pear = builder.extract_node(
881+
"""
882+
from typing import TypedDict
883+
884+
885+
class Apple(TypedDict):
886+
a: int
887+
b: str
888+
889+
890+
Pear = TypedDict('OtherTypedDict', {'a': int, 'b': str})
891+
892+
893+
Apple.__annotations__ #@
894+
Pear.__annotations__ #@
895+
"""
896+
)
897+
898+
assert isinstance(apple, nodes.Attribute)
899+
assert next(apple.infer()) is astroid.Uninferable
900+
assert isinstance(pear, nodes.Attribute)
901+
assert next(pear.infer()) is astroid.Uninferable

tests/test_scoped_nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,7 @@ def registered(cls, application):
12091209
astroid = builder.parse(data, __name__)
12101210
cls = astroid["WebAppObject"]
12111211
assert_keys = [
1212+
"__annotations__",
12121213
"__module__",
12131214
"__qualname__",
12141215
"appli",

0 commit comments

Comments
 (0)