gpath
GPath is a robust, generalised abstract file path that provides path manipulations independent from the local environment, maximising cross-platform compatibility.
1""" 2 GPath is a robust, generalised abstract file path that provides path manipulations independent from the local environment, maximising cross-platform compatibility. 3""" 4 5 6__version__ = '0.4.5' 7 8from . import platform, render 9from ._gpath import GPath, GPathLike 10 11__all__ = ('GPath', 'GPathLike', 'platform', 'render')
81class GPath(Hashable, Sized, Iterable, render.Renderable): 82 """ 83 An immutable generalised abstract file path that has no dependency on any real filesystem. 84 85 The path can be manipulated on a system that is different from where it originated, notably including systems with a different operating system, and it can represent file paths on a system other than local. Examples where this is useful include remote management of servers and when cross-compiling source code for a different platform. 86 87 Since GPath objects are immutable, all operations return a new instance. The path is always stored in a normalised state, and is always treated as case sensitive. 88 89 The path can be rendered as a string using <code>str(<var>g</var>)</code>, which will use `/` as the path separator if possible to maximise cross-platform compatibility. 90 """ 91 92 __slots__ = ( 93 '_parts', 94 '_root', 95 '_drive', 96 '_parent_level', 97 '_platform', 98 '_encoding', 99 ) 100 101 102 def __init__(self, 103 path: Union[str, bytes, os.PathLike, GPath, None]="", 104 platform: Optional[Union[str, Platform]]=None, 105 encoding: Optional[str]=None, 106 ): 107 """ 108 Initialise a normalised and generalised abstract file path, possibly by copying an existing GPath object. 109 110 Parameters 111 ---------- 112 `path` 113 : path-like object representing a (possibly unnormalised) file path, or a GPath object to be copied 114 115 `platform` 116 : interpret `path` as originating from a specific platform. This is usually not required for normal file paths on Windows, Linux or macOS, and is needed only for edge cases (see [compatibility](https://github.com/yushiyangk/gpath#compatibility) in the readme). If `path` is a GPath, this argument has no effect. The platform name should be one of the keys in `gpath.platform.platform_names`. If specified, the platform will propagate to new GPaths returned by operations on this GPath; for binary operations of two GPaths, the platform specified by the left operand will be propagated. See also the `from_*()` static methods. 117 118 `encoding` 119 : the text encoding that should be used to decode paths given as bytes-like objects; if not specified, `'utf_8'` will be used by default. The encoding name should be one of the standard Python text encodings, as listed in the `codecs` module of the standard library. If specified, the encoding will propagate to new GPaths returned by operations on this GPath; for binary operations of two GPaths, the encoding specified by the left operand will be propagated. 120 121 Raises 122 ------ 123 `ValueError` if `other` is an invalid GPath 124 125 Examples 126 -------- 127 ```python 128 GPath("/") 129 GPath("/usr/bin") 130 GPath("C:/Program Files") 131 ``` 132 """ 133 134 self._parts: tuple[str, ...] = tuple() # root- or parent- relative path 135 self._root: bool = False 136 self._drive: str = "" 137 self._parent_level: int = 0 138 139 self._platform: Optional[Platform] = Platform.from_str(platform) if isinstance(platform, str) else platform 140 self._encoding: Optional[str] = encoding 141 142 if isinstance(path, GPath): 143 path._validate() 144 self._parts = path._parts 145 self._root = path._root 146 self._drive = path._drive 147 self._parent_level = path._parent_level 148 149 self._platform = path._platform if self._platform is None else self._platform 150 self._encoding = path._encoding if self._encoding is None else self._encoding 151 return 152 153 if path is None or path == "": 154 return 155 156 path = os.fspath(path) 157 158 if isinstance(path, bytes): 159 if self._encoding is None: 160 path = path.decode(DEFAULT_ENCODING) 161 else: 162 path = path.decode(self._encoding) 163 164 # path is a str 165 166 if self._platform is None: 167 platform = Platform.GENERIC 168 else: 169 platform = self._platform 170 171 if platform == Platform.POSIX: 172 for root in _rules.posix_rules.roots: 173 if path.startswith(root): 174 self._root = True 175 break 176 177 if self._root: 178 rootless_path = path[1:] 179 else: 180 rootless_path = path 181 182 parts = _split_relative(rootless_path, delimiters=_rules.posix_rules.separators) 183 184 elif platform == Platform.WINDOWS: 185 if len(path) >= 2 and path[1] in _rules.windows_rules.drive_postfixes: 186 self._drive = path[0] 187 driveless_path = path[2:] 188 else: 189 driveless_path = path 190 191 for root in _rules.windows_rules.roots: 192 if driveless_path.startswith(root): 193 self._root = True 194 break 195 196 if self._root: 197 rootless_path = driveless_path[1:] 198 else: 199 rootless_path = driveless_path 200 201 parts = _split_relative(rootless_path, delimiters=_rules.windows_rules.separators) 202 203 else: 204 if len(path) >= 2 and path[1] in _rules.generic_rules.drive_postfixes: 205 self._drive = path[0] 206 driveless_path = path[2:] 207 else: 208 driveless_path = path 209 210 for root in _rules.generic_rules.roots: 211 if driveless_path.startswith(root): 212 self._root = True 213 break 214 215 if self._root: 216 rootless_path = driveless_path[1:] 217 else: 218 rootless_path = driveless_path 219 220 parts = _split_relative(rootless_path, delimiters=_rules.generic_rules.separators) 221 222 223 parts = _normalise_relative(parts) 224 parent_level = 0 225 while parent_level < len(parts) and parts[parent_level] in _rules.generic_rules.parent_indicators: 226 parent_level += 1 227 self._parts = tuple(parts[parent_level:]) 228 if self._root == False: 229 self._parent_level = parent_level 230 231 232 @property 233 def named_parts(self) -> list[str]: 234 """ 235 Read-only named components of the path, not including the filesystem root, drive name, or any parent directories 236 237 Examples 238 -------- 239 ```python 240 GPath("usr/local/bin").named_parts # ["usr", "local", "bin"] 241 GPath("../../Documents").named_parts # ["Documents"] 242 GPath("/usr/bin").named_parts # ["usr", "bin"] 243 GPath("C:/Program Files").named_parts # ["Program Files"] 244 ``` 245 """ 246 return list(self._parts) 247 248 @property 249 def relative_parts(self) -> list[str]: 250 """ 251 Read-only relative components of the path, not including the filesystem root or drive name, including one item for each level of parent directory 252 253 Examples 254 -------- 255 ```python 256 GPath("usr/local/bin").relative_parts # ["usr", "local", "bin"] 257 GPath("../../Documents").relative_parts # ["..", "..", "Documents"] 258 GPath("/usr/bin").relative_parts # ["usr", "bin"] 259 GPath("C:/Program Files").relative_parts # ["Program Files"] 260 ``` 261 """ 262 return self.parent_parts + list(self._parts) 263 264 @property 265 def absolute(self) -> bool: 266 """ 267 Read-only flag for whether the path is an absolute path 268 269 Examples 270 -------- 271 ```python 272 GPath("/").absolute # True 273 GPath("C:/Windows").absolute # True 274 GPath("local/bin").absolute # False 275 GPath("../../Documents").absolute # False 276 ``` 277 """ 278 return self._root 279 280 @property 281 def root(self) -> bool: 282 """ 283 Read-only flag for whether the path is exactly the root of the filesystem 284 285 Examples 286 -------- 287 ```python 288 GPath("/").root # True 289 GPath("C:/").root # True 290 GPath("/usr/bin").root # False 291 GPath("C:/Windows").root # False 292 GPath("../../Documents").root # False 293 ``` 294 """ 295 return self._root and len(self._parts) == 0 296 297 @property 298 def drive(self) -> str: 299 """ 300 Read-only drive name 301 302 Examples 303 -------- 304 ```python 305 GPath("C:/Windows").drive # "C:" 306 GPath("/usr/bin").drive # "" 307 GPath("../../Documents").drive # "" 308 ``` 309 """ 310 return self._drive 311 312 @property 313 def parent_level(self) -> int: 314 """ 315 Read-only number of levels of parent directories that the path is relative to, which may be 0 316 317 Examples 318 -------- 319 ```python 320 GPath("../../Documents").parent_level # 2 321 GPath("usr/local/bin").parent_level # 0 322 ``` 323 """ 324 return self._parent_level 325 326 @property 327 def parent_parts(self) -> list[str]: 328 """ 329 Read-only path components representing a parent directory that it is relative to, if any, with one item for each level of parent directory 330 331 Examples 332 -------- 333 ```python 334 GPath("../../Documents").parent_parts # ["..", ".."] 335 GPath("usr/local/bin").parent_parts # [] 336 ``` 337 """ 338 return [_rules.generic_rules.parent_indicators[0] for i in range(self._parent_level)] 339 340 @property 341 def encoding(self) -> Union[str, None]: 342 """ 343 Read-only encoding used to decode other paths that are given as bytes-like objects, or None if the default should be used 344 """ 345 return self._encoding 346 347 @property 348 def platform(self) -> Union[str, None]: 349 """ 350 Read-only platform that other non-GPath operands should be interepreted as, or None if the default should be used 351 """ 352 return str(self._platform) if self._platform is not None else None 353 354 355 @staticmethod 356 def from_posix(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 357 """ 358 Initialise a GPath that originates from a POSIX-like operating system, or copy a GPath such that any future non-GPath operands would be interpreted as originating from a POSIX-like operating system. 359 360 See `__init__()` for details. 361 362 Equivalent to `GPath(path, platform='posix')` 363 ``` 364 """ 365 return GPath(path, platform=Platform.POSIX, encoding=encoding) 366 367 @staticmethod 368 def from_linux(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 369 """ 370 Alias of `from_posix()` 371 """ 372 return GPath.from_posix(path, encoding=encoding) 373 374 @staticmethod 375 def from_macos(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 376 """ 377 Alias of `from_posix()` 378 """ 379 return GPath.from_posix(path, encoding=encoding) 380 381 382 @staticmethod 383 def from_windows(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 384 """ 385 Initialise a GPath that originates from a Windows operating system, or copy a GPath such that any future non-GPath operands would be interpreted as originating from a Windows operating system. 386 387 See `__init__()` for details. 388 389 Equivalent to `GPath(path, platform='windows')` 390 ``` 391 """ 392 return GPath(path, platform=Platform.WINDOWS, encoding=encoding) 393 394 395 #@overload 396 #@staticmethod 397 #def partition(paths: Iterable[GPathLike], /, *, allow_current, allow_parents, platform, encoding) -> dict[GPath, list[GPath]]: 398 # ... 399 #@overload 400 #@staticmethod 401 #def partition(*paths: GPathLike, allow_current, allow_parents, platform, encoding) -> dict[GPath, list[GPath]]: 402 # ... 403 @staticmethod 404 def partition( 405 *paths, 406 allow_current: bool=True, 407 allow_parents: bool=True, 408 platform: Optional[Union[str, Platform]]=None, 409 encoding: Optional[str]=None, 410 ) -> dict[GPath, list[GPath]]: 411 """ 412 Partition a collection of paths based on shared common base paths such that each path belongs to one partition. 413 414 For each partition, return a list of relative paths from the base path of that partition to each corresponding input path within that partition, unless `allow_parents` is True (see below). If the input collection is ordered, the output order is preserved within each partition. If the input collection contains duplicates, the corresponding output lists will as well. 415 416 The number of partitions is minimised by merging partitions as much as possible, so that each partition represents the highest possible level base path. Two partitions can no longer be merged when there is no common base path between them, as determined by `common_with()`. This method takes the same optional arguments as `common_with()`, with the same default values. 417 418 Parameters 419 ---------- 420 `paths: Iterable[GPath | str | bytes | os.PathLike]` or `*paths: GPath | str | bytes | os.PathLike` 421 : the paths to be partitioned, which can be given as either a list-like object or as variadic arguments 422 423 `allow_current` 424 : whether non-parent relative paths with no shared components should be considered to have a common base path (see `common_with()`) 425 426 `allow_parents` 427 : whether paths that are relative to different levels of parent directories should be considered to have a common base path (see `common_with()`). **Warning**: when set to True, the output lists for each partition are invalidated, and explicitly set to empty. This is because it is not possible in general to obtain a relative path from the base path to its members if the base path is a parent directory of a higher level than the member (see `relpath_from()`). This option should be True if and only if the list of members in each partition are not of interest; in most cases False is more appropriate. 428 429 `platform` 430 : the originating platform that should be assumed when interpreting non-GPath objects in `paths`, if any (see `__init__()`). 431 432 `encoding` 433 : the text encoding that should be used to decode bytes-like objects in `paths`, if any (see `__init__()`). 434 435 Returns 436 ------- 437 a dictionary that maps the common base path of each partition to a list of relative paths 438 439 Raises 440 ------ 441 `ValueError` 442 if any of the GPaths are invalid 443 444 Examples 445 -------- 446 ```python 447 partitions = GPath.partition("/usr/bin", "/usr/local/bin", "../../doc", "C:/Windows", "C:/Program Files") 448 449 assert partitions == { 450 GPath("/usr") : [GPath("bin"), GPath("local")], 451 GPath("../../doc") : [GPath("")], 452 GPath("C:/") : [GPath("Windows"), GPath("Program Files")], 453 } 454 ``` 455 """ 456 flattened_paths: list[GPathLike] = [] 457 for path_or_list in paths: 458 if _is_gpathlike(path_or_list): 459 flattened_paths.append(path_or_list) 460 else: 461 flattened_paths.extend(path_or_list) 462 gpaths = [path if isinstance(path, GPath) else GPath(path, encoding=encoding, platform=platform) for path in flattened_paths] 463 464 partition_map = {} 465 if len(gpaths) > 0: 466 if allow_parents == True: 467 partition_map[gpaths[0]] = [] 468 else: 469 partition_map[gpaths[0]] = [gpaths[0]] 470 471 for path in gpaths[1:]: 472 partition_found = False 473 for partition in partition_map: 474 candidate_common = partition.common_with(path, allow_current=allow_current, allow_parents=allow_parents) 475 if candidate_common is not None: 476 partition_found = True 477 if candidate_common != partition: 478 partition_map[candidate_common] = partition_map[partition] 479 del partition_map[partition] 480 if allow_parents == False: 481 partition_map[candidate_common].append(path) 482 break 483 if not partition_found: 484 if allow_parents == True: 485 partition_map[path] = [] 486 else: 487 partition_map[path] = [path] 488 489 for partition, path_list in partition_map.items(): 490 partition_map[partition] = [path.subpath_from(partition) for path in path_list] 491 492 return partition_map 493 494 495 #@overload 496 #@staticmethod 497 #def join(paths: Iterable[GPathLike], /, *, platform, encoding) -> GPath: 498 # ... 499 #@overload 500 #@staticmethod 501 #def join(*paths: GPathLike, platform, encoding) -> GPath: 502 # ... 503 @staticmethod 504 def join(*paths, platform: Optional[Union[str, Platform]]=None, encoding: Optional[str]=None) -> GPath: 505 """ 506 Join a sequence of paths into a single path. Apart from the first item in the sequence, all subsequent paths should be relative paths and any absolute paths will be ignored. 507 508 Parameters 509 ---------- 510 `paths`: `Sequence[GPath | str | bytes | os.PathLike]` or `*paths: GPath | str | bytes | os.PathLike` 511 : the paths to be combined, which can be given as either a list-like object or as variadic arguments 512 513 `platform` 514 : the originating platform that should be assumed when interpreting non-GPath objects in `paths`, if any (see `__init__()`). 515 516 `encoding` 517 : the text encoding that should be used to decode bytes-like objects in `paths`, if any (see `__init__()`). 518 519 Returns 520 ------- 521 the combined path 522 523 Raises 524 ------ 525 `ValueError` if any of the GPaths are invalid 526 527 Examples 528 -------- 529 ```python 530 GPath.join("usr", "local", "bin") # GPath("usr/local/bin") 531 GPath.join("/usr/local/bin", "../../bin") # GPath("/usr/bin") 532 GPath.join("C:/", "Windows") # GPath("C:/Windows") 533 ``` 534 """ 535 flattened_paths: list[GPathLike] = [] 536 for path_or_list in paths: 537 if _is_gpathlike(path_or_list): 538 flattened_paths.append(path_or_list) 539 else: 540 flattened_paths.extend(path_or_list) 541 542 if len(flattened_paths) == 0: 543 return GPath(encoding=encoding, platform=platform) 544 545 combined_path = flattened_paths[0] 546 if not isinstance(combined_path, GPath): 547 combined_path = GPath(combined_path, encoding=encoding, platform=platform) 548 for path in flattened_paths[1:]: 549 combined_path = combined_path + path 550 551 return combined_path 552 553 554 def as_relative(self, parent_level: Optional[int]=None) -> GPath: 555 """ 556 Convert the path to a relative path and return a new copy. 557 558 Parameters 559 ---------- 560 `parent_level` 561 : the number of levels of parent directories that the returned path should be relative to, which may be 0. If set to None, the returned path will have the same parent level as the current path if it is currently a relative path, or have no parent level (i.e. 0) otherwise. 562 563 Raises 564 ------ 565 `TypeError` if `parent_level` is not a valid type 566 567 Examples 568 -------- 569 ```python 570 GPath("/usr/bin").as_relative() # GPath("usr/bin") 571 GPath("C:/Windows").as_relative() # GPath("C:Windows") 572 GPath("../Documents").as_relative() # GPath("../Documents") 573 ``` 574 """ 575 576 new_path = GPath(self) 577 new_path._root = False 578 if parent_level is None: 579 pass 580 elif isinstance(parent_level, int): 581 new_path._parent_level = parent_level 582 else: 583 raise TypeError(f"parent_level must be an int: {parent_level} ({type(parent_level)})") 584 585 return new_path 586 587 588 def as_absolute(self) -> GPath: 589 """ 590 Convert the path to an absolute path and return a new copy. 591 592 Any parent directory that the path is relative to will be removed. If the path is already absolute, an identical copy is returned. 593 594 Examples 595 -------- 596 ```python 597 GPath("usr/bin").as_absolute() # GPath("/usr/bin") 598 GPath("../Documents").as_absolute() # GPath("/Documents") 599 GPath("C:Windows").as_absolute() # GPath("C:/Windows") 600 ``` 601 """ 602 new_path = GPath(self) 603 new_path._root = True 604 new_path._parent_level = 0 605 return new_path 606 607 608 def with_drive(self, drive: Union[str, bytes, None]=None) -> GPath: 609 """ 610 Return a new copy of the path with the drive set to `drive`. 611 612 If `drive` is `""` or None, this would be equivalent to `without_drive()`. 613 614 Parameters 615 ---------- 616 `drive` 617 : the drive for the returned path, or either `""` or None if the returned path should have no drive 618 619 Returns 620 ------- 621 `GPath` 622 : a new path with the given drive 623 624 Raises 625 ------ 626 - `TypeError` if `drive` is not a valid type 627 - `ValueError` if `drive` has more than one character 628 629 Examples 630 -------- 631 ```python 632 GPath("C:/Windows").with_drive() # GPath("/Windows") 633 GPath("C:/Windows").with_drive("D") # GPath("D:/Windows") 634 GPath("/Windows").with_drive("C") # GPath("C:/Windows") 635 ``` 636 """ 637 if drive is None: 638 drive = "" 639 elif isinstance(drive, bytes): 640 if self._encoding is None: 641 drive = drive.decode(DEFAULT_ENCODING) 642 else: 643 drive = drive.decode(self._encoding) 644 elif isinstance(drive, str): 645 pass 646 else: 647 raise TypeError(f"drive must be a str or bytes object: {drive} ({type(drive)})") 648 649 if len(drive) > 1: 650 raise ValueError(f"drive can only be a single character, an empty string or None: {drive}") 651 652 new_path = GPath(self) 653 new_path._drive = drive 654 return new_path 655 656 657 def without_drive(self) -> GPath: 658 """ 659 Return a new copy of the path without a drive. 660 661 Equivalent to `with_drive("")` or `with_drive(None)`. 662 663 Returns 664 ------- 665 `GPath` 666 : a new path without a drive 667 668 Examples 669 -------- 670 ```python 671 GPath("C:/Windows").without_drive() # GPath("/Windows") 672 ``` 673 """ 674 return self.with_drive(None) 675 676 677 def common_with(self, other: GPathLike, allow_current: bool=True, allow_parents: bool=False) -> Optional[GPath]: 678 """ 679 Find the longest common base path shared between `self` and `other`, or return None if no such path exists. 680 681 A common base path might not exist if one path is an absolute path while the other is a relative path, or if the two paths are in different filesystems (with different drive names), or in other cases as controlled by the `allow_current` and `allow_parents` options. 682 683 If using the default options of `allow_current=True` and `allow_parent=False`, the binary operator for bitwise-and can be used: `__and__()` (usage: <code><var>g1</var> & <var>g2</var></code>). 684 685 Parameters 686 ---------- 687 `other` 688 : the path to compare with 689 690 `allow_current` 691 : whether two non-parent relative paths that do not share any components should be considered to have a common base path, namely the imaginary current working directory. For instance, `GPath("some/rel/path").find_common("another/rel/path")` will return `GPath("")` if set to True, or return None if set to False. 692 693 `allow_parents` 694 : whether two relative paths that are relative to different levels of parent directories should be considered to have a common base path, which is the highest level of parent directory between the two paths. For instance, `GPath("../rel/to/parent").find_common("../../rel/to/grandparent")` will return `GPath("../..")` if set to True, or return None if set to False. **Warning**: when set to True, given a higher level of parent directory as output, it may not be possible to find the relative path to one of the inputs (see `relpath_from()`); in most cases False is more appropriate. 695 696 Returns 697 ------- 698 `GPath` 699 : the longest common base path, which may be empty, if it exists 700 701 `None` 702 : otherwise 703 704 Raises 705 ------ 706 `ValueError` if either `self` or `other` is an invalid GPath 707 708 Examples 709 -------- 710 ```python 711 GPath("/usr/bin").find_common("/usr/local/bin") # GPath("/usr") 712 GPath("C:/Windows/System32").find_common("C:/Program Files") # GPath("C:/") 713 GPath("../Documents").find_common("../Pictures") # GPath("..") 714 ``` 715 """ 716 self._validate() 717 if isinstance(other, GPath): 718 other._validate() 719 else: 720 other = GPath(other, encoding=self._encoding) 721 722 if self._drive != other._drive: 723 return None 724 if self._root != other._root: 725 return None 726 727 if allow_parents: 728 allow_current = True 729 730 parts = [] 731 if self._root: 732 common_path = GPath(self) 733 for part1, part2 in zip(self._parts, other._parts): 734 if part1 == part2: 735 parts.append(part1) 736 else: 737 if self._parent_level != other._parent_level: 738 if not allow_parents: 739 return None 740 741 common_path = GPath(self) 742 common_path._parent_level = max(self._parent_level, other._parent_level) 743 else: 744 common_path = GPath(self) 745 for part1, part2 in zip(self._parts, other._parts): 746 if part1 == part2: 747 parts.append(part1) 748 749 common_path._parts = tuple(parts) 750 751 if not allow_current and not bool(common_path): 752 if common_path != self or common_path != other: 753 return None 754 return common_path 755 756 757 def subpath_from(self, base: GPathLike) -> Optional[GPath]: 758 """ 759 Find the relative subpath from `base` to `self` if possible and if `base` contains `self`, or return None otherwise. 760 761 None will also be returned if there are unknown components in the subpath from `base` to `self`. For instance, if `self` is relative to the parent directory while `base` is relative to the grandparent directory, the path from the grandparent directory `../..` to the parent directory `..` cannot be known. 762 763 Similar to `relpath_from()`, but `self` must be a descendent of `base`. 764 765 Parameters 766 ---------- 767 `base` 768 : the base path that the relative subpath should start from 769 770 Returns 771 ------- 772 `GPath` 773 : relative subpath from `base` to `self`, which may be empty, if it exists 774 775 `None` 776 : otherwise 777 778 Raises 779 ------ 780 `ValueError` if either `self` or `base` is an invalid GPath 781 782 Examples 783 -------- 784 ```python 785 GPath("/usr/local/bin").subpath_from("/usr") # GPath("local/bin") 786 GPath("/usr/bin").subpath_from("/usr/local/bin") # None 787 GPath("/usr/bin").subpath_from("../Documents") # None 788 ``` 789 """ 790 if not isinstance(base, GPath): 791 base = GPath(base, encoding=self._encoding) 792 793 if self.common_with(base, allow_current=True, allow_parents=False) is not None and self in base: 794 # If self._parent_level > base._parent_level, self is not in base, whereas if self._parent_level < base._parent_level, path from base to self's parent cannot be known 795 base_length = len(base._parts) 796 new_path = GPath(self) 797 new_path._parts = self._parts[base_length:] # () when self == base 798 new_path._drive = "" 799 new_path._root = False 800 new_path._parent_level = 0 801 return new_path 802 else: 803 return None 804 805 806 def relpath_from(self, origin: GPathLike) -> Optional[GPath]: 807 """ 808 Find the relative path from `origin` to `self` if possible, or return None otherwise. 809 810 None will also be returned if there are unknown components in the relative path from `origin` to `self`. For instance, if `self` is relative to the parent directory while `base` base is relative to the grandparent directory, the path from the grandparent directory `../..` to the parent directory `..` cannot be known. 811 812 Similar to `subpath_from()`, but `self` does not need to be a descendent of `origin`. 813 814 Parameters 815 ---------- 816 `origin` 817 : the origin that the relative path should start from 818 819 Returns 820 ------- 821 `GPath` 822 : relative path from `origin` to `self`, which may be empty, if it exists 823 824 `None` 825 : otherwise 826 827 Raises 828 ------ 829 `ValueError` if either `self` or `origin` is an invalid GPath 830 831 Examples 832 -------- 833 ```python 834 GPath("/usr/local/bin").subpath_from("/usr") # GPath("local/bin") 835 GPath("/usr/bin").subpath_from("/usr/local/bin") # GPath("../../bin") 836 GPath("/usr/bin").subpath_from("../Documents") # None 837 ``` 838 """ 839 self._validate() 840 if not isinstance(origin, GPath): 841 origin = GPath(origin, encoding=self._encoding) 842 843 if origin._root: 844 common = self.common_with(origin) 845 if common is None: 846 return None 847 848 new_path = GPath(self) 849 new_path._parent_level = len(origin) - len(common) 850 new_path._parts = self._parts[len(common):] 851 new_path._drive = "" 852 new_path._root = False 853 return new_path 854 855 else: 856 common = self.common_with(origin, allow_current=True, allow_parents=True) 857 if common is None: 858 return None 859 if common._parent_level > self._parent_level: 860 return None # Path from common to self's parent cannot be known 861 862 # common._dotdot == self._dotdot 863 # origin._dotdot <= self._dotdot 864 865 new_path = GPath(self) 866 new_path._drive = "" 867 new_path._root = False 868 if len(common) == 0: 869 if origin._parent_level == self._parent_level: 870 new_path._parent_level = len(origin) 871 else: 872 new_path._parent_level = (common._parent_level - origin._parent_level) + len(origin) 873 new_path._parts = self._parts 874 else: 875 new_path._parent_level = len(origin) - len(common) 876 new_path._parts = self._parts[len(common):] 877 878 return new_path 879 880 881 def render(self, platform: Union[str, Platform, None]) -> render.RenderedPath: 882 """ 883 Convert the path to a RenderedPath for printing in a specific target operating system. 884 885 This will convert, and coerce if necessary, the generalised abstract GPath into a platform-specific path which can then be converted to a printable string using <code>str(<var>renderred_path</var>)</code>. The resulting string will be in the format preferred by the target platform. 886 887 If the GPath contains features that the target platform does not support (such as drive name when the target platform is POSIX), and if there are no analogous features in the target platform, they will be dropped in the rendered path. 888 889 The rendered path also implements total ordering with binary comparisons, e.g. <code><var>r1</var> < <var>r2</var></code>, making it useful for sorting and collation. This ordering is done with platform-specific semantics, unlike GPath which does not have meaningful order. 890 891 Parameters 892 ---------- 893 `platform` 894 : the target platform where the path is to be used 895 896 Returns 897 ------- 898 `RenderedPath` 899 : platform-specific path ready for sorting or output 900 901 Examples 902 -------- 903 ```python 904 # Print examples 905 print(GPath("/usr/bin").render('linux')) # /usr/bin 906 print(GPath("/usr/bin").render('windows')) # \\usr\\bin 907 print(GPath("C:/Windows").render('linux')) # /Windows 908 print(GPath("C:/Windows").render('windows')) # C:\\Windows 909 910 # Ordering examples 911 GPath("").render('linux') < GPath("abc").render('linux') # True 912 GPath("abc").render('linux') < GPath("..").render('linux') # True 913 GPath("..").render('linux') < GPath("../..").render('linux') # True 914 GPath("../..").render('linux') < GPath("/").render('linux') # True 915 GPath("/").render('linux') < GPath("C:/").render('linux') # False 916 GPath("/").render('linux') <= GPath("C:/").render('linux') # True 917 GPath("/").render('windows') < GPath("C:/").render('windows') # True 918 ``` 919 """ 920 if platform is None: 921 platform = DEFAULT_PLATFORM 922 elif isinstance(platform, str): 923 platform = Platform.from_str(platform) 924 return render.get_type(platform)(self) 925 926 927 def __hash__(self) -> int: 928 """ 929 Calculate hash of the GPath object. 930 931 Usage: <code>hash(<var>g</var>)</code> 932 """ 933 return hash(self._tuple) 934 935 936 def __eq__(self, other: GPathLike) -> bool: 937 """ 938 Check if two GPaths are completely identical. 939 940 Always return False if `other` is not a GPath object, even if it is a GPath-like object. 941 942 Usage: <code><var>g1</var> == <var>g2</var></code> 943 944 Examples 945 -------- 946 ```python 947 GPath("/usr/bin") == GPath("/usr/bin") # True 948 GPath("/usr/bin") == GPath("usr/bin") # False 949 GPath("C:/") == GPath("D:/") # False 950 ``` 951 """ 952 if not isinstance(other, GPath): 953 other = GPath(other, encoding=self._encoding) 954 return self._tuple == other._tuple 955 956 957 def __bool__(self) -> bool: 958 """ 959 False if `self` is a relative path without any relative components and without a drive, and True otherwise. 960 961 Usage: <code>bool(<var>g</var>)</code>, <code>not <var>g</var></code>, or <code>if <var>g</var>:</code> 962 963 Examples 964 -------- 965 ```python 966 bool(GPath("/")) # True 967 bool(GPath("..")) # True 968 bool(GPath("doc")) # True 969 bool(GPath("")) # False 970 ``` 971 """ 972 return self._root or self._drive != "" or self._parent_level != 0 or len(self._parts) > 0 973 974 975 def __str__(self) -> str: 976 """ 977 Return a platform-independent string representation of the path. 978 979 Usage: <code>str(<var>g</var>)</code> 980 """ 981 return str(self.render(Platform.GENERIC)) 982 983 984 def __repr__(self) -> str: 985 """ 986 Return a string that, when printed, gives the Python code associated with instantiating the GPath object. 987 988 Usage: <code>repr(<var>g</var>)</code> 989 """ 990 if self._platform is None: 991 platform_repr = "" 992 else: 993 platform_repr = f", platform={repr(self._platform)}" 994 995 if self._encoding is None: 996 encoding_repr = "" 997 else: 998 encoding_repr = f", encoding={repr(self._encoding)}" 999 1000 if bool(self): 1001 return f"GPath({repr(str(self))}{platform_repr}{encoding_repr})" 1002 else: 1003 return f"GPath({repr('')}{platform_repr}{encoding_repr})" 1004 1005 1006 def __len__(self) -> int: 1007 """ 1008 Get the number of named path components, excluding any drive name or parent directories. 1009 1010 Usage: <code>len(<var>g</var>)</code> 1011 1012 Examples 1013 -------- 1014 ```python 1015 len(GPath("/usr/bin")) # 2 1016 len(GPath("/")) # 0 1017 len(GPath("C:/Windows")) # 0 1018 len(GPath("C:/")) # 0 1019 ``` 1020 """ 1021 return len(self._parts) 1022 1023 1024 def __getitem__(self, index: Union[int, slice]) -> Union[str, list[str]]: 1025 """ 1026 Get a 0-indexed named path component, or a slice of path components, excluding any drive name or parent directories. 1027 1028 Usage: <code><var>g</var>[<var>n</var>]</code>, <code><var>g</var>[<var>start</var>:<var>end</var>]</code>, <code><var>g</var>[<var>start</var>:<var>end</var>:<var>step</var>]</code>, etc. 1029 1030 Examples 1031 -------- 1032 ```python 1033 GPath("/usr/local/bin")[1] # "local" 1034 GPath("/usr/local/bin")[-1] # "bin" 1035 GPath("/usr/local/bin")[1:] # ["local", "bin"] 1036 GPath("/usr/local/bin")[::2] # ["usr", "bin"] 1037 ``` 1038 """ 1039 if isinstance(index, int): 1040 return self._parts[index] 1041 elif isinstance(index, slice): 1042 return list(self._parts[index]) 1043 1044 1045 def __iter__(self) -> Iterator[str]: 1046 """ 1047 Get an iterator through the named path components, excluding any drive name or parent directories. 1048 1049 Usage: <code>iter(<var>g</var>)</code> or <code>for <var>p</var> in <var>g</var>:</code> 1050 """ 1051 return iter(self._parts) 1052 1053 1054 def __contains__(self, other: GPathLike) -> bool: 1055 """ 1056 Check if the path represented by `self` contains the path represented by `other`; i.e. check if `self` is a parent directory of `other`. 1057 1058 Usage: <code><var>other</var> in <var>self</var></code> 1059 1060 Raises `ValueError` if either GPath is invalid 1061 1062 Examples 1063 -------- 1064 ```python 1065 GPath("/usr/local/bin") in GPath("/usr") # True 1066 GPath("/usr/local/bin") in GPath("/bin") # False 1067 GPath("..") in GPath("../..") # True 1068 GPath("..") in GPath("C:/") # False 1069 ``` 1070 """ 1071 if not isinstance(other, GPath): 1072 other = GPath(other, encoding=self._encoding) 1073 1074 common_path = self.common_with(other, allow_current=True, allow_parents=True) 1075 return common_path is not None and common_path == self 1076 1077 1078 def __add__(self, other: GPathLike) -> GPath: 1079 """ 1080 Add (concatenate) `other` to the end of `self`, and return a new copy. 1081 1082 If `other` is an absolute path, the returned path will be an absolute path that matches `other`, apart from the drive name. 1083 1084 If `other` has a drive, the returned path will have the same drive as `other`. Otherwise, the returned path will have the same drive as `self`. If neither has a drive, the returned path will not have a drive as well. 1085 1086 Alias: `__truediv__()` 1087 1088 Usage: <code><var>self</var> + <var>other</var></code> or <code><var>self</var> / <var>other</var></code> 1089 1090 Raises `ValueError` if either GPath is invalid 1091 1092 Examples 1093 -------- 1094 ```python 1095 GPath("/usr") + GPath("local/bin") # GPath("/usr/local/bin") 1096 GPath("C:/Windows/System32") + GPath("../SysWOW64") # GPath("C:/Windows/SysWOW64") 1097 GPath("C:/Windows/System32") + GPath("/usr/bin") # GPath("C:/usr/bin") 1098 GPath("..") + GPath("../..") # GPath("../../..") 1099 GPath("..") / GPath("../..") # GPath("../../..") 1100 ``` 1101 """ 1102 if isinstance(other, GPath): 1103 other._validate 1104 else: 1105 other = GPath(other, encoding=self._encoding) 1106 1107 new_path = GPath(self) 1108 if other._root: 1109 new_path._parts = other._parts 1110 new_path._root = other._root 1111 new_path._parent_level = other._parent_level 1112 else: 1113 new_parts = [part for part in self._parts] 1114 for i in range(other._parent_level): 1115 if len(new_parts) > 0: 1116 new_parts.pop() 1117 elif not new_path._root: 1118 new_path._parent_level += 1 1119 else: 1120 pass # parent of directory of root is still root 1121 1122 new_parts.extend(other._parts) 1123 new_path._parts = tuple(new_parts) 1124 1125 if other._drive != "": 1126 new_path._drive = other._drive 1127 1128 return new_path 1129 1130 1131 def __sub__(self, n: int) -> GPath: 1132 """ 1133 Remove `n` components from the end of the path and return a new copy. 1134 1135 Usage: <code><var>self</var> - <var>n</var></code> 1136 1137 Raises `ValueError` if `self` is an invalid GPath or if `n` is negative 1138 1139 Examples 1140 -------- 1141 ```python 1142 GPath("C:/Windows/System32") - 1 # GPath("C:/Windows") 1143 GPath("/usr/bin") - 2 # GPath("/") 1144 GPath("Documents") - 3 # GPath("..") 1145 GPath("/") - 1 # GPath("/") 1146 ``` 1147 """ 1148 if n < 0: 1149 raise ValueError("cannot subtract a negative number of components from the path: {n}; use __add__() instead") 1150 1151 new_path = GPath(self) 1152 new_parts = [part for part in self._parts] 1153 for i in range(n): 1154 if len(new_parts) > 0: 1155 new_parts.pop() 1156 elif not new_path._root: 1157 new_path._parent_level += 1 1158 else: 1159 pass # removing components from root should still give root 1160 new_path._parts = tuple(new_parts) 1161 return new_path 1162 1163 1164 def __mul__(self, n: int) -> GPath: 1165 """ 1166 Duplicate the named components of `self` `n` times and return a new path with the duplicated components. 1167 1168 Named components will be duplicated separately from the components representing a parent directory. If `self` is an absolute path, only the relative components will be duplicated. 1169 1170 If `n` is 0, the result is an empty path (either relative or absolute). 1171 1172 Usage: <code><var>self</var> * <var>n</var></code> 1173 1174 Raises `ValueError` if `self` is an invalid GPath or if `n` is negative. 1175 1176 Examples 1177 -------- 1178 ```python 1179 GPath("/usr/bin") * 2 # GPath("/usr/bin/usr/bin") 1180 GPath("../docs") * 2 # GPath("../../docs/docs") 1181 GPath("C:/Windows") * 0 # GPath("C:/") 1182 ``` 1183 """ 1184 if n < 0: 1185 raise ValueError("cannot multiply path by a negative integer: {n}") 1186 new_path = GPath(self) 1187 new_path._parent_level = self._parent_level * n 1188 new_path._parts = self._parts * n 1189 return new_path 1190 1191 1192 def __truediv__(self, other: GPathLike) -> GPath: 1193 """ 1194 Alias of `__add__()`. 1195 1196 Usage: <code><var>self</var> + <var>other</var></code> or <code><var>self</var> / <var>other</var></code> 1197 """ 1198 return self.__add__(other) 1199 1200 1201 def __and__(self, other: GPathLike) -> Union[GPath, None]: 1202 """ 1203 Equivalent to `self.common_with(other)`, using the default options of `common_with()`. 1204 1205 Usage: <code><var>g1</var> & <var>g2</var></code> 1206 """ 1207 return self.common_with(other) 1208 1209 1210 def __lshift__(self, n: int) -> GPath: 1211 """ 1212 Move the imaginary current working directory `n` steps up the filesystem tree. 1213 1214 If `self` is a relative path, remove up to `n` levels of parent directories from the start of the path and return a copy. If it is an absolute path, return a copy of `self` unchanged. 1215 1216 If `n` is negative, this is equivalent to `__rshift__(-n)`. 1217 1218 Usage: <code><var>self</var> << <var>n</var></code> 1219 1220 Raises `ValueError` if `self` is an invalid GPath. 1221 1222 Examples 1223 -------- 1224 ```python 1225 GPath("../SysWOW64/drivers") << 1 # GPath("SysWOW64/drivers") 1226 GPath("../doc") << 2 # GPath("doc") 1227 GPath("/usr/bin") << 2 # GPath("/usr/bin") 1228 ``` 1229 """ 1230 if n < 0: 1231 return self.__rshift__(-1 * n) 1232 new_path = GPath(self) 1233 if not new_path._root: 1234 new_path._parent_level = max(new_path._parent_level - n, 0) 1235 return new_path 1236 1237 1238 def __rshift__(self, n: int) -> GPath: 1239 """ 1240 Move the imaginary current working directory `n` steps down the filesystem tree. 1241 1242 If `self` is a relative path, add `n` levels of parent directories to the start of the path and return a copy. If it is an absolute path, return a copy of `self` unchanged. 1243 1244 If `n` is negative, this is equivalent to `__lshift__(-n)`. 1245 1246 Usage: <code><var>self</var> >> <var>n</var></code> 1247 1248 Raises `ValueError` if `self` is an invalid GPath 1249 1250 Examples 1251 -------- 1252 ```python 1253 GPath("../SysWOW64/drivers") >> 1 # GPath("../../SysWOW64/drivers") 1254 GPath("/usr/bin") >> 2 # GPath("/usr/bin") 1255 ``` 1256 """ 1257 if n < 0: 1258 return self.__lshift__(-1 * n) 1259 new_path = GPath(self) 1260 if not new_path._root: 1261 new_path._parent_level += n 1262 return new_path 1263 1264 1265 @property 1266 def _tuple(self) -> tuple: 1267 # Get a tuple of all fields 1268 return ( 1269 self._root, 1270 self._drive, 1271 self._parent_level, 1272 self._parts, 1273 self._platform, 1274 self._encoding, 1275 ) 1276 1277 1278 def _validate(self) -> bool: 1279 # Check if self is in a valid state 1280 if self._parent_level < 0: 1281 raise ValueError(f"invalid GPath, _parent cannot be negative: {repr(self)}") 1282 if self._root: 1283 if self._parent_level != 0: 1284 raise ValueError(f"invalid GPath, _parent must be 0 when root is True: {repr(self)}") 1285 return True
An immutable generalised abstract file path that has no dependency on any real filesystem.
The path can be manipulated on a system that is different from where it originated, notably including systems with a different operating system, and it can represent file paths on a system other than local. Examples where this is useful include remote management of servers and when cross-compiling source code for a different platform.
Since GPath objects are immutable, all operations return a new instance. The path is always stored in a normalised state, and is always treated as case sensitive.
The path can be rendered as a string using str(g)
, which will use /
as the path separator if possible to maximise cross-platform compatibility.
102 def __init__(self, 103 path: Union[str, bytes, os.PathLike, GPath, None]="", 104 platform: Optional[Union[str, Platform]]=None, 105 encoding: Optional[str]=None, 106 ): 107 """ 108 Initialise a normalised and generalised abstract file path, possibly by copying an existing GPath object. 109 110 Parameters 111 ---------- 112 `path` 113 : path-like object representing a (possibly unnormalised) file path, or a GPath object to be copied 114 115 `platform` 116 : interpret `path` as originating from a specific platform. This is usually not required for normal file paths on Windows, Linux or macOS, and is needed only for edge cases (see [compatibility](https://github.com/yushiyangk/gpath#compatibility) in the readme). If `path` is a GPath, this argument has no effect. The platform name should be one of the keys in `gpath.platform.platform_names`. If specified, the platform will propagate to new GPaths returned by operations on this GPath; for binary operations of two GPaths, the platform specified by the left operand will be propagated. See also the `from_*()` static methods. 117 118 `encoding` 119 : the text encoding that should be used to decode paths given as bytes-like objects; if not specified, `'utf_8'` will be used by default. The encoding name should be one of the standard Python text encodings, as listed in the `codecs` module of the standard library. If specified, the encoding will propagate to new GPaths returned by operations on this GPath; for binary operations of two GPaths, the encoding specified by the left operand will be propagated. 120 121 Raises 122 ------ 123 `ValueError` if `other` is an invalid GPath 124 125 Examples 126 -------- 127 ```python 128 GPath("/") 129 GPath("/usr/bin") 130 GPath("C:/Program Files") 131 ``` 132 """ 133 134 self._parts: tuple[str, ...] = tuple() # root- or parent- relative path 135 self._root: bool = False 136 self._drive: str = "" 137 self._parent_level: int = 0 138 139 self._platform: Optional[Platform] = Platform.from_str(platform) if isinstance(platform, str) else platform 140 self._encoding: Optional[str] = encoding 141 142 if isinstance(path, GPath): 143 path._validate() 144 self._parts = path._parts 145 self._root = path._root 146 self._drive = path._drive 147 self._parent_level = path._parent_level 148 149 self._platform = path._platform if self._platform is None else self._platform 150 self._encoding = path._encoding if self._encoding is None else self._encoding 151 return 152 153 if path is None or path == "": 154 return 155 156 path = os.fspath(path) 157 158 if isinstance(path, bytes): 159 if self._encoding is None: 160 path = path.decode(DEFAULT_ENCODING) 161 else: 162 path = path.decode(self._encoding) 163 164 # path is a str 165 166 if self._platform is None: 167 platform = Platform.GENERIC 168 else: 169 platform = self._platform 170 171 if platform == Platform.POSIX: 172 for root in _rules.posix_rules.roots: 173 if path.startswith(root): 174 self._root = True 175 break 176 177 if self._root: 178 rootless_path = path[1:] 179 else: 180 rootless_path = path 181 182 parts = _split_relative(rootless_path, delimiters=_rules.posix_rules.separators) 183 184 elif platform == Platform.WINDOWS: 185 if len(path) >= 2 and path[1] in _rules.windows_rules.drive_postfixes: 186 self._drive = path[0] 187 driveless_path = path[2:] 188 else: 189 driveless_path = path 190 191 for root in _rules.windows_rules.roots: 192 if driveless_path.startswith(root): 193 self._root = True 194 break 195 196 if self._root: 197 rootless_path = driveless_path[1:] 198 else: 199 rootless_path = driveless_path 200 201 parts = _split_relative(rootless_path, delimiters=_rules.windows_rules.separators) 202 203 else: 204 if len(path) >= 2 and path[1] in _rules.generic_rules.drive_postfixes: 205 self._drive = path[0] 206 driveless_path = path[2:] 207 else: 208 driveless_path = path 209 210 for root in _rules.generic_rules.roots: 211 if driveless_path.startswith(root): 212 self._root = True 213 break 214 215 if self._root: 216 rootless_path = driveless_path[1:] 217 else: 218 rootless_path = driveless_path 219 220 parts = _split_relative(rootless_path, delimiters=_rules.generic_rules.separators) 221 222 223 parts = _normalise_relative(parts) 224 parent_level = 0 225 while parent_level < len(parts) and parts[parent_level] in _rules.generic_rules.parent_indicators: 226 parent_level += 1 227 self._parts = tuple(parts[parent_level:]) 228 if self._root == False: 229 self._parent_level = parent_level
Initialise a normalised and generalised abstract file path, possibly by copying an existing GPath object.
Parameters
path
: path-like object representing a (possibly unnormalised) file path, or a GPath object to be copied
platform
: interpret path
as originating from a specific platform. This is usually not required for normal file paths on Windows, Linux or macOS, and is needed only for edge cases (see compatibility in the readme). If path
is a GPath, this argument has no effect. The platform name should be one of the keys in gpath.platform.platform_names
. If specified, the platform will propagate to new GPaths returned by operations on this GPath; for binary operations of two GPaths, the platform specified by the left operand will be propagated. See also the from_*()
static methods.
encoding
: the text encoding that should be used to decode paths given as bytes-like objects; if not specified, 'utf_8'
will be used by default. The encoding name should be one of the standard Python text encodings, as listed in the codecs
module of the standard library. If specified, the encoding will propagate to new GPaths returned by operations on this GPath; for binary operations of two GPaths, the encoding specified by the left operand will be propagated.
Raises
ValueError
if other
is an invalid GPath
Examples
GPath("/")
GPath("/usr/bin")
GPath("C:/Program Files")
Read-only named components of the path, not including the filesystem root, drive name, or any parent directories
Examples
GPath("usr/local/bin").named_parts # ["usr", "local", "bin"]
GPath("../../Documents").named_parts # ["Documents"]
GPath("/usr/bin").named_parts # ["usr", "bin"]
GPath("C:/Program Files").named_parts # ["Program Files"]
Read-only relative components of the path, not including the filesystem root or drive name, including one item for each level of parent directory
Examples
GPath("usr/local/bin").relative_parts # ["usr", "local", "bin"]
GPath("../../Documents").relative_parts # ["..", "..", "Documents"]
GPath("/usr/bin").relative_parts # ["usr", "bin"]
GPath("C:/Program Files").relative_parts # ["Program Files"]
Read-only flag for whether the path is an absolute path
Examples
GPath("/").absolute # True
GPath("C:/Windows").absolute # True
GPath("local/bin").absolute # False
GPath("../../Documents").absolute # False
Read-only flag for whether the path is exactly the root of the filesystem
Examples
GPath("/").root # True
GPath("C:/").root # True
GPath("/usr/bin").root # False
GPath("C:/Windows").root # False
GPath("../../Documents").root # False
Read-only drive name
Examples
GPath("C:/Windows").drive # "C:"
GPath("/usr/bin").drive # ""
GPath("../../Documents").drive # ""
Read-only number of levels of parent directories that the path is relative to, which may be 0
Examples
GPath("../../Documents").parent_level # 2
GPath("usr/local/bin").parent_level # 0
Read-only path components representing a parent directory that it is relative to, if any, with one item for each level of parent directory
Examples
GPath("../../Documents").parent_parts # ["..", ".."]
GPath("usr/local/bin").parent_parts # []
Read-only encoding used to decode other paths that are given as bytes-like objects, or None if the default should be used
Read-only platform that other non-GPath operands should be interepreted as, or None if the default should be used
355 @staticmethod 356 def from_posix(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 357 """ 358 Initialise a GPath that originates from a POSIX-like operating system, or copy a GPath such that any future non-GPath operands would be interpreted as originating from a POSIX-like operating system. 359 360 See `__init__()` for details. 361 362 Equivalent to `GPath(path, platform='posix')` 363 ``` 364 """ 365 return GPath(path, platform=Platform.POSIX, encoding=encoding)
Initialise a GPath that originates from a POSIX-like operating system, or copy a GPath such that any future non-GPath operands would be interpreted as originating from a POSIX-like operating system.
See __init__()
for details.
Equivalent to GPath(path, platform='posix')
```
367 @staticmethod 368 def from_linux(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 369 """ 370 Alias of `from_posix()` 371 """ 372 return GPath.from_posix(path, encoding=encoding)
Alias of from_posix()
374 @staticmethod 375 def from_macos(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 376 """ 377 Alias of `from_posix()` 378 """ 379 return GPath.from_posix(path, encoding=encoding)
Alias of from_posix()
382 @staticmethod 383 def from_windows(path: Union[str, bytes, os.PathLike, GPath, None]="", encoding: Optional[str]=None) -> GPath: 384 """ 385 Initialise a GPath that originates from a Windows operating system, or copy a GPath such that any future non-GPath operands would be interpreted as originating from a Windows operating system. 386 387 See `__init__()` for details. 388 389 Equivalent to `GPath(path, platform='windows')` 390 ``` 391 """ 392 return GPath(path, platform=Platform.WINDOWS, encoding=encoding)
Initialise a GPath that originates from a Windows operating system, or copy a GPath such that any future non-GPath operands would be interpreted as originating from a Windows operating system.
See __init__()
for details.
Equivalent to GPath(path, platform='windows')
```
403 @staticmethod 404 def partition( 405 *paths, 406 allow_current: bool=True, 407 allow_parents: bool=True, 408 platform: Optional[Union[str, Platform]]=None, 409 encoding: Optional[str]=None, 410 ) -> dict[GPath, list[GPath]]: 411 """ 412 Partition a collection of paths based on shared common base paths such that each path belongs to one partition. 413 414 For each partition, return a list of relative paths from the base path of that partition to each corresponding input path within that partition, unless `allow_parents` is True (see below). If the input collection is ordered, the output order is preserved within each partition. If the input collection contains duplicates, the corresponding output lists will as well. 415 416 The number of partitions is minimised by merging partitions as much as possible, so that each partition represents the highest possible level base path. Two partitions can no longer be merged when there is no common base path between them, as determined by `common_with()`. This method takes the same optional arguments as `common_with()`, with the same default values. 417 418 Parameters 419 ---------- 420 `paths: Iterable[GPath | str | bytes | os.PathLike]` or `*paths: GPath | str | bytes | os.PathLike` 421 : the paths to be partitioned, which can be given as either a list-like object or as variadic arguments 422 423 `allow_current` 424 : whether non-parent relative paths with no shared components should be considered to have a common base path (see `common_with()`) 425 426 `allow_parents` 427 : whether paths that are relative to different levels of parent directories should be considered to have a common base path (see `common_with()`). **Warning**: when set to True, the output lists for each partition are invalidated, and explicitly set to empty. This is because it is not possible in general to obtain a relative path from the base path to its members if the base path is a parent directory of a higher level than the member (see `relpath_from()`). This option should be True if and only if the list of members in each partition are not of interest; in most cases False is more appropriate. 428 429 `platform` 430 : the originating platform that should be assumed when interpreting non-GPath objects in `paths`, if any (see `__init__()`). 431 432 `encoding` 433 : the text encoding that should be used to decode bytes-like objects in `paths`, if any (see `__init__()`). 434 435 Returns 436 ------- 437 a dictionary that maps the common base path of each partition to a list of relative paths 438 439 Raises 440 ------ 441 `ValueError` 442 if any of the GPaths are invalid 443 444 Examples 445 -------- 446 ```python 447 partitions = GPath.partition("/usr/bin", "/usr/local/bin", "../../doc", "C:/Windows", "C:/Program Files") 448 449 assert partitions == { 450 GPath("/usr") : [GPath("bin"), GPath("local")], 451 GPath("../../doc") : [GPath("")], 452 GPath("C:/") : [GPath("Windows"), GPath("Program Files")], 453 } 454 ``` 455 """ 456 flattened_paths: list[GPathLike] = [] 457 for path_or_list in paths: 458 if _is_gpathlike(path_or_list): 459 flattened_paths.append(path_or_list) 460 else: 461 flattened_paths.extend(path_or_list) 462 gpaths = [path if isinstance(path, GPath) else GPath(path, encoding=encoding, platform=platform) for path in flattened_paths] 463 464 partition_map = {} 465 if len(gpaths) > 0: 466 if allow_parents == True: 467 partition_map[gpaths[0]] = [] 468 else: 469 partition_map[gpaths[0]] = [gpaths[0]] 470 471 for path in gpaths[1:]: 472 partition_found = False 473 for partition in partition_map: 474 candidate_common = partition.common_with(path, allow_current=allow_current, allow_parents=allow_parents) 475 if candidate_common is not None: 476 partition_found = True 477 if candidate_common != partition: 478 partition_map[candidate_common] = partition_map[partition] 479 del partition_map[partition] 480 if allow_parents == False: 481 partition_map[candidate_common].append(path) 482 break 483 if not partition_found: 484 if allow_parents == True: 485 partition_map[path] = [] 486 else: 487 partition_map[path] = [path] 488 489 for partition, path_list in partition_map.items(): 490 partition_map[partition] = [path.subpath_from(partition) for path in path_list] 491 492 return partition_map
Partition a collection of paths based on shared common base paths such that each path belongs to one partition.
For each partition, return a list of relative paths from the base path of that partition to each corresponding input path within that partition, unless allow_parents
is True (see below). If the input collection is ordered, the output order is preserved within each partition. If the input collection contains duplicates, the corresponding output lists will as well.
The number of partitions is minimised by merging partitions as much as possible, so that each partition represents the highest possible level base path. Two partitions can no longer be merged when there is no common base path between them, as determined by common_with()
. This method takes the same optional arguments as common_with()
, with the same default values.
Parameters
paths: Iterable[GPath | str | bytes | os.PathLike]
or *paths: GPath | str | bytes | os.PathLike
: the paths to be partitioned, which can be given as either a list-like object or as variadic arguments
allow_current
: whether non-parent relative paths with no shared components should be considered to have a common base path (see common_with()
)
allow_parents
: whether paths that are relative to different levels of parent directories should be considered to have a common base path (see common_with()
). Warning: when set to True, the output lists for each partition are invalidated, and explicitly set to empty. This is because it is not possible in general to obtain a relative path from the base path to its members if the base path is a parent directory of a higher level than the member (see relpath_from()
). This option should be True if and only if the list of members in each partition are not of interest; in most cases False is more appropriate.
platform
: the originating platform that should be assumed when interpreting non-GPath objects in paths
, if any (see __init__()
).
encoding
: the text encoding that should be used to decode bytes-like objects in paths
, if any (see __init__()
).
Returns
a dictionary that maps the common base path of each partition to a list of relative paths
Raises
ValueError
if any of the GPaths are invalid
Examples
partitions = GPath.partition("/usr/bin", "/usr/local/bin", "../../doc", "C:/Windows", "C:/Program Files")
assert partitions == {
GPath("/usr") : [GPath("bin"), GPath("local")],
GPath("../../doc") : [GPath("")],
GPath("C:/") : [GPath("Windows"), GPath("Program Files")],
}
503 @staticmethod 504 def join(*paths, platform: Optional[Union[str, Platform]]=None, encoding: Optional[str]=None) -> GPath: 505 """ 506 Join a sequence of paths into a single path. Apart from the first item in the sequence, all subsequent paths should be relative paths and any absolute paths will be ignored. 507 508 Parameters 509 ---------- 510 `paths`: `Sequence[GPath | str | bytes | os.PathLike]` or `*paths: GPath | str | bytes | os.PathLike` 511 : the paths to be combined, which can be given as either a list-like object or as variadic arguments 512 513 `platform` 514 : the originating platform that should be assumed when interpreting non-GPath objects in `paths`, if any (see `__init__()`). 515 516 `encoding` 517 : the text encoding that should be used to decode bytes-like objects in `paths`, if any (see `__init__()`). 518 519 Returns 520 ------- 521 the combined path 522 523 Raises 524 ------ 525 `ValueError` if any of the GPaths are invalid 526 527 Examples 528 -------- 529 ```python 530 GPath.join("usr", "local", "bin") # GPath("usr/local/bin") 531 GPath.join("/usr/local/bin", "../../bin") # GPath("/usr/bin") 532 GPath.join("C:/", "Windows") # GPath("C:/Windows") 533 ``` 534 """ 535 flattened_paths: list[GPathLike] = [] 536 for path_or_list in paths: 537 if _is_gpathlike(path_or_list): 538 flattened_paths.append(path_or_list) 539 else: 540 flattened_paths.extend(path_or_list) 541 542 if len(flattened_paths) == 0: 543 return GPath(encoding=encoding, platform=platform) 544 545 combined_path = flattened_paths[0] 546 if not isinstance(combined_path, GPath): 547 combined_path = GPath(combined_path, encoding=encoding, platform=platform) 548 for path in flattened_paths[1:]: 549 combined_path = combined_path + path 550 551 return combined_path
Join a sequence of paths into a single path. Apart from the first item in the sequence, all subsequent paths should be relative paths and any absolute paths will be ignored.
Parameters
paths
: Sequence[GPath | str | bytes | os.PathLike]
or *paths: GPath | str | bytes | os.PathLike
: the paths to be combined, which can be given as either a list-like object or as variadic arguments
platform
: the originating platform that should be assumed when interpreting non-GPath objects in paths
, if any (see __init__()
).
encoding
: the text encoding that should be used to decode bytes-like objects in paths
, if any (see __init__()
).
Returns
the combined path
Raises
ValueError
if any of the GPaths are invalid
Examples
GPath.join("usr", "local", "bin") # GPath("usr/local/bin")
GPath.join("/usr/local/bin", "../../bin") # GPath("/usr/bin")
GPath.join("C:/", "Windows") # GPath("C:/Windows")
554 def as_relative(self, parent_level: Optional[int]=None) -> GPath: 555 """ 556 Convert the path to a relative path and return a new copy. 557 558 Parameters 559 ---------- 560 `parent_level` 561 : the number of levels of parent directories that the returned path should be relative to, which may be 0. If set to None, the returned path will have the same parent level as the current path if it is currently a relative path, or have no parent level (i.e. 0) otherwise. 562 563 Raises 564 ------ 565 `TypeError` if `parent_level` is not a valid type 566 567 Examples 568 -------- 569 ```python 570 GPath("/usr/bin").as_relative() # GPath("usr/bin") 571 GPath("C:/Windows").as_relative() # GPath("C:Windows") 572 GPath("../Documents").as_relative() # GPath("../Documents") 573 ``` 574 """ 575 576 new_path = GPath(self) 577 new_path._root = False 578 if parent_level is None: 579 pass 580 elif isinstance(parent_level, int): 581 new_path._parent_level = parent_level 582 else: 583 raise TypeError(f"parent_level must be an int: {parent_level} ({type(parent_level)})") 584 585 return new_path
Convert the path to a relative path and return a new copy.
Parameters
parent_level
: the number of levels of parent directories that the returned path should be relative to, which may be 0. If set to None, the returned path will have the same parent level as the current path if it is currently a relative path, or have no parent level (i.e. 0) otherwise.
Raises
TypeError
if parent_level
is not a valid type
Examples
GPath("/usr/bin").as_relative() # GPath("usr/bin")
GPath("C:/Windows").as_relative() # GPath("C:Windows")
GPath("../Documents").as_relative() # GPath("../Documents")
588 def as_absolute(self) -> GPath: 589 """ 590 Convert the path to an absolute path and return a new copy. 591 592 Any parent directory that the path is relative to will be removed. If the path is already absolute, an identical copy is returned. 593 594 Examples 595 -------- 596 ```python 597 GPath("usr/bin").as_absolute() # GPath("/usr/bin") 598 GPath("../Documents").as_absolute() # GPath("/Documents") 599 GPath("C:Windows").as_absolute() # GPath("C:/Windows") 600 ``` 601 """ 602 new_path = GPath(self) 603 new_path._root = True 604 new_path._parent_level = 0 605 return new_path
Convert the path to an absolute path and return a new copy.
Any parent directory that the path is relative to will be removed. If the path is already absolute, an identical copy is returned.
Examples
GPath("usr/bin").as_absolute() # GPath("/usr/bin")
GPath("../Documents").as_absolute() # GPath("/Documents")
GPath("C:Windows").as_absolute() # GPath("C:/Windows")
608 def with_drive(self, drive: Union[str, bytes, None]=None) -> GPath: 609 """ 610 Return a new copy of the path with the drive set to `drive`. 611 612 If `drive` is `""` or None, this would be equivalent to `without_drive()`. 613 614 Parameters 615 ---------- 616 `drive` 617 : the drive for the returned path, or either `""` or None if the returned path should have no drive 618 619 Returns 620 ------- 621 `GPath` 622 : a new path with the given drive 623 624 Raises 625 ------ 626 - `TypeError` if `drive` is not a valid type 627 - `ValueError` if `drive` has more than one character 628 629 Examples 630 -------- 631 ```python 632 GPath("C:/Windows").with_drive() # GPath("/Windows") 633 GPath("C:/Windows").with_drive("D") # GPath("D:/Windows") 634 GPath("/Windows").with_drive("C") # GPath("C:/Windows") 635 ``` 636 """ 637 if drive is None: 638 drive = "" 639 elif isinstance(drive, bytes): 640 if self._encoding is None: 641 drive = drive.decode(DEFAULT_ENCODING) 642 else: 643 drive = drive.decode(self._encoding) 644 elif isinstance(drive, str): 645 pass 646 else: 647 raise TypeError(f"drive must be a str or bytes object: {drive} ({type(drive)})") 648 649 if len(drive) > 1: 650 raise ValueError(f"drive can only be a single character, an empty string or None: {drive}") 651 652 new_path = GPath(self) 653 new_path._drive = drive 654 return new_path
Return a new copy of the path with the drive set to drive
.
If drive
is ""
or None, this would be equivalent to without_drive()
.
Parameters
drive
: the drive for the returned path, or either ""
or None if the returned path should have no drive
Returns
GPath
: a new path with the given drive
Raises
TypeError
ifdrive
is not a valid typeValueError
ifdrive
has more than one character
Examples
GPath("C:/Windows").with_drive() # GPath("/Windows")
GPath("C:/Windows").with_drive("D") # GPath("D:/Windows")
GPath("/Windows").with_drive("C") # GPath("C:/Windows")
657 def without_drive(self) -> GPath: 658 """ 659 Return a new copy of the path without a drive. 660 661 Equivalent to `with_drive("")` or `with_drive(None)`. 662 663 Returns 664 ------- 665 `GPath` 666 : a new path without a drive 667 668 Examples 669 -------- 670 ```python 671 GPath("C:/Windows").without_drive() # GPath("/Windows") 672 ``` 673 """ 674 return self.with_drive(None)
Return a new copy of the path without a drive.
Equivalent to with_drive("")
or with_drive(None)
.
Returns
GPath
: a new path without a drive
Examples
GPath("C:/Windows").without_drive() # GPath("/Windows")
677 def common_with(self, other: GPathLike, allow_current: bool=True, allow_parents: bool=False) -> Optional[GPath]: 678 """ 679 Find the longest common base path shared between `self` and `other`, or return None if no such path exists. 680 681 A common base path might not exist if one path is an absolute path while the other is a relative path, or if the two paths are in different filesystems (with different drive names), or in other cases as controlled by the `allow_current` and `allow_parents` options. 682 683 If using the default options of `allow_current=True` and `allow_parent=False`, the binary operator for bitwise-and can be used: `__and__()` (usage: <code><var>g1</var> & <var>g2</var></code>). 684 685 Parameters 686 ---------- 687 `other` 688 : the path to compare with 689 690 `allow_current` 691 : whether two non-parent relative paths that do not share any components should be considered to have a common base path, namely the imaginary current working directory. For instance, `GPath("some/rel/path").find_common("another/rel/path")` will return `GPath("")` if set to True, or return None if set to False. 692 693 `allow_parents` 694 : whether two relative paths that are relative to different levels of parent directories should be considered to have a common base path, which is the highest level of parent directory between the two paths. For instance, `GPath("../rel/to/parent").find_common("../../rel/to/grandparent")` will return `GPath("../..")` if set to True, or return None if set to False. **Warning**: when set to True, given a higher level of parent directory as output, it may not be possible to find the relative path to one of the inputs (see `relpath_from()`); in most cases False is more appropriate. 695 696 Returns 697 ------- 698 `GPath` 699 : the longest common base path, which may be empty, if it exists 700 701 `None` 702 : otherwise 703 704 Raises 705 ------ 706 `ValueError` if either `self` or `other` is an invalid GPath 707 708 Examples 709 -------- 710 ```python 711 GPath("/usr/bin").find_common("/usr/local/bin") # GPath("/usr") 712 GPath("C:/Windows/System32").find_common("C:/Program Files") # GPath("C:/") 713 GPath("../Documents").find_common("../Pictures") # GPath("..") 714 ``` 715 """ 716 self._validate() 717 if isinstance(other, GPath): 718 other._validate() 719 else: 720 other = GPath(other, encoding=self._encoding) 721 722 if self._drive != other._drive: 723 return None 724 if self._root != other._root: 725 return None 726 727 if allow_parents: 728 allow_current = True 729 730 parts = [] 731 if self._root: 732 common_path = GPath(self) 733 for part1, part2 in zip(self._parts, other._parts): 734 if part1 == part2: 735 parts.append(part1) 736 else: 737 if self._parent_level != other._parent_level: 738 if not allow_parents: 739 return None 740 741 common_path = GPath(self) 742 common_path._parent_level = max(self._parent_level, other._parent_level) 743 else: 744 common_path = GPath(self) 745 for part1, part2 in zip(self._parts, other._parts): 746 if part1 == part2: 747 parts.append(part1) 748 749 common_path._parts = tuple(parts) 750 751 if not allow_current and not bool(common_path): 752 if common_path != self or common_path != other: 753 return None 754 return common_path
Find the longest common base path shared between self
and other
, or return None if no such path exists.
A common base path might not exist if one path is an absolute path while the other is a relative path, or if the two paths are in different filesystems (with different drive names), or in other cases as controlled by the allow_current
and allow_parents
options.
If using the default options of allow_current=True
and allow_parent=False
, the binary operator for bitwise-and can be used: __and__()
(usage: g1 & g2
).
Parameters
other
: the path to compare with
allow_current
: whether two non-parent relative paths that do not share any components should be considered to have a common base path, namely the imaginary current working directory. For instance, GPath("some/rel/path").find_common("another/rel/path")
will return GPath("")
if set to True, or return None if set to False.
allow_parents
: whether two relative paths that are relative to different levels of parent directories should be considered to have a common base path, which is the highest level of parent directory between the two paths. For instance, GPath("../rel/to/parent").find_common("../../rel/to/grandparent")
will return GPath("../..")
if set to True, or return None if set to False. Warning: when set to True, given a higher level of parent directory as output, it may not be possible to find the relative path to one of the inputs (see relpath_from()
); in most cases False is more appropriate.
Returns
GPath
: the longest common base path, which may be empty, if it exists
None
: otherwise
Raises
ValueError
if either self
or other
is an invalid GPath
Examples
GPath("/usr/bin").find_common("/usr/local/bin") # GPath("/usr")
GPath("C:/Windows/System32").find_common("C:/Program Files") # GPath("C:/")
GPath("../Documents").find_common("../Pictures") # GPath("..")
757 def subpath_from(self, base: GPathLike) -> Optional[GPath]: 758 """ 759 Find the relative subpath from `base` to `self` if possible and if `base` contains `self`, or return None otherwise. 760 761 None will also be returned if there are unknown components in the subpath from `base` to `self`. For instance, if `self` is relative to the parent directory while `base` is relative to the grandparent directory, the path from the grandparent directory `../..` to the parent directory `..` cannot be known. 762 763 Similar to `relpath_from()`, but `self` must be a descendent of `base`. 764 765 Parameters 766 ---------- 767 `base` 768 : the base path that the relative subpath should start from 769 770 Returns 771 ------- 772 `GPath` 773 : relative subpath from `base` to `self`, which may be empty, if it exists 774 775 `None` 776 : otherwise 777 778 Raises 779 ------ 780 `ValueError` if either `self` or `base` is an invalid GPath 781 782 Examples 783 -------- 784 ```python 785 GPath("/usr/local/bin").subpath_from("/usr") # GPath("local/bin") 786 GPath("/usr/bin").subpath_from("/usr/local/bin") # None 787 GPath("/usr/bin").subpath_from("../Documents") # None 788 ``` 789 """ 790 if not isinstance(base, GPath): 791 base = GPath(base, encoding=self._encoding) 792 793 if self.common_with(base, allow_current=True, allow_parents=False) is not None and self in base: 794 # If self._parent_level > base._parent_level, self is not in base, whereas if self._parent_level < base._parent_level, path from base to self's parent cannot be known 795 base_length = len(base._parts) 796 new_path = GPath(self) 797 new_path._parts = self._parts[base_length:] # () when self == base 798 new_path._drive = "" 799 new_path._root = False 800 new_path._parent_level = 0 801 return new_path 802 else: 803 return None
Find the relative subpath from base
to self
if possible and if base
contains self
, or return None otherwise.
None will also be returned if there are unknown components in the subpath from base
to self
. For instance, if self
is relative to the parent directory while base
is relative to the grandparent directory, the path from the grandparent directory ../..
to the parent directory ..
cannot be known.
Similar to relpath_from()
, but self
must be a descendent of base
.
Parameters
base
: the base path that the relative subpath should start from
Returns
GPath
: relative subpath from base
to self
, which may be empty, if it exists
None
: otherwise
Raises
ValueError
if either self
or base
is an invalid GPath
Examples
GPath("/usr/local/bin").subpath_from("/usr") # GPath("local/bin")
GPath("/usr/bin").subpath_from("/usr/local/bin") # None
GPath("/usr/bin").subpath_from("../Documents") # None
806 def relpath_from(self, origin: GPathLike) -> Optional[GPath]: 807 """ 808 Find the relative path from `origin` to `self` if possible, or return None otherwise. 809 810 None will also be returned if there are unknown components in the relative path from `origin` to `self`. For instance, if `self` is relative to the parent directory while `base` base is relative to the grandparent directory, the path from the grandparent directory `../..` to the parent directory `..` cannot be known. 811 812 Similar to `subpath_from()`, but `self` does not need to be a descendent of `origin`. 813 814 Parameters 815 ---------- 816 `origin` 817 : the origin that the relative path should start from 818 819 Returns 820 ------- 821 `GPath` 822 : relative path from `origin` to `self`, which may be empty, if it exists 823 824 `None` 825 : otherwise 826 827 Raises 828 ------ 829 `ValueError` if either `self` or `origin` is an invalid GPath 830 831 Examples 832 -------- 833 ```python 834 GPath("/usr/local/bin").subpath_from("/usr") # GPath("local/bin") 835 GPath("/usr/bin").subpath_from("/usr/local/bin") # GPath("../../bin") 836 GPath("/usr/bin").subpath_from("../Documents") # None 837 ``` 838 """ 839 self._validate() 840 if not isinstance(origin, GPath): 841 origin = GPath(origin, encoding=self._encoding) 842 843 if origin._root: 844 common = self.common_with(origin) 845 if common is None: 846 return None 847 848 new_path = GPath(self) 849 new_path._parent_level = len(origin) - len(common) 850 new_path._parts = self._parts[len(common):] 851 new_path._drive = "" 852 new_path._root = False 853 return new_path 854 855 else: 856 common = self.common_with(origin, allow_current=True, allow_parents=True) 857 if common is None: 858 return None 859 if common._parent_level > self._parent_level: 860 return None # Path from common to self's parent cannot be known 861 862 # common._dotdot == self._dotdot 863 # origin._dotdot <= self._dotdot 864 865 new_path = GPath(self) 866 new_path._drive = "" 867 new_path._root = False 868 if len(common) == 0: 869 if origin._parent_level == self._parent_level: 870 new_path._parent_level = len(origin) 871 else: 872 new_path._parent_level = (common._parent_level - origin._parent_level) + len(origin) 873 new_path._parts = self._parts 874 else: 875 new_path._parent_level = len(origin) - len(common) 876 new_path._parts = self._parts[len(common):] 877 878 return new_path
Find the relative path from origin
to self
if possible, or return None otherwise.
None will also be returned if there are unknown components in the relative path from origin
to self
. For instance, if self
is relative to the parent directory while base
base is relative to the grandparent directory, the path from the grandparent directory ../..
to the parent directory ..
cannot be known.
Similar to subpath_from()
, but self
does not need to be a descendent of origin
.
Parameters
origin
: the origin that the relative path should start from
Returns
GPath
: relative path from origin
to self
, which may be empty, if it exists
None
: otherwise
Raises
ValueError
if either self
or origin
is an invalid GPath
Examples
GPath("/usr/local/bin").subpath_from("/usr") # GPath("local/bin")
GPath("/usr/bin").subpath_from("/usr/local/bin") # GPath("../../bin")
GPath("/usr/bin").subpath_from("../Documents") # None
881 def render(self, platform: Union[str, Platform, None]) -> render.RenderedPath: 882 """ 883 Convert the path to a RenderedPath for printing in a specific target operating system. 884 885 This will convert, and coerce if necessary, the generalised abstract GPath into a platform-specific path which can then be converted to a printable string using <code>str(<var>renderred_path</var>)</code>. The resulting string will be in the format preferred by the target platform. 886 887 If the GPath contains features that the target platform does not support (such as drive name when the target platform is POSIX), and if there are no analogous features in the target platform, they will be dropped in the rendered path. 888 889 The rendered path also implements total ordering with binary comparisons, e.g. <code><var>r1</var> < <var>r2</var></code>, making it useful for sorting and collation. This ordering is done with platform-specific semantics, unlike GPath which does not have meaningful order. 890 891 Parameters 892 ---------- 893 `platform` 894 : the target platform where the path is to be used 895 896 Returns 897 ------- 898 `RenderedPath` 899 : platform-specific path ready for sorting or output 900 901 Examples 902 -------- 903 ```python 904 # Print examples 905 print(GPath("/usr/bin").render('linux')) # /usr/bin 906 print(GPath("/usr/bin").render('windows')) # \\usr\\bin 907 print(GPath("C:/Windows").render('linux')) # /Windows 908 print(GPath("C:/Windows").render('windows')) # C:\\Windows 909 910 # Ordering examples 911 GPath("").render('linux') < GPath("abc").render('linux') # True 912 GPath("abc").render('linux') < GPath("..").render('linux') # True 913 GPath("..").render('linux') < GPath("../..").render('linux') # True 914 GPath("../..").render('linux') < GPath("/").render('linux') # True 915 GPath("/").render('linux') < GPath("C:/").render('linux') # False 916 GPath("/").render('linux') <= GPath("C:/").render('linux') # True 917 GPath("/").render('windows') < GPath("C:/").render('windows') # True 918 ``` 919 """ 920 if platform is None: 921 platform = DEFAULT_PLATFORM 922 elif isinstance(platform, str): 923 platform = Platform.from_str(platform) 924 return render.get_type(platform)(self)
Convert the path to a RenderedPath for printing in a specific target operating system.
This will convert, and coerce if necessary, the generalised abstract GPath into a platform-specific path which can then be converted to a printable string using str(renderred_path)
. The resulting string will be in the format preferred by the target platform.
If the GPath contains features that the target platform does not support (such as drive name when the target platform is POSIX), and if there are no analogous features in the target platform, they will be dropped in the rendered path.
The rendered path also implements total ordering with binary comparisons, e.g. r1 < r2
, making it useful for sorting and collation. This ordering is done with platform-specific semantics, unlike GPath which does not have meaningful order.
Parameters
platform
: the target platform where the path is to be used
Returns
RenderedPath
: platform-specific path ready for sorting or output
Examples
# Print examples
print(GPath("/usr/bin").render('linux')) # /usr/bin
print(GPath("/usr/bin").render('windows')) # \usr\bin
print(GPath("C:/Windows").render('linux')) # /Windows
print(GPath("C:/Windows").render('windows')) # C:\Windows
# Ordering examples
GPath("").render('linux') < GPath("abc").render('linux') # True
GPath("abc").render('linux') < GPath("..").render('linux') # True
GPath("..").render('linux') < GPath("../..").render('linux') # True
GPath("../..").render('linux') < GPath("/").render('linux') # True
GPath("/").render('linux') < GPath("C:/").render('linux') # False
GPath("/").render('linux') <= GPath("C:/").render('linux') # True
GPath("/").render('windows') < GPath("C:/").render('windows') # True
927 def __hash__(self) -> int: 928 """ 929 Calculate hash of the GPath object. 930 931 Usage: <code>hash(<var>g</var>)</code> 932 """ 933 return hash(self._tuple)
Calculate hash of the GPath object.
Usage: hash(g)
936 def __eq__(self, other: GPathLike) -> bool: 937 """ 938 Check if two GPaths are completely identical. 939 940 Always return False if `other` is not a GPath object, even if it is a GPath-like object. 941 942 Usage: <code><var>g1</var> == <var>g2</var></code> 943 944 Examples 945 -------- 946 ```python 947 GPath("/usr/bin") == GPath("/usr/bin") # True 948 GPath("/usr/bin") == GPath("usr/bin") # False 949 GPath("C:/") == GPath("D:/") # False 950 ``` 951 """ 952 if not isinstance(other, GPath): 953 other = GPath(other, encoding=self._encoding) 954 return self._tuple == other._tuple
Check if two GPaths are completely identical.
Always return False if other
is not a GPath object, even if it is a GPath-like object.
Usage: g1 == g2
Examples
GPath("/usr/bin") == GPath("/usr/bin") # True
GPath("/usr/bin") == GPath("usr/bin") # False
GPath("C:/") == GPath("D:/") # False
957 def __bool__(self) -> bool: 958 """ 959 False if `self` is a relative path without any relative components and without a drive, and True otherwise. 960 961 Usage: <code>bool(<var>g</var>)</code>, <code>not <var>g</var></code>, or <code>if <var>g</var>:</code> 962 963 Examples 964 -------- 965 ```python 966 bool(GPath("/")) # True 967 bool(GPath("..")) # True 968 bool(GPath("doc")) # True 969 bool(GPath("")) # False 970 ``` 971 """ 972 return self._root or self._drive != "" or self._parent_level != 0 or len(self._parts) > 0
False if self
is a relative path without any relative components and without a drive, and True otherwise.
Usage: bool(g)
, not g
, or if g:
Examples
bool(GPath("/")) # True
bool(GPath("..")) # True
bool(GPath("doc")) # True
bool(GPath("")) # False
975 def __str__(self) -> str: 976 """ 977 Return a platform-independent string representation of the path. 978 979 Usage: <code>str(<var>g</var>)</code> 980 """ 981 return str(self.render(Platform.GENERIC))
Return a platform-independent string representation of the path.
Usage: str(g)
984 def __repr__(self) -> str: 985 """ 986 Return a string that, when printed, gives the Python code associated with instantiating the GPath object. 987 988 Usage: <code>repr(<var>g</var>)</code> 989 """ 990 if self._platform is None: 991 platform_repr = "" 992 else: 993 platform_repr = f", platform={repr(self._platform)}" 994 995 if self._encoding is None: 996 encoding_repr = "" 997 else: 998 encoding_repr = f", encoding={repr(self._encoding)}" 999 1000 if bool(self): 1001 return f"GPath({repr(str(self))}{platform_repr}{encoding_repr})" 1002 else: 1003 return f"GPath({repr('')}{platform_repr}{encoding_repr})"
Return a string that, when printed, gives the Python code associated with instantiating the GPath object.
Usage: repr(g)
1006 def __len__(self) -> int: 1007 """ 1008 Get the number of named path components, excluding any drive name or parent directories. 1009 1010 Usage: <code>len(<var>g</var>)</code> 1011 1012 Examples 1013 -------- 1014 ```python 1015 len(GPath("/usr/bin")) # 2 1016 len(GPath("/")) # 0 1017 len(GPath("C:/Windows")) # 0 1018 len(GPath("C:/")) # 0 1019 ``` 1020 """ 1021 return len(self._parts)
Get the number of named path components, excluding any drive name or parent directories.
Usage: len(g)
Examples
len(GPath("/usr/bin")) # 2
len(GPath("/")) # 0
len(GPath("C:/Windows")) # 0
len(GPath("C:/")) # 0
1024 def __getitem__(self, index: Union[int, slice]) -> Union[str, list[str]]: 1025 """ 1026 Get a 0-indexed named path component, or a slice of path components, excluding any drive name or parent directories. 1027 1028 Usage: <code><var>g</var>[<var>n</var>]</code>, <code><var>g</var>[<var>start</var>:<var>end</var>]</code>, <code><var>g</var>[<var>start</var>:<var>end</var>:<var>step</var>]</code>, etc. 1029 1030 Examples 1031 -------- 1032 ```python 1033 GPath("/usr/local/bin")[1] # "local" 1034 GPath("/usr/local/bin")[-1] # "bin" 1035 GPath("/usr/local/bin")[1:] # ["local", "bin"] 1036 GPath("/usr/local/bin")[::2] # ["usr", "bin"] 1037 ``` 1038 """ 1039 if isinstance(index, int): 1040 return self._parts[index] 1041 elif isinstance(index, slice): 1042 return list(self._parts[index])
Get a 0-indexed named path component, or a slice of path components, excluding any drive name or parent directories.
Usage: g[n]
, g[start:end]
, g[start:end:step]
, etc.
Examples
GPath("/usr/local/bin")[1] # "local"
GPath("/usr/local/bin")[-1] # "bin"
GPath("/usr/local/bin")[1:] # ["local", "bin"]
GPath("/usr/local/bin")[::2] # ["usr", "bin"]
1045 def __iter__(self) -> Iterator[str]: 1046 """ 1047 Get an iterator through the named path components, excluding any drive name or parent directories. 1048 1049 Usage: <code>iter(<var>g</var>)</code> or <code>for <var>p</var> in <var>g</var>:</code> 1050 """ 1051 return iter(self._parts)
Get an iterator through the named path components, excluding any drive name or parent directories.
Usage: iter(g)
or for p in g:
1054 def __contains__(self, other: GPathLike) -> bool: 1055 """ 1056 Check if the path represented by `self` contains the path represented by `other`; i.e. check if `self` is a parent directory of `other`. 1057 1058 Usage: <code><var>other</var> in <var>self</var></code> 1059 1060 Raises `ValueError` if either GPath is invalid 1061 1062 Examples 1063 -------- 1064 ```python 1065 GPath("/usr/local/bin") in GPath("/usr") # True 1066 GPath("/usr/local/bin") in GPath("/bin") # False 1067 GPath("..") in GPath("../..") # True 1068 GPath("..") in GPath("C:/") # False 1069 ``` 1070 """ 1071 if not isinstance(other, GPath): 1072 other = GPath(other, encoding=self._encoding) 1073 1074 common_path = self.common_with(other, allow_current=True, allow_parents=True) 1075 return common_path is not None and common_path == self
Check if the path represented by self
contains the path represented by other
; i.e. check if self
is a parent directory of other
.
Usage: other in self
Raises ValueError
if either GPath is invalid
Examples
GPath("/usr/local/bin") in GPath("/usr") # True
GPath("/usr/local/bin") in GPath("/bin") # False
GPath("..") in GPath("../..") # True
GPath("..") in GPath("C:/") # False
1078 def __add__(self, other: GPathLike) -> GPath: 1079 """ 1080 Add (concatenate) `other` to the end of `self`, and return a new copy. 1081 1082 If `other` is an absolute path, the returned path will be an absolute path that matches `other`, apart from the drive name. 1083 1084 If `other` has a drive, the returned path will have the same drive as `other`. Otherwise, the returned path will have the same drive as `self`. If neither has a drive, the returned path will not have a drive as well. 1085 1086 Alias: `__truediv__()` 1087 1088 Usage: <code><var>self</var> + <var>other</var></code> or <code><var>self</var> / <var>other</var></code> 1089 1090 Raises `ValueError` if either GPath is invalid 1091 1092 Examples 1093 -------- 1094 ```python 1095 GPath("/usr") + GPath("local/bin") # GPath("/usr/local/bin") 1096 GPath("C:/Windows/System32") + GPath("../SysWOW64") # GPath("C:/Windows/SysWOW64") 1097 GPath("C:/Windows/System32") + GPath("/usr/bin") # GPath("C:/usr/bin") 1098 GPath("..") + GPath("../..") # GPath("../../..") 1099 GPath("..") / GPath("../..") # GPath("../../..") 1100 ``` 1101 """ 1102 if isinstance(other, GPath): 1103 other._validate 1104 else: 1105 other = GPath(other, encoding=self._encoding) 1106 1107 new_path = GPath(self) 1108 if other._root: 1109 new_path._parts = other._parts 1110 new_path._root = other._root 1111 new_path._parent_level = other._parent_level 1112 else: 1113 new_parts = [part for part in self._parts] 1114 for i in range(other._parent_level): 1115 if len(new_parts) > 0: 1116 new_parts.pop() 1117 elif not new_path._root: 1118 new_path._parent_level += 1 1119 else: 1120 pass # parent of directory of root is still root 1121 1122 new_parts.extend(other._parts) 1123 new_path._parts = tuple(new_parts) 1124 1125 if other._drive != "": 1126 new_path._drive = other._drive 1127 1128 return new_path
Add (concatenate) other
to the end of self
, and return a new copy.
If other
is an absolute path, the returned path will be an absolute path that matches other
, apart from the drive name.
If other
has a drive, the returned path will have the same drive as other
. Otherwise, the returned path will have the same drive as self
. If neither has a drive, the returned path will not have a drive as well.
Alias: __truediv__()
Usage: self + other
or self / other
Raises ValueError
if either GPath is invalid
Examples
GPath("/usr") + GPath("local/bin") # GPath("/usr/local/bin")
GPath("C:/Windows/System32") + GPath("../SysWOW64") # GPath("C:/Windows/SysWOW64")
GPath("C:/Windows/System32") + GPath("/usr/bin") # GPath("C:/usr/bin")
GPath("..") + GPath("../..") # GPath("../../..")
GPath("..") / GPath("../..") # GPath("../../..")
1131 def __sub__(self, n: int) -> GPath: 1132 """ 1133 Remove `n` components from the end of the path and return a new copy. 1134 1135 Usage: <code><var>self</var> - <var>n</var></code> 1136 1137 Raises `ValueError` if `self` is an invalid GPath or if `n` is negative 1138 1139 Examples 1140 -------- 1141 ```python 1142 GPath("C:/Windows/System32") - 1 # GPath("C:/Windows") 1143 GPath("/usr/bin") - 2 # GPath("/") 1144 GPath("Documents") - 3 # GPath("..") 1145 GPath("/") - 1 # GPath("/") 1146 ``` 1147 """ 1148 if n < 0: 1149 raise ValueError("cannot subtract a negative number of components from the path: {n}; use __add__() instead") 1150 1151 new_path = GPath(self) 1152 new_parts = [part for part in self._parts] 1153 for i in range(n): 1154 if len(new_parts) > 0: 1155 new_parts.pop() 1156 elif not new_path._root: 1157 new_path._parent_level += 1 1158 else: 1159 pass # removing components from root should still give root 1160 new_path._parts = tuple(new_parts) 1161 return new_path
Remove n
components from the end of the path and return a new copy.
Usage: self - n
Raises ValueError
if self
is an invalid GPath or if n
is negative
Examples
GPath("C:/Windows/System32") - 1 # GPath("C:/Windows")
GPath("/usr/bin") - 2 # GPath("/")
GPath("Documents") - 3 # GPath("..")
GPath("/") - 1 # GPath("/")
1164 def __mul__(self, n: int) -> GPath: 1165 """ 1166 Duplicate the named components of `self` `n` times and return a new path with the duplicated components. 1167 1168 Named components will be duplicated separately from the components representing a parent directory. If `self` is an absolute path, only the relative components will be duplicated. 1169 1170 If `n` is 0, the result is an empty path (either relative or absolute). 1171 1172 Usage: <code><var>self</var> * <var>n</var></code> 1173 1174 Raises `ValueError` if `self` is an invalid GPath or if `n` is negative. 1175 1176 Examples 1177 -------- 1178 ```python 1179 GPath("/usr/bin") * 2 # GPath("/usr/bin/usr/bin") 1180 GPath("../docs") * 2 # GPath("../../docs/docs") 1181 GPath("C:/Windows") * 0 # GPath("C:/") 1182 ``` 1183 """ 1184 if n < 0: 1185 raise ValueError("cannot multiply path by a negative integer: {n}") 1186 new_path = GPath(self) 1187 new_path._parent_level = self._parent_level * n 1188 new_path._parts = self._parts * n 1189 return new_path
Duplicate the named components of self
n
times and return a new path with the duplicated components.
Named components will be duplicated separately from the components representing a parent directory. If self
is an absolute path, only the relative components will be duplicated.
If n
is 0, the result is an empty path (either relative or absolute).
Usage: self * n
Raises ValueError
if self
is an invalid GPath or if n
is negative.
Examples
GPath("/usr/bin") * 2 # GPath("/usr/bin/usr/bin")
GPath("../docs") * 2 # GPath("../../docs/docs")
GPath("C:/Windows") * 0 # GPath("C:/")
1192 def __truediv__(self, other: GPathLike) -> GPath: 1193 """ 1194 Alias of `__add__()`. 1195 1196 Usage: <code><var>self</var> + <var>other</var></code> or <code><var>self</var> / <var>other</var></code> 1197 """ 1198 return self.__add__(other)
Alias of __add__()
.
Usage: self + other
or self / other
1201 def __and__(self, other: GPathLike) -> Union[GPath, None]: 1202 """ 1203 Equivalent to `self.common_with(other)`, using the default options of `common_with()`. 1204 1205 Usage: <code><var>g1</var> & <var>g2</var></code> 1206 """ 1207 return self.common_with(other)
Equivalent to self.common_with(other)
, using the default options of common_with()
.
Usage: g1 & g2
1210 def __lshift__(self, n: int) -> GPath: 1211 """ 1212 Move the imaginary current working directory `n` steps up the filesystem tree. 1213 1214 If `self` is a relative path, remove up to `n` levels of parent directories from the start of the path and return a copy. If it is an absolute path, return a copy of `self` unchanged. 1215 1216 If `n` is negative, this is equivalent to `__rshift__(-n)`. 1217 1218 Usage: <code><var>self</var> << <var>n</var></code> 1219 1220 Raises `ValueError` if `self` is an invalid GPath. 1221 1222 Examples 1223 -------- 1224 ```python 1225 GPath("../SysWOW64/drivers") << 1 # GPath("SysWOW64/drivers") 1226 GPath("../doc") << 2 # GPath("doc") 1227 GPath("/usr/bin") << 2 # GPath("/usr/bin") 1228 ``` 1229 """ 1230 if n < 0: 1231 return self.__rshift__(-1 * n) 1232 new_path = GPath(self) 1233 if not new_path._root: 1234 new_path._parent_level = max(new_path._parent_level - n, 0) 1235 return new_path
Move the imaginary current working directory n
steps up the filesystem tree.
If self
is a relative path, remove up to n
levels of parent directories from the start of the path and return a copy. If it is an absolute path, return a copy of self
unchanged.
If n
is negative, this is equivalent to __rshift__(-n)
.
Usage: self << n
Raises ValueError
if self
is an invalid GPath.
Examples
GPath("../SysWOW64/drivers") << 1 # GPath("SysWOW64/drivers")
GPath("../doc") << 2 # GPath("doc")
GPath("/usr/bin") << 2 # GPath("/usr/bin")
1238 def __rshift__(self, n: int) -> GPath: 1239 """ 1240 Move the imaginary current working directory `n` steps down the filesystem tree. 1241 1242 If `self` is a relative path, add `n` levels of parent directories to the start of the path and return a copy. If it is an absolute path, return a copy of `self` unchanged. 1243 1244 If `n` is negative, this is equivalent to `__lshift__(-n)`. 1245 1246 Usage: <code><var>self</var> >> <var>n</var></code> 1247 1248 Raises `ValueError` if `self` is an invalid GPath 1249 1250 Examples 1251 -------- 1252 ```python 1253 GPath("../SysWOW64/drivers") >> 1 # GPath("../../SysWOW64/drivers") 1254 GPath("/usr/bin") >> 2 # GPath("/usr/bin") 1255 ``` 1256 """ 1257 if n < 0: 1258 return self.__lshift__(-1 * n) 1259 new_path = GPath(self) 1260 if not new_path._root: 1261 new_path._parent_level += n 1262 return new_path
Move the imaginary current working directory n
steps down the filesystem tree.
If self
is a relative path, add n
levels of parent directories to the start of the path and return a copy. If it is an absolute path, return a copy of self
unchanged.
If n
is negative, this is equivalent to __lshift__(-n)
.
Usage: self >> n
Raises ValueError
if self
is an invalid GPath
Examples
GPath("../SysWOW64/drivers") >> 1 # GPath("../../SysWOW64/drivers")
GPath("/usr/bin") >> 2 # GPath("/usr/bin")
Inherited Members
- collections.abc.Hashable
- __subclasshook__
- collections.abc.Iterable
- __class_getitem__
- builtins.object
- __new__
- __getattribute__
- __setattr__
- __delattr__
- __lt__
- __le__
- __ne__
- __gt__
- __ge__
- __reduce_ex__
- __reduce__
- __getstate__
- __init_subclass__
- __format__
- __sizeof__
- __dir__