Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/brain/brain_attrs.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

42 statements  

1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 

2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE 

3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt 

4 

5""" 

6Astroid hook for the attrs library 

7 

8Without this hook pylint reports unsupported-assignment-operation 

9for attrs classes 

10""" 

11 

12from astroid import nodes 

13from astroid.brain.helpers import is_class_var 

14from astroid.manager import AstroidManager 

15from astroid.util import safe_infer 

16 

17ATTRIB_NAMES = frozenset( 

18 ( 

19 "attr.Factory", 

20 "attr.ib", 

21 "attrib", 

22 "attr.attrib", 

23 "attr.field", 

24 "attrs.field", 

25 "field", 

26 ) 

27) 

28NEW_ATTRS_NAMES = frozenset( 

29 ( 

30 "attrs.define", 

31 "attrs.mutable", 

32 "attrs.frozen", 

33 ) 

34) 

35ATTRS_NAMES = frozenset( 

36 ( 

37 "attr.s", 

38 "attrs", 

39 "attr.attrs", 

40 "attr.attributes", 

41 "attr.define", 

42 "attr.mutable", 

43 "attr.frozen", 

44 *NEW_ATTRS_NAMES, 

45 ) 

46) 

47 

48 

49def is_decorated_with_attrs(node, decorator_names=ATTRS_NAMES) -> bool: 

50 """Return whether a decorated node has an attr decorator applied.""" 

51 if not node.decorators: 

52 return False 

53 for decorator_attribute in node.decorators.nodes: 

54 if isinstance(decorator_attribute, nodes.Call): # decorator with arguments 

55 decorator_attribute = decorator_attribute.func 

56 if decorator_attribute.as_string() in decorator_names: 

57 return True 

58 

59 inferred = safe_infer(decorator_attribute) 

60 if inferred and inferred.root().name == "attr._next_gen": 

61 return True 

62 return False 

63 

64 

65def attr_attributes_transform(node: nodes.ClassDef) -> None: 

66 """Given that the ClassNode has an attr decorator, 

67 rewrite class attributes as instance attributes 

68 """ 

69 # Astroid can't infer this attribute properly 

70 # Prevents https://github.com/pylint-dev/pylint/issues/1884 

71 node.locals["__attrs_attrs__"] = [nodes.Unknown(parent=node)] 

72 

73 use_bare_annotations = is_decorated_with_attrs(node, NEW_ATTRS_NAMES) 

74 for cdef_body_node in node.body: 

75 if not isinstance(cdef_body_node, (nodes.Assign, nodes.AnnAssign)): 

76 continue 

77 if isinstance(cdef_body_node.value, nodes.Call): 

78 if cdef_body_node.value.func.as_string() not in ATTRIB_NAMES: 

79 continue 

80 elif not use_bare_annotations: 

81 continue 

82 

83 # Skip attributes that are explicitly annotated as class variables 

84 if isinstance(cdef_body_node, nodes.AnnAssign) and is_class_var( 

85 cdef_body_node.annotation 

86 ): 

87 continue 

88 

89 targets = ( 

90 cdef_body_node.targets 

91 if hasattr(cdef_body_node, "targets") 

92 else [cdef_body_node.target] 

93 ) 

94 for target in targets: 

95 rhs_node = nodes.Unknown( 

96 lineno=cdef_body_node.lineno, 

97 col_offset=cdef_body_node.col_offset, 

98 parent=cdef_body_node, 

99 ) 

100 if isinstance(target, nodes.AssignName): 

101 # Could be a subscript if the code analysed is 

102 # i = Optional[str] = "" 

103 # See https://github.com/pylint-dev/pylint/issues/4439 

104 node.locals[target.name] = [rhs_node] 

105 node.instance_attrs[target.name] = [rhs_node] 

106 

107 

108def register(manager: AstroidManager) -> None: 

109 manager.register_transform( 

110 nodes.ClassDef, attr_attributes_transform, is_decorated_with_attrs 

111 )