66
77from collections .abc import Iterator
88
9- from astroid import bases , context , nodes
9+ from astroid import bases , context , nodes , util
1010from astroid .builder import _extract_single_node
1111from astroid .const import PY313
1212from astroid .exceptions import InferenceError , UseInferenceDefault
@@ -43,12 +43,26 @@ def infer_parents_subscript(
4343 if isinstance (subscript_node .slice , nodes .Const ):
4444 path_cls = next (_extract_single_node (PATH_TEMPLATE ).infer ())
4545 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 ])
4652
4753 raise UseInferenceDefault
4854
4955
5056def _looks_like_parents_name (node : nodes .Name ) -> bool :
5157 """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+
5266 # Look for the assignment in the current scope
5367 try :
5468 frame , stmts = node .lookup (node .name )
@@ -98,6 +112,55 @@ def infer_parents_name(
98112class _PathParents:
99113 def __getitem__(self, key):
100114 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
101164 return Path()
102165""" )
103166 return iter ([parents_cls .instantiate_class ()])
@@ -114,3 +177,8 @@ def register(manager: AstroidManager) -> None:
114177 inference_tip (infer_parents_name ),
115178 _looks_like_parents_name ,
116179 )
180+ manager .register_transform (
181+ nodes .Attribute ,
182+ inference_tip (infer_parents_attribute ),
183+ _looks_like_parents_attribute ,
184+ )
0 commit comments