2929import posixpath
3030import sys
3131from collections import defaultdict
32- from typing import Dict , List , Optional , Tuple , Union
32+ from typing import Dict , Generator , Iterable , List , Optional , Tuple , Union
3333
3434
3535class _RepositoryMapping :
@@ -143,25 +143,25 @@ def is_empty(self) -> bool:
143143 )
144144
145145
146- class Path (pathlib .PurePath ):
146+ class Path (pathlib .Path ):
147147 """A pathlib-like path object for runfiles.
148148
149- This class extends `pathlib.PurePath ` and resolves paths
149+ This class extends `pathlib.Path ` and resolves paths
150150 using the associated `Runfiles` instance when converted to a string.
151151 """
152152
153- # For Python < 3.12 compatibility when subclassing PurePath directly
154- _flavour = getattr (type (pathlib .PurePath ()), "_flavour" , None )
153+ # For Python < 3.12 compatibility when subclassing Path directly
154+ _flavour = getattr (type (pathlib .Path ()), "_flavour" , None )
155155
156156 def __new__ (
157157 cls ,
158158 * args : Union [str , os .PathLike ],
159159 runfiles : Optional ["Runfiles" ] = None ,
160160 source_repo : Optional [str ] = None ,
161- ) -> "Path " :
161+ ) -> "Self " :
162162 """Private constructor. Use Runfiles.root() to create instances."""
163163 obj = super ().__new__ (cls , * args )
164- # Type checkers might complain about adding attributes to PurePath ,
164+ # Type checkers might complain about adding attributes to Path ,
165165 # but this is standard for pathlib subclasses.
166166 obj ._runfiles = runfiles # type: ignore
167167 obj ._source_repo = source_repo # type: ignore
@@ -173,90 +173,205 @@ def __init__(
173173 runfiles : Optional ["Runfiles" ] = None ,
174174 source_repo : Optional [str ] = None ,
175175 ) -> None :
176- pass
176+ # In Python 3.12+, pathlib was refactored and Path.__init__ now accepts
177+ # *args. Prior to 3.12, Path did not define __init__, so
178+ # super().__init__(*args) would fall through to object.__init__, which
179+ # raises a TypeError because it takes no arguments.
180+ if sys .version_info >= (3 , 12 ):
181+ super ().__init__ (* args )
182+ else :
183+ super ().__init__ ()
184+
185+ # We override resolve() and absolute() to ensure that in Python < 3.12,
186+ # where pathlib internally uses object.__new__ instead of our custom
187+ # __new__ or with_segments(), the runfiles state is preserved. We delegate
188+ # to self._as_path() because super().resolve() creates intermediate objects
189+ # that would otherwise crash during internal stat() calls.
190+ # override
191+ def resolve (self , strict : bool = False ) -> "Self" :
192+ return type (self )(
193+ self ._as_path ().resolve (strict = strict ),
194+ runfiles = self ._runfiles ,
195+ source_repo = self ._source_repo ,
196+ )
197+
198+ # override
199+ def absolute (self ) -> "Self" :
200+ return type (self )(
201+ self ._as_path ().absolute (),
202+ runfiles = self ._runfiles ,
203+ source_repo = self ._source_repo ,
204+ )
177205
178- def with_segments (self , * pathsegments : Union [str , os .PathLike ]) -> "Path" :
206+ # override
207+ def with_segments (self , * pathsegments : Union [str , os .PathLike ]) -> "Self" :
179208 """Used by Python 3.12+ pathlib to create new path objects."""
180209 return type (self )(
181210 * pathsegments ,
182- runfiles = self ._runfiles , # type: ignore
183- source_repo = self ._source_repo , # type: ignore
211+ runfiles = self ._runfiles ,
212+ source_repo = self ._source_repo ,
184213 )
185214
186215 # For Python < 3.12
187- @classmethod
188- def _from_parts (cls , args : Tuple [str , ...]) -> "Path" :
189- obj = super ()._from_parts (args ) # type: ignore
190- # These will be set by the calling instance later, or we can't set them here
191- # properly without context. Usually pathlib calls this from an instance
192- # method like _make_child, which we also might need to override.
193- return obj
194-
195- def _make_child (self , args : Tuple [str , ...]) -> "Path" :
216+ # override
217+ def _make_child (self , args : Tuple [str , ...]) -> "Self" :
196218 obj = super ()._make_child (args ) # type: ignore
197219 obj ._runfiles = self ._runfiles # type: ignore
198220 obj ._source_repo = self ._source_repo # type: ignore
199221 return obj
200222
201- @classmethod
202- def _from_parsed_parts (cls , drv : str , root : str , parts : List [str ]) -> "Path" :
203- obj = super ()._from_parsed_parts (drv , root , parts ) # type: ignore
204- return obj
205-
206- def _make_child_relpath (self , part : str ) -> "Path" :
207- obj = super ()._make_child_relpath (part ) # type: ignore
208- obj ._runfiles = self ._runfiles # type: ignore
209- obj ._source_repo = self ._source_repo # type: ignore
210- return obj
211-
223+ # override
212224 @property
213- def parents (self ) -> Tuple ["Path " , ...]:
225+ def parents (self ) -> Tuple ["Self " , ...]:
214226 return tuple (
215227 type (self )(
216228 p ,
217- runfiles = getattr ( self , " _runfiles" , None ) ,
218- source_repo = getattr ( self , " _source_repo" , None ) ,
229+ runfiles = self . _runfiles ,
230+ source_repo = self . _source_repo ,
219231 )
220232 for p in super ().parents
221233 )
222234
235+ # override
223236 @property
224- def parent (self ) -> "Path " :
237+ def parent (self ) -> "Self " :
225238 return type (self )(
226239 super ().parent ,
227- runfiles = getattr ( self , " _runfiles" , None ) ,
228- source_repo = getattr ( self , " _source_repo" , None ) ,
240+ runfiles = self . _runfiles ,
241+ source_repo = self . _source_repo ,
229242 )
230243
231- def with_name (self , name : str ) -> "Path" :
244+ @property
245+ def runfile_path (self ) -> str :
246+ """Returns the runfiles-root relative path."""
247+ path_posix = super ().__str__ ().replace ("\\ " , "/" )
248+ if path_posix == "." :
249+ return ""
250+ return path_posix
251+
252+ # override
253+ def with_name (self , name : str ) -> "Self" :
232254 return type (self )(
233255 super ().with_name (name ),
234- runfiles = getattr ( self , " _runfiles" , None ) ,
235- source_repo = getattr ( self , " _source_repo" , None ) ,
256+ runfiles = self . _runfiles ,
257+ source_repo = self . _source_repo ,
236258 )
237259
238- def with_suffix (self , suffix : str ) -> "Path" :
260+ # override
261+ def with_suffix (self , suffix : str ) -> "Self" :
239262 return type (self )(
240263 super ().with_suffix (suffix ),
241- runfiles = getattr ( self , " _runfiles" , None ) ,
242- source_repo = getattr ( self , " _source_repo" , None ) ,
264+ runfiles = self . _runfiles ,
265+ source_repo = self . _source_repo ,
243266 )
244267
268+ def _as_path (self ) -> pathlib .Path :
269+ return pathlib .Path (str (self ))
270+
271+ # override
272+ def stat (self , * , follow_symlinks : bool = True ) -> os .stat_result :
273+ return self ._as_path ().stat (follow_symlinks = follow_symlinks )
274+
275+ # override
276+ def lstat (self ) -> os .stat_result :
277+ return self ._as_path ().lstat ()
278+
279+ # override
280+ def exists (self ) -> bool :
281+ return self ._as_path ().exists ()
282+
283+ # override
284+ def is_dir (self ) -> bool :
285+ return self ._as_path ().is_dir ()
286+
287+ # override
288+ def is_file (self ) -> bool :
289+ return self ._as_path ().is_file ()
290+
291+ # override
292+ def is_symlink (self ) -> bool :
293+ return self ._as_path ().is_symlink ()
294+
295+ # override
296+ def is_block_device (self ) -> bool :
297+ return self ._as_path ().is_block_device ()
298+
299+ # override
300+ def is_char_device (self ) -> bool :
301+ return self ._as_path ().is_char_device ()
302+
303+ # override
304+ def is_fifo (self ) -> bool :
305+ return self ._as_path ().is_fifo ()
306+
307+ # override
308+ def is_socket (self ) -> bool :
309+ return self ._as_path ().is_socket ()
310+
311+ # override
312+ def open (
313+ self ,
314+ mode : str = "r" ,
315+ buffering : int = - 1 ,
316+ encoding : Optional [str ] = None ,
317+ errors : Optional [str ] = None ,
318+ newline : Optional [str ] = None ,
319+ ):
320+ return self ._as_path ().open (
321+ mode = mode ,
322+ buffering = buffering ,
323+ encoding = encoding ,
324+ errors = errors ,
325+ newline = newline ,
326+ )
327+
328+ # override
329+ def read_bytes (self ) -> bytes :
330+ return self ._as_path ().read_bytes ()
331+
332+ # override
333+ def read_text (
334+ self , encoding : Optional [str ] = None , errors : Optional [str ] = None
335+ ) -> str :
336+ return self ._as_path ().read_text (encoding = encoding , errors = errors )
337+
338+ # override
339+ def iterdir (self ) -> Generator ["Self" , None , None ]:
340+ resolved = self ._as_path ()
341+ for p in resolved .iterdir ():
342+ yield self / p .name
343+
344+ # override
345+ def glob (self , pattern : str ) -> Generator ["Self" , None , None ]:
346+ resolved = self ._as_path ()
347+ for p in resolved .glob (pattern ):
348+ yield self / p .relative_to (resolved )
349+
350+ # override
351+ def rglob (self , pattern : str ) -> Generator ["Self" , None , None ]:
352+ resolved = self ._as_path ()
353+ for p in resolved .rglob (pattern ):
354+ yield self / p .relative_to (resolved )
355+
245356 def __repr__ (self ) -> str :
246- return 'runfiles.Path({!r})' .format (super (). __str__ () )
357+ return 'runfiles.Path({!r})' .format (self . runfile_path )
247358
248359 def __str__ (self ) -> str :
249360 path_posix = super ().__str__ ().replace ("\\ " , "/" )
250361 if not path_posix or path_posix == "." :
251362 # pylint: disable=protected-access
252363 return self ._runfiles ._python_runfiles_root # type: ignore
253364 resolved = self ._runfiles .Rlocation (path_posix , source_repo = self ._source_repo ) # type: ignore
254- return resolved if resolved is not None else super ().__str__ ()
365+ if resolved is not None :
366+ return resolved
367+
368+ # pylint: disable=protected-access
369+ return posixpath .join (self ._runfiles ._python_runfiles_root , path_posix ) # type: ignore
255370
256371 def __fspath__ (self ) -> str :
257372 return str (self )
258373
259- def runfiles_root (self ) -> "Path " :
374+ def runfiles_root (self ) -> "Self " :
260375 """Returns a Path object representing the runfiles root."""
261376 return self ._runfiles .root (source_repo = self ._source_repo ) # type: ignore
262377
0 commit comments