66
77from collections .abc import Iterator
88
9- from astroid import bases , context , nodes , util
9+ from astroid import bases , context , nodes
1010from astroid .builder import _extract_single_node
1111from astroid .const import PY313
1212from astroid .exceptions import InferenceError , UseInferenceDefault
2121
2222def _looks_like_parents_subscript (node : nodes .Subscript ) -> bool :
2323 if not (
24- isinstance (node .value , nodes .Attribute ) and node .value .attrname == "parents"
24+ (isinstance (node .value , nodes .Attribute ) and node .value .attrname == "parents" )
25+ or isinstance (node .value , nodes .Name )
2526 ):
2627 return False
2728
@@ -43,142 +44,13 @@ def infer_parents_subscript(
4344 if isinstance (subscript_node .slice , nodes .Const ):
4445 path_cls = next (_extract_single_node (PATH_TEMPLATE ).infer ())
4546 return iter ([path_cls .instantiate_class ()])
46- elif isinstance (subscript_node .slice , nodes .Slice ):
47- # For slices, return a tuple
48- parents_tuple = nodes .Tuple ()
49- path_cls = next (_extract_single_node (PATH_TEMPLATE ).infer ())
50- parents_tuple .elts = [path_cls .instantiate_class () for _ in range (2 )] # Mock some parents
51- return iter ([parents_tuple ])
5247
5348 raise UseInferenceDefault
5449
5550
56- def _looks_like_parents_name (node : nodes .Name ) -> bool :
57- """Check if a Name node was assigned from a Path.parents attribute."""
58- # Only apply to direct Name nodes, not to Name nodes in subscripts
59- if isinstance (node .parent , nodes .Subscript ):
60- return False
61-
62- # Only apply to Name nodes that are direct expressions, not in subscripts
63- if not isinstance (node .parent , nodes .Expr ):
64- return False
65-
66- # Look for the assignment in the current scope
67- try :
68- frame , stmts = node .lookup (node .name )
69- if not stmts :
70- return False
71-
72- # Check each assignment statement
73- for stmt in stmts :
74- if isinstance (stmt , nodes .AssignName ):
75- # Get the parent Assign node
76- assign_node = stmt .parent
77- if isinstance (assign_node , nodes .Assign ):
78- # Check if the value is an Attribute access to .parents
79- if (isinstance (assign_node .value , nodes .Attribute )
80- and assign_node .value .attrname == "parents" ):
81- try :
82- # Check if the attribute is from a Path object
83- value = next (assign_node .value .expr .infer ())
84- if (isinstance (value , bases .Instance )
85- and isinstance (value ._proxied , nodes .ClassDef )
86- and value .qname () in ("pathlib.Path" , "pathlib._local.Path" )):
87- return True
88- except (InferenceError , StopIteration ):
89- pass
90- except (InferenceError , StopIteration ):
91- pass
92- return False
93-
94-
95- def infer_parents_name (
96- name_node : nodes .Name , ctx : context .InferenceContext | None = None
97- ) -> Iterator [bases .Instance ]:
98- """Infer a Name node that was assigned from Path.parents."""
99- if PY313 :
100- # For Python 3.13+, parents is a tuple
101- from astroid import nodes
102- # Create a tuple that behaves like Path.parents
103- parents_tuple = nodes .Tuple ()
104- # Add some mock Path elements to make indexing work
105- path_cls = next (_extract_single_node (PATH_TEMPLATE ).infer ())
106- parents_tuple .elts = [path_cls .instantiate_class () for _ in range (3 )] # Mock some parents
107- return iter ([parents_tuple ])
108- else :
109- # For older versions, it's a _PathParents object
110- # We need to create a mock _PathParents instance that behaves correctly
111- parents_cls = _extract_single_node ("""
112- class _PathParents:
113- def __getitem__(self, key):
114- from pathlib import Path
115- if isinstance(key, slice):
116- # Return a tuple for slices
117- return (Path(), Path())
118- # For indexing, return a Path object
119- return Path()
120- """ )
121- return iter ([parents_cls .instantiate_class ()])
122-
123-
124- def _looks_like_parents_attribute (node : nodes .Attribute ) -> bool :
125- """Check if an Attribute node is accessing Path.parents."""
126- if node .attrname != "parents" :
127- return False
128-
129- # Check if the expression is a Path object
130- try :
131- expr_inferred = list (node .expr .infer ())
132- if expr_inferred and not isinstance (expr_inferred [0 ], util .UninferableBase ):
133- expr_value = expr_inferred [0 ]
134- if (isinstance (expr_value , bases .Instance )
135- and isinstance (expr_value ._proxied , nodes .ClassDef )
136- and expr_value .qname () in ("pathlib.Path" , "pathlib._local.Path" )):
137- return True
138- except (InferenceError , StopIteration ):
139- pass
140-
141- return False
142-
143-
144- def infer_parents_attribute (
145- attr_node : nodes .Attribute , ctx : context .InferenceContext | None = None
146- ) -> Iterator [bases .Instance ]:
147- """Infer Path.parents attribute access."""
148- if PY313 :
149- # For Python 3.13+, parents is a tuple
150- parents_tuple = nodes .Tuple ()
151- path_cls = next (_extract_single_node (PATH_TEMPLATE ).infer ())
152- parents_tuple .elts = [path_cls .instantiate_class () for _ in range (3 )] # Mock some parents
153- return iter ([parents_tuple ])
154- else :
155- # For older versions, it's a _PathParents object
156- parents_cls = _extract_single_node ("""
157- class _PathParents:
158- def __getitem__(self, key):
159- from pathlib import Path
160- if isinstance(key, slice):
161- # Return a tuple for slices
162- return (Path(), Path())
163- # For indexing, return a Path object
164- return Path()
165- """ )
166- return iter ([parents_cls .instantiate_class ()])
167-
168-
16951def register (manager : AstroidManager ) -> None :
17052 manager .register_transform (
17153 nodes .Subscript ,
17254 inference_tip (infer_parents_subscript ),
17355 _looks_like_parents_subscript ,
17456 )
175- manager .register_transform (
176- nodes .Name ,
177- inference_tip (infer_parents_name ),
178- _looks_like_parents_name ,
179- )
180- manager .register_transform (
181- nodes .Attribute ,
182- inference_tip (infer_parents_attribute ),
183- _looks_like_parents_attribute ,
184- )
0 commit comments