| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1afe0e0 commit d00d942
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -530,10 +530,10 @@ Pure paths provide the following methods and properties: | |||
| 530 | 530 | unintended effects. | |
| 531 | 531 | ||
| 532 | 532 | ||
| 533 | - .. method:: PurePath.joinpath(*other) | ||
| 533 | + .. method:: PurePath.joinpath(*pathsegments) | ||
| 534 | 534 | ||
| 535 | 535 | Calling this method is equivalent to combining the path with each of | |
| 536 | - the *other* arguments in turn:: | ||
| 536 | + the given *pathsegments* in turn:: | ||
| 537 | 537 | ||
| 538 | 538 | >>> PurePosixPath('/etc').joinpath('passwd') | |
| 539 | 539 | PurePosixPath('/etc/passwd') | |
@@ -680,6 +680,30 @@ Pure paths provide the following methods and properties: | |||
| 680 | 680 | PureWindowsPath('README') | |
| 681 | 681 | ||
| 682 | 682 | ||
| 683 | + .. method:: PurePath.with_segments(*pathsegments) | ||
| 684 | + | ||
| 685 | + Create a new path object of the same type by combining the given | ||
| 686 | + *pathsegments*. This method is called whenever a derivative path is created, | ||
| 687 | + such as from :attr:`parent` and :meth:`relative_to`. Subclasses may | ||
| 688 | + override this method to pass information to derivative paths, for example:: | ||
| 689 | + | ||
| 690 | + from pathlib import PurePosixPath | ||
| 691 | + | ||
| 692 | + class MyPath(PurePosixPath): | ||
| 693 | + def __init__(self, *pathsegments, session_id): | ||
| 694 | + super().__init__(*pathsegments) | ||
| 695 | + self.session_id = session_id | ||
| 696 | + | ||
| 697 | + def with_segments(self, *pathsegments): | ||
| 698 | + return type(self)(*pathsegments, session_id=self.session_id) | ||
| 699 | + | ||
| 700 | + etc = MyPath('/etc', session_id=42) | ||
| 701 | + hosts = etc / 'hosts' | ||
| 702 | + print(hosts.session_id) # 42 | ||
| 703 | + | ||
| 704 | + .. versionadded:: 3.12 | ||
| 705 | + | ||
| 706 | + | ||
| 683 | 707 | .. _concrete-paths: | |
| 684 | 708 | ||
| 685 | 709 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -348,6 +348,11 @@ inspect | |||
| 348 | 348 | pathlib | |
| 349 | 349 | ------- | |
| 350 | 350 | ||
| 351 | + * Add support for subclassing :class:`pathlib.PurePath` and | ||
| 352 | + :class:`~pathlib.Path`, plus their Posix- and Windows-specific variants. | ||
| 353 | + Subclasses may override the :meth:`~pathlib.PurePath.with_segments` method | ||
| 354 | + to pass information between path instances. | ||
| 355 | + | ||
| 351 | 356 | * Add :meth:`~pathlib.Path.walk` for walking the directory trees and generating | |
| 352 | 357 | all file or directory names within them, similar to :func:`os.walk`. | |
| 353 | 358 | (Contributed by Stanislav Zmiev in :gh:`90385`.) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -204,11 +204,10 @@ def _select_from(self, parent_path, scandir): | |||
| 204 | 204 | class _PathParents(Sequence): | |
| 205 | 205 | """This object provides sequence-like access to the logical ancestors | |
| 206 | 206 | of a path. Don't try to construct it yourself.""" | |
| 207 | - __slots__ = ('_pathcls', '_drv', '_root', '_tail') | ||
| 207 | + __slots__ = ('_path', '_drv', '_root', '_tail') | ||
| 208 | 208 | ||
| 209 | 209 | def __init__(self, path): | |
| 210 | - # We don't store the instance to avoid reference cycles | ||
| 211 | - self._pathcls = type(path) | ||
| 210 | + self._path = path | ||
| 212 | 211 | self._drv = path.drive | |
| 213 | 212 | self._root = path.root | |
| 214 | 213 | self._tail = path._tail | |
@@ -224,11 +223,11 @@ def __getitem__(self, idx): | |||
| 224 | 223 | raise IndexError(idx) | |
| 225 | 224 | if idx < 0: | |
| 226 | 225 | idx += len(self) | |
| 227 | - return self._pathcls._from_parsed_parts(self._drv, self._root, | ||
| 228 | - self._tail[:-idx - 1]) | ||
| 226 | + return self._path._from_parsed_parts(self._drv, self._root, | ||
| 227 | + self._tail[:-idx - 1]) | ||
| 229 | 228 | ||
| 230 | 229 | def __repr__(self): | |
| 231 | - return "<{}.parents>".format(self._pathcls.__name__) | ||
| 230 | + return "<{}.parents>".format(type(self._path).__name__) | ||
| 232 | 231 | ||
| 233 | 232 | ||
| 234 | 233 | class PurePath(object): | |
@@ -316,6 +315,13 @@ def __init__(self, *args): | |||
| 316 | 315 | else: | |
| 317 | 316 | self._raw_path = self._flavour.join(*paths) | |
| 318 | 317 | ||
| 318 | + def with_segments(self, *pathsegments): | ||
| 319 | + """Construct a new path object from any number of path-like objects. | ||
| 320 | + Subclasses may override this method to customize how new path objects | ||
| 321 | + are created from methods like `iterdir()`. | ||
| 322 | + """ | ||
| 323 | + return type(self)(*pathsegments) | ||
| 324 | + | ||
| 319 | 325 | @classmethod | |
| 320 | 326 | def _parse_path(cls, path): | |
| 321 | 327 | if not path: | |
@@ -342,15 +348,14 @@ def _load_parts(self): | |||
| 342 | 348 | self._root = root | |
| 343 | 349 | self._tail_cached = tail | |
| 344 | 350 | ||
| 345 | - @classmethod | ||
| 346 | - def _from_parsed_parts(cls, drv, root, tail): | ||
| 347 | - path = cls._format_parsed_parts(drv, root, tail) | ||
| 348 | - self = cls(path) | ||
| 349 | - self._str = path or '.' | ||
| 350 | - self._drv = drv | ||
| 351 | - self._root = root | ||
| 352 | - self._tail_cached = tail | ||
| 353 | - return self | ||
| 351 | + def _from_parsed_parts(self, drv, root, tail): | ||
| 352 | + path_str = self._format_parsed_parts(drv, root, tail) | ||
| 353 | + path = self.with_segments(path_str) | ||
| 354 | + path._str = path_str or '.' | ||
| 355 | + path._drv = drv | ||
| 356 | + path._root = root | ||
| 357 | + path._tail_cached = tail | ||
| 358 | + return path | ||
| 354 | 359 | ||
| 355 | 360 | @classmethod | |
| 356 | 361 | def _format_parsed_parts(cls, drv, root, tail): | |
@@ -584,8 +589,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): | |||
| 584 | 589 | "scheduled for removal in Python {remove}") | |
| 585 | 590 | warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, | |
| 586 | 591 | remove=(3, 14)) | |
| 587 | - path_cls = type(self) | ||
| 588 | - other = path_cls(other, *_deprecated) | ||
| 592 | + other = self.with_segments(other, *_deprecated) | ||
| 589 | 593 | for step, path in enumerate([other] + list(other.parents)): | |
| 590 | 594 | if self.is_relative_to(path): | |
| 591 | 595 | break | |
@@ -594,7 +598,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): | |||
| 594 | 598 | if step and not walk_up: | |
| 595 | 599 | raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}") | |
| 596 | 600 | parts = ['..'] * step + self._tail[len(path._tail):] | |
| 597 | - return path_cls(*parts) | ||
| 601 | + return self.with_segments(*parts) | ||
| 598 | 602 | ||
| 599 | 603 | def is_relative_to(self, other, /, *_deprecated): | |
| 600 | 604 | """Return True if the path is relative to another path or False. | |
@@ -605,7 +609,7 @@ def is_relative_to(self, other, /, *_deprecated): | |||
| 605 | 609 | "scheduled for removal in Python {remove}") | |
| 606 | 610 | warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", | |
| 607 | 611 | msg, remove=(3, 14)) | |
| 608 | - other = type(self)(other, *_deprecated) | ||
| 612 | + other = self.with_segments(other, *_deprecated) | ||
| 609 | 613 | return other == self or other in self.parents | |
| 610 | 614 | ||
| 611 | 615 | @property | |
@@ -617,13 +621,13 @@ def parts(self): | |||
| 617 | 621 | else: | |
| 618 | 622 | return tuple(self._tail) | |
| 619 | 623 | ||
| 620 | - def joinpath(self, *args): | ||
| 624 | + def joinpath(self, *pathsegments): | ||
| 621 | 625 | """Combine this path with one or several arguments, and return a | |
| 622 | 626 | new path representing either a subpath (if all arguments are relative | |
| 623 | 627 | paths) or a totally different path (if one of the arguments is | |
| 624 | 628 | anchored). | |
| 625 | 629 | """ | |
| 626 | - return self.__class__(self, *args) | ||
| 630 | + return self.with_segments(self, *pathsegments) | ||
| 627 | 631 | ||
| 628 | 632 | def __truediv__(self, key): | |
| 629 | 633 | try: | |
@@ -633,7 +637,7 @@ def __truediv__(self, key): | |||
| 633 | 637 | ||
| 634 | 638 | def __rtruediv__(self, key): | |
| 635 | 639 | try: | |
| 636 | - return type(self)(key, self) | ||
| 640 | + return self.with_segments(key, self) | ||
| 637 | 641 | except TypeError: | |
| 638 | 642 | return NotImplemented | |
| 639 | 643 | ||
@@ -650,6 +654,8 @@ def parent(self): | |||
| 650 | 654 | @property | |
| 651 | 655 | def parents(self): | |
| 652 | 656 | """A sequence of this path's logical parents.""" | |
| 657 | + # The value of this property should not be cached on the path object, | ||
| 658 | + # as doing so would introduce a reference cycle. | ||
| 653 | 659 | return _PathParents(self) | |
| 654 | 660 | ||
| 655 | 661 | def is_absolute(self): | |
@@ -680,7 +686,7 @@ def match(self, path_pattern): | |||
| 680 | 686 | """ | |
| 681 | 687 | Return True if this path matches the given pattern. | |
| 682 | 688 | """ | |
| 683 | - pat = type(self)(path_pattern) | ||
| 689 | + pat = self.with_segments(path_pattern) | ||
| 684 | 690 | if not pat.parts: | |
| 685 | 691 | raise ValueError("empty pattern") | |
| 686 | 692 | pat_parts = pat._parts_normcase | |
@@ -755,7 +761,7 @@ def _make_child_relpath(self, name): | |||
| 755 | 761 | path_str = f'{path_str}{name}' | |
| 756 | 762 | else: | |
| 757 | 763 | path_str = name | |
| 758 | - path = type(self)(path_str) | ||
| 764 | + path = self.with_segments(path_str) | ||
| 759 | 765 | path._str = path_str | |
| 760 | 766 | path._drv = self.drive | |
| 761 | 767 | path._root = self.root | |
@@ -805,7 +811,7 @@ def samefile(self, other_path): | |||
| 805 | 811 | try: | |
| 806 | 812 | other_st = other_path.stat() | |
| 807 | 813 | except AttributeError: | |
| 808 | - other_st = self.__class__(other_path).stat() | ||
| 814 | + other_st = self.with_segments(other_path).stat() | ||
| 809 | 815 | return self._flavour.samestat(st, other_st) | |
| 810 | 816 | ||
| 811 | 817 | def iterdir(self): | |
@@ -867,7 +873,7 @@ def absolute(self): | |||
| 867 | 873 | cwd = self._flavour.abspath(self.drive) | |
| 868 | 874 | else: | |
| 869 | 875 | cwd = os.getcwd() | |
| 870 | - return type(self)(cwd, self) | ||
| 876 | + return self.with_segments(cwd, self) | ||
| 871 | 877 | ||
| 872 | 878 | def resolve(self, strict=False): | |
| 873 | 879 | """ | |
@@ -885,7 +891,7 @@ def check_eloop(e): | |||
| 885 | 891 | except OSError as e: | |
| 886 | 892 | check_eloop(e) | |
| 887 | 893 | raise | |
| 888 | - p = type(self)(s) | ||
| 894 | + p = self.with_segments(s) | ||
| 889 | 895 | ||
| 890 | 896 | # In non-strict mode, realpath() doesn't raise on symlink loops. | |
| 891 | 897 | # Ensure we get an exception by calling stat() | |
@@ -975,7 +981,7 @@ def readlink(self): | |||
| 975 | 981 | """ | |
| 976 | 982 | if not hasattr(os, "readlink"): | |
| 977 | 983 | raise NotImplementedError("os.readlink() not available on this system") | |
| 978 | - return type(self)(os.readlink(self)) | ||
| 984 | + return self.with_segments(os.readlink(self)) | ||
| 979 | 985 | ||
| 980 | 986 | def touch(self, mode=0o666, exist_ok=True): | |
| 981 | 987 | """ | |
@@ -1064,7 +1070,7 @@ def rename(self, target): | |||
| 1064 | 1070 | Returns the new Path instance pointing to the target path. | |
| 1065 | 1071 | """ | |
| 1066 | 1072 | os.rename(self, target) | |
| 1067 | - return self.__class__(target) | ||
| 1073 | + return self.with_segments(target) | ||
| 1068 | 1074 | ||
| 1069 | 1075 | def replace(self, target): | |
| 1070 | 1076 | """ | |
@@ -1077,7 +1083,7 @@ def replace(self, target): | |||
| 1077 | 1083 | Returns the new Path instance pointing to the target path. | |
| 1078 | 1084 | """ | |
| 1079 | 1085 | os.replace(self, target) | |
| 1080 | - return self.__class__(target) | ||
| 1086 | + return self.with_segments(target) | ||
| 1081 | 1087 | ||
| 1082 | 1088 | def symlink_to(self, target, target_is_directory=False): | |
| 1083 | 1089 | """ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,11 +29,12 @@ | |||
| 29 | 29 | # | |
| 30 | 30 | ||
| 31 | 31 | class _BasePurePathSubclass(object): | |
| 32 | - init_called = False | ||
| 32 | + def __init__(self, *pathsegments, session_id): | ||
| 33 | + super().__init__(*pathsegments) | ||
| 34 | + self.session_id = session_id | ||
| 33 | 35 | ||
| 34 | - def __init__(self, *args): | ||
| 35 | - super().__init__(*args) | ||
| 36 | - self.init_called = True | ||
| 36 | + def with_segments(self, *pathsegments): | ||
| 37 | + return type(self)(*pathsegments, session_id=self.session_id) | ||
| 37 | 38 | ||
| 38 | 39 | ||
| 39 | 40 | class _BasePurePathTest(object): | |
@@ -121,20 +122,21 @@ def test_str_subclass_common(self): | |||
| 121 | 122 | self._check_str_subclass('a/b.txt') | |
| 122 | 123 | self._check_str_subclass('/a/b.txt') | |
| 123 | 124 | ||
| 124 | - def test_init_called_common(self): | ||
| 125 | + def test_with_segments_common(self): | ||
| 125 | 126 | class P(_BasePurePathSubclass, self.cls): | |
| 126 | 127 | pass | |
| 127 | - p = P('foo', 'bar') | ||
| 128 | - self.assertTrue((p / 'foo').init_called) | ||
| 129 | - self.assertTrue(('foo' / p).init_called) | ||
| 130 | - self.assertTrue(p.joinpath('foo').init_called) | ||
| 131 | - self.assertTrue(p.with_name('foo').init_called) | ||
| 132 | - self.assertTrue(p.with_stem('foo').init_called) | ||
| 133 | - self.assertTrue(p.with_suffix('.foo').init_called) | ||
| 134 | - self.assertTrue(p.relative_to('foo').init_called) | ||
| 135 | - self.assertTrue(p.parent.init_called) | ||
| 128 | + p = P('foo', 'bar', session_id=42) | ||
| 129 | + self.assertEqual(42, (p / 'foo').session_id) | ||
| 130 | + self.assertEqual(42, ('foo' / p).session_id) | ||
| 131 | + self.assertEqual(42, p.joinpath('foo').session_id) | ||
| 132 | + self.assertEqual(42, p.with_name('foo').session_id) | ||
| 133 | + self.assertEqual(42, p.with_stem('foo').session_id) | ||
| 134 | + self.assertEqual(42, p.with_suffix('.foo').session_id) | ||
| 135 | + self.assertEqual(42, p.with_segments('foo').session_id) | ||
| 136 | + self.assertEqual(42, p.relative_to('foo').session_id) | ||
| 137 | + self.assertEqual(42, p.parent.session_id) | ||
| 136 | 138 | for parent in p.parents: | |
| 137 | - self.assertTrue(parent.init_called) | ||
| 139 | + self.assertEqual(42, parent.session_id) | ||
| 138 | 140 | ||
| 139 | 141 | def _get_drive_root_parts(self, parts): | |
| 140 | 142 | path = self.cls(*parts) | |
@@ -1647,6 +1649,26 @@ def test_home(self): | |||
| 1647 | 1649 | env['HOME'] = os.path.join(BASE, 'home') | |
| 1648 | 1650 | self._test_home(self.cls.home()) | |
| 1649 | 1651 | ||
| 1652 | + def test_with_segments(self): | ||
| 1653 | + class P(_BasePurePathSubclass, self.cls): | ||
| 1654 | + pass | ||
| 1655 | + p = P(BASE, session_id=42) | ||
| 1656 | + self.assertEqual(42, p.absolute().session_id) | ||
| 1657 | + self.assertEqual(42, p.resolve().session_id) | ||
| 1658 | + self.assertEqual(42, p.with_segments('~').expanduser().session_id) | ||
| 1659 | + self.assertEqual(42, (p / 'fileA').rename(p / 'fileB').session_id) | ||
| 1660 | + self.assertEqual(42, (p / 'fileB').replace(p / 'fileA').session_id) | ||
| 1661 | + if os_helper.can_symlink(): | ||
| 1662 | + self.assertEqual(42, (p / 'linkA').readlink().session_id) | ||
| 1663 | + for path in p.iterdir(): | ||
| 1664 | + self.assertEqual(42, path.session_id) | ||
| 1665 | + for path in p.glob('*'): | ||
| 1666 | + self.assertEqual(42, path.session_id) | ||
| 1667 | + for path in p.rglob('*'): | ||
| 1668 | + self.assertEqual(42, path.session_id) | ||
| 1669 | + for dirpath, dirnames, filenames in p.walk(): | ||
| 1670 | + self.assertEqual(42, dirpath.session_id) | ||
| 1671 | + | ||
| 1650 | 1672 | def test_samefile(self): | |
| 1651 | 1673 | fileA_path = os.path.join(BASE, 'fileA') | |
| 1652 | 1674 | fileB_path = os.path.join(BASE, 'dirB', 'fileB') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + Add :meth:`pathlib.PurePath.with_segments`, which creates a path object from | ||
| 2 | + arguments. This method is called whenever a derivative path is created, such | ||
| 3 | + as from :attr:`pathlib.PurePath.parent`. Subclasses may override this method | ||
| 4 | + to share information between path objects. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments