1""" 2Abstract base classes for rich path objects. 3 4This module is published as a PyPI package called "pathlib-abc". 5 6This module is also a *PRIVATE* part of the Python standard library, where 7it's developed alongside pathlib. If it finds success and maturity as a PyPI 8package, it could become a public part of the standard library. 9 10Two base classes are defined here -- PurePathBase and PathBase -- that 11resemble pathlib's PurePath and Path respectively. 12""" 13 14import functools 15from glob import _Globber, _no_recurse_symlinks 16from errno import ENOENT, ENOTDIR, EBADF, ELOOP, EINVAL 17from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO 18 19 20__all__ = ["UnsupportedOperation"] 21 22# 23# Internals 24# 25 26_WINERROR_NOT_READY = 21 # drive exists but is not accessible 27_WINERROR_INVALID_NAME = 123 # fix for bpo-35306 28_WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself 29 30# EBADF - guard against macOS `stat` throwing EBADF 31_IGNORED_ERRNOS = (ENOENT, ENOTDIR, EBADF, ELOOP) 32 33_IGNORED_WINERRORS = ( 34 _WINERROR_NOT_READY, 35 _WINERROR_INVALID_NAME, 36 _WINERROR_CANT_RESOLVE_FILENAME) 37 38def _ignore_error(exception): 39 return (getattr(exception, 'errno', None) in _IGNORED_ERRNOS or 40 getattr(exception, 'winerror', None) in _IGNORED_WINERRORS) 41 42 43@functools.cache 44def _is_case_sensitive(parser): 45 return parser.normcase('Aa') == 'Aa' 46 47 48class UnsupportedOperation(NotImplementedError): 49 """An exception that is raised when an unsupported operation is called on 50 a path object. 51 """ 52 pass 53 54 55class ParserBase: 56 """Base class for path parsers, which do low-level path manipulation. 57 58 Path parsers provide a subset of the os.path API, specifically those 59 functions needed to provide PurePathBase functionality. Each PurePathBase 60 subclass references its path parser via a 'parser' class attribute. 61 62 Every method in this base class raises an UnsupportedOperation exception. 63 """ 64 65 @classmethod 66 def _unsupported_msg(cls, attribute): 67 return f"{cls.__name__}.{attribute} is unsupported" 68 69 @property 70 def sep(self): 71 """The character used to separate path components.""" 72 raise UnsupportedOperation(self._unsupported_msg('sep')) 73 74 def join(self, path, *paths): 75 """Join path segments.""" 76 raise UnsupportedOperation(self._unsupported_msg('join()')) 77 78 def split(self, path): 79 """Split the path into a pair (head, tail), where *head* is everything 80 before the final path separator, and *tail* is everything after. 81 Either part may be empty. 82 """ 83 raise UnsupportedOperation(self._unsupported_msg('split()')) 84 85 def splitdrive(self, path): 86 """Split the path into a 2-item tuple (drive, tail), where *drive* is 87 a device name or mount point, and *tail* is everything after the 88 drive. Either part may be empty.""" 89 raise UnsupportedOperation(self._unsupported_msg('splitdrive()')) 90 91 def normcase(self, path): 92 """Normalize the case of the path.""" 93 raise UnsupportedOperation(self._unsupported_msg('normcase()')) 94 95 def isabs(self, path): 96 """Returns whether the path is absolute, i.e. unaffected by the 97 current directory or drive.""" 98 raise UnsupportedOperation(self._unsupported_msg('isabs()')) 99 100 101class PurePathBase: 102 """Base class for pure path objects. 103 104 This class *does not* provide several magic methods that are defined in 105 its subclass PurePath. They are: __fspath__, __bytes__, __reduce__, 106 __hash__, __eq__, __lt__, __le__, __gt__, __ge__. Its initializer and path 107 joining methods accept only strings, not os.PathLike objects more broadly. 108 """ 109 110 __slots__ = ( 111 # The `_raw_path` slot store a joined string path. This is set in the 112 # `__init__()` method. 113 '_raw_path', 114 115 # The '_resolving' slot stores a boolean indicating whether the path 116 # is being processed by `PathBase.resolve()`. This prevents duplicate 117 # work from occurring when `resolve()` calls `stat()` or `readlink()`. 118 '_resolving', 119 ) 120 parser = ParserBase() 121 _globber = _Globber 122 123 def __init__(self, path, *paths): 124 self._raw_path = self.parser.join(path, *paths) if paths else path 125 if not isinstance(self._raw_path, str): 126 raise TypeError( 127 f"path should be a str, not {type(self._raw_path).__name__!r}") 128 self._resolving = False 129 130 def with_segments(self, *pathsegments): 131 """Construct a new path object from any number of path-like objects. 132 Subclasses may override this method to customize how new path objects 133 are created from methods like `iterdir()`. 134 """ 135 return type(self)(*pathsegments) 136 137 def __str__(self): 138 """Return the string representation of the path, suitable for 139 passing to system calls.""" 140 return self._raw_path 141 142 def as_posix(self): 143 """Return the string representation of the path with forward (/) 144 slashes.""" 145 return str(self).replace(self.parser.sep, '/') 146 147 @property 148 def drive(self): 149 """The drive prefix (letter or UNC path), if any.""" 150 return self.parser.splitdrive(self.anchor)[0] 151 152 @property 153 def root(self): 154 """The root of the path, if any.""" 155 return self.parser.splitdrive(self.anchor)[1] 156 157 @property 158 def anchor(self): 159 """The concatenation of the drive and root, or ''.""" 160 return self._stack[0] 161 162 @property 163 def name(self): 164 """The final path component, if any.""" 165 return self.parser.split(self._raw_path)[1] 166 167 @property 168 def suffix(self): 169 """ 170 The final component's last suffix, if any. 171 172 This includes the leading period. For example: '.txt' 173 """ 174 name = self.name 175 i = name.rfind('.') 176 if 0 < i < len(name) - 1: 177 return name[i:] 178 else: 179 return '' 180 181 @property 182 def suffixes(self): 183 """ 184 A list of the final component's suffixes, if any. 185 186 These include the leading periods. For example: ['.tar', '.gz'] 187 """ 188 name = self.name 189 if name.endswith('.'): 190 return [] 191 name = name.lstrip('.') 192 return ['.' + suffix for suffix in name.split('.')[1:]] 193 194 @property 195 def stem(self): 196 """The final path component, minus its last suffix.""" 197 name = self.name 198 i = name.rfind('.') 199 if 0 < i < len(name) - 1: 200 return name[:i] 201 else: 202 return name 203 204 def with_name(self, name): 205 """Return a new path with the file name changed.""" 206 split = self.parser.split 207 if split(name)[0]: 208 raise ValueError(f"Invalid name {name!r}") 209 return self.with_segments(split(self._raw_path)[0], name) 210 211 def with_stem(self, stem): 212 """Return a new path with the stem changed.""" 213 suffix = self.suffix 214 if not suffix: 215 return self.with_name(stem) 216 elif not stem: 217 # If the suffix is non-empty, we can't make the stem empty. 218 raise ValueError(f"{self!r} has a non-empty suffix") 219 else: 220 return self.with_name(stem + suffix) 221 222 def with_suffix(self, suffix): 223 """Return a new path with the file suffix changed. If the path 224 has no suffix, add given suffix. If the given suffix is an empty 225 string, remove the suffix from the path. 226 """ 227 stem = self.stem 228 if not stem: 229 # If the stem is empty, we can't make the suffix non-empty. 230 raise ValueError(f"{self!r} has an empty name") 231 elif suffix and not (suffix.startswith('.') and len(suffix) > 1): 232 raise ValueError(f"Invalid suffix {suffix!r}") 233 else: 234 return self.with_name(stem + suffix) 235 236 def relative_to(self, other, *, walk_up=False): 237 """Return the relative path to another path identified by the passed 238 arguments. If the operation is not possible (because this is not 239 related to the other path), raise ValueError. 240 241 The *walk_up* parameter controls whether `..` may be used to resolve 242 the path. 243 """ 244 if not isinstance(other, PurePathBase): 245 other = self.with_segments(other) 246 anchor0, parts0 = self._stack 247 anchor1, parts1 = other._stack 248 if anchor0 != anchor1: 249 raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors") 250 while parts0 and parts1 and parts0[-1] == parts1[-1]: 251 parts0.pop() 252 parts1.pop() 253 for part in parts1: 254 if not part or part == '.': 255 pass 256 elif not walk_up: 257 raise ValueError(f"{self._raw_path!r} is not in the subpath of {other._raw_path!r}") 258 elif part == '..': 259 raise ValueError(f"'..' segment in {other._raw_path!r} cannot be walked") 260 else: 261 parts0.append('..') 262 return self.with_segments('', *reversed(parts0)) 263 264 def is_relative_to(self, other): 265 """Return True if the path is relative to another path or False. 266 """ 267 if not isinstance(other, PurePathBase): 268 other = self.with_segments(other) 269 anchor0, parts0 = self._stack 270 anchor1, parts1 = other._stack 271 if anchor0 != anchor1: 272 return False 273 while parts0 and parts1 and parts0[-1] == parts1[-1]: 274 parts0.pop() 275 parts1.pop() 276 for part in parts1: 277 if part and part != '.': 278 return False 279 return True 280 281 @property 282 def parts(self): 283 """An object providing sequence-like access to the 284 components in the filesystem path.""" 285 anchor, parts = self._stack 286 if anchor: 287 parts.append(anchor) 288 return tuple(reversed(parts)) 289 290 def joinpath(self, *pathsegments): 291 """Combine this path with one or several arguments, and return a 292 new path representing either a subpath (if all arguments are relative 293 paths) or a totally different path (if one of the arguments is 294 anchored). 295 """ 296 return self.with_segments(self._raw_path, *pathsegments) 297 298 def __truediv__(self, key): 299 try: 300 return self.with_segments(self._raw_path, key) 301 except TypeError: 302 return NotImplemented 303 304 def __rtruediv__(self, key): 305 try: 306 return self.with_segments(key, self._raw_path) 307 except TypeError: 308 return NotImplemented 309 310 @property 311 def _stack(self): 312 """ 313 Split the path into a 2-tuple (anchor, parts), where *anchor* is the 314 uppermost parent of the path (equivalent to path.parents[-1]), and 315 *parts* is a reversed list of parts following the anchor. 316 """ 317 split = self.parser.split 318 path = self._raw_path 319 parent, name = split(path) 320 names = [] 321 while path != parent: 322 names.append(name) 323 path = parent 324 parent, name = split(path) 325 return path, names 326 327 @property 328 def parent(self): 329 """The logical parent of the path.""" 330 path = self._raw_path 331 parent = self.parser.split(path)[0] 332 if path != parent: 333 parent = self.with_segments(parent) 334 parent._resolving = self._resolving 335 return parent 336 return self 337 338 @property 339 def parents(self): 340 """A sequence of this path's logical parents.""" 341 split = self.parser.split 342 path = self._raw_path 343 parent = split(path)[0] 344 parents = [] 345 while path != parent: 346 parents.append(self.with_segments(parent)) 347 path = parent 348 parent = split(path)[0] 349 return tuple(parents) 350 351 def is_absolute(self): 352 """True if the path is absolute (has both a root and, if applicable, 353 a drive).""" 354 return self.parser.isabs(self._raw_path) 355 356 @property 357 def _pattern_str(self): 358 """The path expressed as a string, for use in pattern-matching.""" 359 return str(self) 360 361 def match(self, path_pattern, *, case_sensitive=None): 362 """ 363 Return True if this path matches the given pattern. If the pattern is 364 relative, matching is done from the right; otherwise, the entire path 365 is matched. The recursive wildcard '**' is *not* supported by this 366 method. 367 """ 368 if not isinstance(path_pattern, PurePathBase): 369 path_pattern = self.with_segments(path_pattern) 370 if case_sensitive is None: 371 case_sensitive = _is_case_sensitive(self.parser) 372 sep = path_pattern.parser.sep 373 path_parts = self.parts[::-1] 374 pattern_parts = path_pattern.parts[::-1] 375 if not pattern_parts: 376 raise ValueError("empty pattern") 377 if len(path_parts) < len(pattern_parts): 378 return False 379 if len(path_parts) > len(pattern_parts) and path_pattern.anchor: 380 return False 381 globber = self._globber(sep, case_sensitive) 382 for path_part, pattern_part in zip(path_parts, pattern_parts): 383 match = globber.compile(pattern_part) 384 if match(path_part) is None: 385 return False 386 return True 387 388 def full_match(self, pattern, *, case_sensitive=None): 389 """ 390 Return True if this path matches the given glob-style pattern. The 391 pattern is matched against the entire path. 392 """ 393 if not isinstance(pattern, PurePathBase): 394 pattern = self.with_segments(pattern) 395 if case_sensitive is None: 396 case_sensitive = _is_case_sensitive(self.parser) 397 globber = self._globber(pattern.parser.sep, case_sensitive, recursive=True) 398 match = globber.compile(pattern._pattern_str) 399 return match(self._pattern_str) is not None 400 401 402 403class PathBase(PurePathBase): 404 """Base class for concrete path objects. 405 406 This class provides dummy implementations for many methods that derived 407 classes can override selectively; the default implementations raise 408 UnsupportedOperation. The most basic methods, such as stat() and open(), 409 directly raise UnsupportedOperation; these basic methods are called by 410 other methods such as is_dir() and read_text(). 411 412 The Path class derives this class to implement local filesystem paths. 413 Users may derive their own classes to implement virtual filesystem paths, 414 such as paths in archive files or on remote storage systems. 415 """ 416 __slots__ = () 417 418 # Maximum number of symlinks to follow in resolve() 419 _max_symlinks = 40 420 421 @classmethod 422 def _unsupported_msg(cls, attribute): 423 return f"{cls.__name__}.{attribute} is unsupported" 424 425 def stat(self, *, follow_symlinks=True): 426 """ 427 Return the result of the stat() system call on this path, like 428 os.stat() does. 429 """ 430 raise UnsupportedOperation(self._unsupported_msg('stat()')) 431 432 def lstat(self): 433 """ 434 Like stat(), except if the path points to a symlink, the symlink's 435 status information is returned, rather than its target's. 436 """ 437 return self.stat(follow_symlinks=False) 438 439 440 # Convenience functions for querying the stat results 441 442 def exists(self, *, follow_symlinks=True): 443 """ 444 Whether this path exists. 445 446 This method normally follows symlinks; to check whether a symlink exists, 447 add the argument follow_symlinks=False. 448 """ 449 try: 450 self.stat(follow_symlinks=follow_symlinks) 451 except OSError as e: 452 if not _ignore_error(e): 453 raise 454 return False 455 except ValueError: 456 # Non-encodable path 457 return False 458 return True 459 460 def is_dir(self, *, follow_symlinks=True): 461 """ 462 Whether this path is a directory. 463 """ 464 try: 465 return S_ISDIR(self.stat(follow_symlinks=follow_symlinks).st_mode) 466 except OSError as e: 467 if not _ignore_error(e): 468 raise 469 # Path doesn't exist or is a broken symlink 470 # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) 471 return False 472 except ValueError: 473 # Non-encodable path 474 return False 475 476 def is_file(self, *, follow_symlinks=True): 477 """ 478 Whether this path is a regular file (also True for symlinks pointing 479 to regular files). 480 """ 481 try: 482 return S_ISREG(self.stat(follow_symlinks=follow_symlinks).st_mode) 483 except OSError as e: 484 if not _ignore_error(e): 485 raise 486 # Path doesn't exist or is a broken symlink 487 # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) 488 return False 489 except ValueError: 490 # Non-encodable path 491 return False 492 493 def is_mount(self): 494 """ 495 Check if this path is a mount point 496 """ 497 # Need to exist and be a dir 498 if not self.exists() or not self.is_dir(): 499 return False 500 501 try: 502 parent_dev = self.parent.stat().st_dev 503 except OSError: 504 return False 505 506 dev = self.stat().st_dev 507 if dev != parent_dev: 508 return True 509 ino = self.stat().st_ino 510 parent_ino = self.parent.stat().st_ino 511 return ino == parent_ino 512 513 def is_symlink(self): 514 """ 515 Whether this path is a symbolic link. 516 """ 517 try: 518 return S_ISLNK(self.lstat().st_mode) 519 except OSError as e: 520 if not _ignore_error(e): 521 raise 522 # Path doesn't exist 523 return False 524 except ValueError: 525 # Non-encodable path 526 return False 527 528 def is_junction(self): 529 """ 530 Whether this path is a junction. 531 """ 532 # Junctions are a Windows-only feature, not present in POSIX nor the 533 # majority of virtual filesystems. There is no cross-platform idiom 534 # to check for junctions (using stat().st_mode). 535 return False 536 537 def is_block_device(self): 538 """ 539 Whether this path is a block device. 540 """ 541 try: 542 return S_ISBLK(self.stat().st_mode) 543 except OSError as e: 544 if not _ignore_error(e): 545 raise 546 # Path doesn't exist or is a broken symlink 547 # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) 548 return False 549 except ValueError: 550 # Non-encodable path 551 return False 552 553 def is_char_device(self): 554 """ 555 Whether this path is a character device. 556 """ 557 try: 558 return S_ISCHR(self.stat().st_mode) 559 except OSError as e: 560 if not _ignore_error(e): 561 raise 562 # Path doesn't exist or is a broken symlink 563 # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) 564 return False 565 except ValueError: 566 # Non-encodable path 567 return False 568 569 def is_fifo(self): 570 """ 571 Whether this path is a FIFO. 572 """ 573 try: 574 return S_ISFIFO(self.stat().st_mode) 575 except OSError as e: 576 if not _ignore_error(e): 577 raise 578 # Path doesn't exist or is a broken symlink 579 # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) 580 return False 581 except ValueError: 582 # Non-encodable path 583 return False 584 585 def is_socket(self): 586 """ 587 Whether this path is a socket. 588 """ 589 try: 590 return S_ISSOCK(self.stat().st_mode) 591 except OSError as e: 592 if not _ignore_error(e): 593 raise 594 # Path doesn't exist or is a broken symlink 595 # (see http://web.archive.org/web/20200623061726/https://bitbucket.org/pitrou/pathlib/issues/12/ ) 596 return False 597 except ValueError: 598 # Non-encodable path 599 return False 600 601 def samefile(self, other_path): 602 """Return whether other_path is the same or not as this file 603 (as returned by os.path.samefile()). 604 """ 605 st = self.stat() 606 try: 607 other_st = other_path.stat() 608 except AttributeError: 609 other_st = self.with_segments(other_path).stat() 610 return (st.st_ino == other_st.st_ino and 611 st.st_dev == other_st.st_dev) 612 613 def open(self, mode='r', buffering=-1, encoding=None, 614 errors=None, newline=None): 615 """ 616 Open the file pointed to by this path and return a file object, as 617 the built-in open() function does. 618 """ 619 raise UnsupportedOperation(self._unsupported_msg('open()')) 620 621 def read_bytes(self): 622 """ 623 Open the file in bytes mode, read it, and close the file. 624 """ 625 with self.open(mode='rb') as f: 626 return f.read() 627 628 def read_text(self, encoding=None, errors=None, newline=None): 629 """ 630 Open the file in text mode, read it, and close the file. 631 """ 632 with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f: 633 return f.read() 634 635 def write_bytes(self, data): 636 """ 637 Open the file in bytes mode, write to it, and close the file. 638 """ 639 # type-check for the buffer interface before truncating the file 640 view = memoryview(data) 641 with self.open(mode='wb') as f: 642 return f.write(view) 643 644 def write_text(self, data, encoding=None, errors=None, newline=None): 645 """ 646 Open the file in text mode, write to it, and close the file. 647 """ 648 if not isinstance(data, str): 649 raise TypeError('data must be str, not %s' % 650 data.__class__.__name__) 651 with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f: 652 return f.write(data) 653 654 def iterdir(self): 655 """Yield path objects of the directory contents. 656 657 The children are yielded in arbitrary order, and the 658 special entries '.' and '..' are not included. 659 """ 660 raise UnsupportedOperation(self._unsupported_msg('iterdir()')) 661 662 def _glob_selector(self, parts, case_sensitive, recurse_symlinks): 663 if case_sensitive is None: 664 case_sensitive = _is_case_sensitive(self.parser) 665 case_pedantic = False 666 else: 667 # The user has expressed a case sensitivity choice, but we don't 668 # know the case sensitivity of the underlying filesystem, so we 669 # must use scandir() for everything, including non-wildcard parts. 670 case_pedantic = True 671 recursive = True if recurse_symlinks else _no_recurse_symlinks 672 globber = self._globber(self.parser.sep, case_sensitive, case_pedantic, recursive) 673 return globber.selector(parts) 674 675 def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): 676 """Iterate over this subtree and yield all existing files (of any 677 kind, including directories) matching the given relative pattern. 678 """ 679 if not isinstance(pattern, PurePathBase): 680 pattern = self.with_segments(pattern) 681 anchor, parts = pattern._stack 682 if anchor: 683 raise NotImplementedError("Non-relative patterns are unsupported") 684 select = self._glob_selector(parts, case_sensitive, recurse_symlinks) 685 return select(self) 686 687 def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): 688 """Recursively yield all existing files (of any kind, including 689 directories) matching the given relative pattern, anywhere in 690 this subtree. 691 """ 692 if not isinstance(pattern, PurePathBase): 693 pattern = self.with_segments(pattern) 694 pattern = '**' / pattern 695 return self.glob(pattern, case_sensitive=case_sensitive, recurse_symlinks=recurse_symlinks) 696 697 def walk(self, top_down=True, on_error=None, follow_symlinks=False): 698 """Walk the directory tree from this directory, similar to os.walk().""" 699 paths = [self] 700 while paths: 701 path = paths.pop() 702 if isinstance(path, tuple): 703 yield path 704 continue 705 dirnames = [] 706 filenames = [] 707 if not top_down: 708 paths.append((path, dirnames, filenames)) 709 try: 710 for child in path.iterdir(): 711 try: 712 if child.is_dir(follow_symlinks=follow_symlinks): 713 if not top_down: 714 paths.append(child) 715 dirnames.append(child.name) 716 else: 717 filenames.append(child.name) 718 except OSError: 719 filenames.append(child.name) 720 except OSError as error: 721 if on_error is not None: 722 on_error(error) 723 if not top_down: 724 while not isinstance(paths.pop(), tuple): 725 pass 726 continue 727 if top_down: 728 yield path, dirnames, filenames 729 paths += [path.joinpath(d) for d in reversed(dirnames)] 730 731 def absolute(self): 732 """Return an absolute version of this path 733 No normalization or symlink resolution is performed. 734 735 Use resolve() to resolve symlinks and remove '..' segments. 736 """ 737 raise UnsupportedOperation(self._unsupported_msg('absolute()')) 738 739 @classmethod 740 def cwd(cls): 741 """Return a new path pointing to the current working directory.""" 742 # We call 'absolute()' rather than using 'os.getcwd()' directly to 743 # enable users to replace the implementation of 'absolute()' in a 744 # subclass and benefit from the new behaviour here. This works because 745 # os.path.abspath('.') == os.getcwd(). 746 return cls('').absolute() 747 748 def expanduser(self): 749 """ Return a new path with expanded ~ and ~user constructs 750 (as returned by os.path.expanduser) 751 """ 752 raise UnsupportedOperation(self._unsupported_msg('expanduser()')) 753 754 @classmethod 755 def home(cls): 756 """Return a new path pointing to expanduser('~'). 757 """ 758 return cls("~").expanduser() 759 760 def readlink(self): 761 """ 762 Return the path to which the symbolic link points. 763 """ 764 raise UnsupportedOperation(self._unsupported_msg('readlink()')) 765 readlink._supported = False 766 767 def resolve(self, strict=False): 768 """ 769 Make the path absolute, resolving all symlinks on the way and also 770 normalizing it. 771 """ 772 if self._resolving: 773 return self 774 path_root, parts = self._stack 775 path = self.with_segments(path_root) 776 try: 777 path = path.absolute() 778 except UnsupportedOperation: 779 path_tail = [] 780 else: 781 path_root, path_tail = path._stack 782 path_tail.reverse() 783 784 # If the user has *not* overridden the `readlink()` method, then symlinks are unsupported 785 # and (in non-strict mode) we can improve performance by not calling `stat()`. 786 querying = strict or getattr(self.readlink, '_supported', True) 787 link_count = 0 788 while parts: 789 part = parts.pop() 790 if not part or part == '.': 791 continue 792 if part == '..': 793 if not path_tail: 794 if path_root: 795 # Delete '..' segment immediately following root 796 continue 797 elif path_tail[-1] != '..': 798 # Delete '..' segment and its predecessor 799 path_tail.pop() 800 continue 801 path_tail.append(part) 802 if querying and part != '..': 803 path = self.with_segments(path_root + self.parser.sep.join(path_tail)) 804 path._resolving = True 805 try: 806 st = path.stat(follow_symlinks=False) 807 if S_ISLNK(st.st_mode): 808 # Like Linux and macOS, raise OSError(errno.ELOOP) if too many symlinks are 809 # encountered during resolution. 810 link_count += 1 811 if link_count >= self._max_symlinks: 812 raise OSError(ELOOP, "Too many symbolic links in path", self._raw_path) 813 target_root, target_parts = path.readlink()._stack 814 # If the symlink target is absolute (like '/etc/hosts'), set the current 815 # path to its uppermost parent (like '/'). 816 if target_root: 817 path_root = target_root 818 path_tail.clear() 819 else: 820 path_tail.pop() 821 # Add the symlink target's reversed tail parts (like ['hosts', 'etc']) to 822 # the stack of unresolved path parts. 823 parts.extend(target_parts) 824 continue 825 elif parts and not S_ISDIR(st.st_mode): 826 raise NotADirectoryError(ENOTDIR, "Not a directory", self._raw_path) 827 except OSError: 828 if strict: 829 raise 830 else: 831 querying = False 832 return self.with_segments(path_root + self.parser.sep.join(path_tail)) 833 834 def symlink_to(self, target, target_is_directory=False): 835 """ 836 Make this path a symlink pointing to the target path. 837 Note the order of arguments (link, target) is the reverse of os.symlink. 838 """ 839 raise UnsupportedOperation(self._unsupported_msg('symlink_to()')) 840 841 def hardlink_to(self, target): 842 """ 843 Make this path a hard link pointing to the same file as *target*. 844 845 Note the order of arguments (self, target) is the reverse of os.link's. 846 """ 847 raise UnsupportedOperation(self._unsupported_msg('hardlink_to()')) 848 849 def touch(self, mode=0o666, exist_ok=True): 850 """ 851 Create this file with the given access mode, if it doesn't exist. 852 """ 853 raise UnsupportedOperation(self._unsupported_msg('touch()')) 854 855 def mkdir(self, mode=0o777, parents=False, exist_ok=False): 856 """ 857 Create a new directory at this given path. 858 """ 859 raise UnsupportedOperation(self._unsupported_msg('mkdir()')) 860 861 def rename(self, target): 862 """ 863 Rename this path to the target path. 864 865 The target path may be absolute or relative. Relative paths are 866 interpreted relative to the current working directory, *not* the 867 directory of the Path object. 868 869 Returns the new Path instance pointing to the target path. 870 """ 871 raise UnsupportedOperation(self._unsupported_msg('rename()')) 872 873 def replace(self, target): 874 """ 875 Rename this path to the target path, overwriting if that path exists. 876 877 The target path may be absolute or relative. Relative paths are 878 interpreted relative to the current working directory, *not* the 879 directory of the Path object. 880 881 Returns the new Path instance pointing to the target path. 882 """ 883 raise UnsupportedOperation(self._unsupported_msg('replace()')) 884 885 def chmod(self, mode, *, follow_symlinks=True): 886 """ 887 Change the permissions of the path, like os.chmod(). 888 """ 889 raise UnsupportedOperation(self._unsupported_msg('chmod()')) 890 891 def lchmod(self, mode): 892 """ 893 Like chmod(), except if the path points to a symlink, the symlink's 894 permissions are changed, rather than its target's. 895 """ 896 self.chmod(mode, follow_symlinks=False) 897 898 def unlink(self, missing_ok=False): 899 """ 900 Remove this file or link. 901 If the path is a directory, use rmdir() instead. 902 """ 903 raise UnsupportedOperation(self._unsupported_msg('unlink()')) 904 905 def rmdir(self): 906 """ 907 Remove this directory. The directory must be empty. 908 """ 909 raise UnsupportedOperation(self._unsupported_msg('rmdir()')) 910 911 def owner(self, *, follow_symlinks=True): 912 """ 913 Return the login name of the file owner. 914 """ 915 raise UnsupportedOperation(self._unsupported_msg('owner()')) 916 917 def group(self, *, follow_symlinks=True): 918 """ 919 Return the group name of the file gid. 920 """ 921 raise UnsupportedOperation(self._unsupported_msg('group()')) 922 923 @classmethod 924 def from_uri(cls, uri): 925 """Return a new path from the given 'file' URI.""" 926 raise UnsupportedOperation(cls._unsupported_msg('from_uri()')) 927 928 def as_uri(self): 929 """Return the path as a URI.""" 930 raise UnsupportedOperation(self._unsupported_msg('as_uri()')) 931