-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
GH-100479: Add optional blueprint
argument to pathlib.PurePath
#100481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
a6fdd0e
b061747
99eb8b1
4759d01
ef6f4c3
595b8ae
dcfe70a
117fe4b
e75dedc
5a6bd3f
f2f1048
3c172fb
4637109
ae48454
e7a8fe3
7f12faa
687c764
d7e326a
a65d499
d4b15d7
958b183
1e10188
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -14,7 +14,6 @@ | |||||||||||||||||||||||||||
import re | ||||||||||||||||||||||||||||
import sys | ||||||||||||||||||||||||||||
import warnings | ||||||||||||||||||||||||||||
from _collections_abc import Sequence | ||||||||||||||||||||||||||||
from errno import ENOENT, ENOTDIR, EBADF, ELOOP | ||||||||||||||||||||||||||||
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO | ||||||||||||||||||||||||||||
from urllib.parse import quote_from_bytes as urlquote_from_bytes | ||||||||||||||||||||||||||||
|
@@ -207,36 +206,6 @@ def _select_from(self, parent_path, is_dir, exists, scandir, normcase): | |||||||||||||||||||||||||||
# Public API | ||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
class _PathParents(Sequence): | ||||||||||||||||||||||||||||
"""This object provides sequence-like access to the logical ancestors | ||||||||||||||||||||||||||||
of a path. Don't try to construct it yourself.""" | ||||||||||||||||||||||||||||
__slots__ = ('_pathcls', '_drv', '_root', '_tail') | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def __init__(self, path): | ||||||||||||||||||||||||||||
# We don't store the instance to avoid reference cycles | ||||||||||||||||||||||||||||
self._pathcls = type(path) | ||||||||||||||||||||||||||||
self._drv = path.drive | ||||||||||||||||||||||||||||
self._root = path.root | ||||||||||||||||||||||||||||
self._tail = path._tail | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def __len__(self): | ||||||||||||||||||||||||||||
return len(self._tail) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def __getitem__(self, idx): | ||||||||||||||||||||||||||||
if isinstance(idx, slice): | ||||||||||||||||||||||||||||
return tuple(self[i] for i in range(*idx.indices(len(self)))) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
if idx >= len(self) or idx < -len(self): | ||||||||||||||||||||||||||||
raise IndexError(idx) | ||||||||||||||||||||||||||||
if idx < 0: | ||||||||||||||||||||||||||||
idx += len(self) | ||||||||||||||||||||||||||||
return self._pathcls._from_parsed_parts(self._drv, self._root, | ||||||||||||||||||||||||||||
self._tail[:-idx - 1]) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def __repr__(self): | ||||||||||||||||||||||||||||
return "<{}.parents>".format(self._pathcls.__name__) | ||||||||||||||||||||||||||||
AlexWaygood marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
class PurePath(object): | ||||||||||||||||||||||||||||
"""Base class for manipulating paths without I/O. | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
@@ -334,15 +303,14 @@ def _load_parts(self): | |||||||||||||||||||||||||||
self._root = root | ||||||||||||||||||||||||||||
self._tail_cached = tail | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@classmethod | ||||||||||||||||||||||||||||
def _from_parsed_parts(cls, drv, root, tail): | ||||||||||||||||||||||||||||
path = cls._format_parsed_parts(drv, root, tail) | ||||||||||||||||||||||||||||
self = cls(path) | ||||||||||||||||||||||||||||
self._str = path or '.' | ||||||||||||||||||||||||||||
self._drv = drv | ||||||||||||||||||||||||||||
self._root = root | ||||||||||||||||||||||||||||
self._tail_cached = tail | ||||||||||||||||||||||||||||
return self | ||||||||||||||||||||||||||||
def _from_parsed_parts(self, drv, root, tail): | ||||||||||||||||||||||||||||
path_str = self._format_parsed_parts(drv, root, tail) | ||||||||||||||||||||||||||||
path = self.makepath(path_str) | ||||||||||||||||||||||||||||
path._str = path_str or '.' | ||||||||||||||||||||||||||||
path._drv = drv | ||||||||||||||||||||||||||||
path._root = root | ||||||||||||||||||||||||||||
path._tail_cached = tail | ||||||||||||||||||||||||||||
return path | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@classmethod | ||||||||||||||||||||||||||||
def _format_parsed_parts(cls, drv, root, tail): | ||||||||||||||||||||||||||||
|
@@ -576,8 +544,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): | |||||||||||||||||||||||||||
"scheduled for removal in Python {remove}") | ||||||||||||||||||||||||||||
warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, | ||||||||||||||||||||||||||||
remove=(3, 14)) | ||||||||||||||||||||||||||||
path_cls = type(self) | ||||||||||||||||||||||||||||
other = path_cls(other, *_deprecated) | ||||||||||||||||||||||||||||
other = self.makepath(other, *_deprecated) | ||||||||||||||||||||||||||||
for step, path in enumerate([other] + list(other.parents)): | ||||||||||||||||||||||||||||
if self.is_relative_to(path): | ||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||
|
@@ -586,7 +553,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): | |||||||||||||||||||||||||||
if step and not walk_up: | ||||||||||||||||||||||||||||
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}") | ||||||||||||||||||||||||||||
parts = ['..'] * step + self._tail[len(path._tail):] | ||||||||||||||||||||||||||||
return path_cls(*parts) | ||||||||||||||||||||||||||||
return self.makepath(*parts) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def is_relative_to(self, other, /, *_deprecated): | ||||||||||||||||||||||||||||
"""Return True if the path is relative to another path or False. | ||||||||||||||||||||||||||||
|
@@ -597,7 +564,7 @@ def is_relative_to(self, other, /, *_deprecated): | |||||||||||||||||||||||||||
"scheduled for removal in Python {remove}") | ||||||||||||||||||||||||||||
warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", | ||||||||||||||||||||||||||||
msg, remove=(3, 14)) | ||||||||||||||||||||||||||||
other = type(self)(other, *_deprecated) | ||||||||||||||||||||||||||||
other = self.makepath(other, *_deprecated) | ||||||||||||||||||||||||||||
return other == self or other in self.parents | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||
|
@@ -609,13 +576,20 @@ def parts(self): | |||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
return tuple(self._tail) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def makepath(self, *args): | ||||||||||||||||||||||||||||
barneygale marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
"""Construct a new path object from any number of path-like objects. | ||||||||||||||||||||||||||||
Subclasses may override this method to customize how new path objects | ||||||||||||||||||||||||||||
are created from methods like `iterdir()`. | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
return type(self)(*args) | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, wouldn't this be simpler as a classmethod?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Making it a classmethod means that you can't share state between paths no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see -- sorry, just getting back to pathlib stuff after a while away! I guess I find the "makepath" name quite unintuitive in that case. The name to me implies that it's "just" an alternative constructor, which I'd expect to be a classmethod. Maybe something like "sproutpath" (feel free to bikeshed the name), which is clearer that it is using the state of the current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What about "newpath"? I think that reinforces the fact that the returned object has completely different segments to the old one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although this method is mostly called under-the-hood, it may also be useful when dealing with subclasses of pathlib classes. For example: from tarfile import TarFile, TarPath
readme_path = TarPath('README.txt', tarfile=TarFile('blah.tar.gz'))
license_path = readme_path.newpath('LICENSE.txt') I mention this just in case it helps us with the naming question. In the above example, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of the options floated so far, I like the "newpath" name the best. But it's your module now, afterall, so I don't feel like I should have the final call here :D You could maybe start a thread on Discord or Discuss if you'd like the opinion of more core devs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
barneygale marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def joinpath(self, *args): | ||||||||||||||||||||||||||||
"""Combine this path with one or several arguments, and return a | ||||||||||||||||||||||||||||
new path representing either a subpath (if all arguments are relative | ||||||||||||||||||||||||||||
paths) or a totally different path (if one of the arguments is | ||||||||||||||||||||||||||||
anchored). | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
return self.__class__(self._raw_path, *args) | ||||||||||||||||||||||||||||
return self.makepath(self._raw_path, *args) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def __truediv__(self, key): | ||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||
|
@@ -625,7 +599,7 @@ def __truediv__(self, key): | |||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def __rtruediv__(self, key): | ||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||
return type(self)(key, self._raw_path) | ||||||||||||||||||||||||||||
return self.makepath(key, self._raw_path) | ||||||||||||||||||||||||||||
except TypeError: | ||||||||||||||||||||||||||||
return NotImplemented | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
@@ -641,8 +615,13 @@ def parent(self): | |||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||
def parents(self): | ||||||||||||||||||||||||||||
"""A sequence of this path's logical parents.""" | ||||||||||||||||||||||||||||
return _PathParents(self) | ||||||||||||||||||||||||||||
"""A tuple of this path's logical parents.""" | ||||||||||||||||||||||||||||
drv = self.drive | ||||||||||||||||||||||||||||
root = self.root | ||||||||||||||||||||||||||||
tail = self._tail | ||||||||||||||||||||||||||||
return tuple( | ||||||||||||||||||||||||||||
self._from_parsed_parts(drv, root, tail[:idx]) | ||||||||||||||||||||||||||||
for idx in reversed(range(len(tail)))) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def is_absolute(self): | ||||||||||||||||||||||||||||
"""True if the path is absolute (has both a root and, if applicable, | ||||||||||||||||||||||||||||
|
@@ -672,7 +651,7 @@ def match(self, path_pattern): | |||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
Return True if this path matches the given pattern. | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
pat = type(self)(path_pattern) | ||||||||||||||||||||||||||||
pat = self.makepath(path_pattern) | ||||||||||||||||||||||||||||
if not pat.parts: | ||||||||||||||||||||||||||||
raise ValueError("empty pattern") | ||||||||||||||||||||||||||||
pat_parts = pat._parts_normcase | ||||||||||||||||||||||||||||
|
@@ -747,7 +726,7 @@ def _make_child_relpath(self, name): | |||||||||||||||||||||||||||
path_str = f'{path_str}{name}' | ||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
path_str = name | ||||||||||||||||||||||||||||
path = type(self)(path_str) | ||||||||||||||||||||||||||||
path = self.makepath(path_str) | ||||||||||||||||||||||||||||
path._str = path_str | ||||||||||||||||||||||||||||
path._drv = self.drive | ||||||||||||||||||||||||||||
path._root = self.root | ||||||||||||||||||||||||||||
|
@@ -797,7 +776,7 @@ def samefile(self, other_path): | |||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||
other_st = other_path.stat() | ||||||||||||||||||||||||||||
except AttributeError: | ||||||||||||||||||||||||||||
other_st = self.__class__(other_path).stat() | ||||||||||||||||||||||||||||
other_st = self.makepath(other_path).stat() | ||||||||||||||||||||||||||||
return self._flavour.samestat(st, other_st) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def iterdir(self): | ||||||||||||||||||||||||||||
|
@@ -859,7 +838,7 @@ def absolute(self): | |||||||||||||||||||||||||||
cwd = self._flavour.abspath(self.drive) | ||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
cwd = os.getcwd() | ||||||||||||||||||||||||||||
return type(self)(cwd, self._raw_path) | ||||||||||||||||||||||||||||
return self.makepath(cwd, self._raw_path) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def resolve(self, strict=False): | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
@@ -877,7 +856,7 @@ def check_eloop(e): | |||||||||||||||||||||||||||
except OSError as e: | ||||||||||||||||||||||||||||
check_eloop(e) | ||||||||||||||||||||||||||||
raise | ||||||||||||||||||||||||||||
p = type(self)(s) | ||||||||||||||||||||||||||||
p = self.makepath(s) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# In non-strict mode, realpath() doesn't raise on symlink loops. | ||||||||||||||||||||||||||||
# Ensure we get an exception by calling stat() | ||||||||||||||||||||||||||||
|
@@ -967,7 +946,7 @@ def readlink(self): | |||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
if not hasattr(os, "readlink"): | ||||||||||||||||||||||||||||
raise NotImplementedError("os.readlink() not available on this system") | ||||||||||||||||||||||||||||
return type(self)(os.readlink(self)) | ||||||||||||||||||||||||||||
return self.makepath(os.readlink(self)) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def touch(self, mode=0o666, exist_ok=True): | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
@@ -1056,7 +1035,7 @@ def rename(self, target): | |||||||||||||||||||||||||||
Returns the new Path instance pointing to the target path. | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
os.rename(self, target) | ||||||||||||||||||||||||||||
return self.__class__(target) | ||||||||||||||||||||||||||||
return self.makepath(target) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def replace(self, target): | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
@@ -1069,7 +1048,7 @@ def replace(self, target): | |||||||||||||||||||||||||||
Returns the new Path instance pointing to the target path. | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
os.replace(self, target) | ||||||||||||||||||||||||||||
return self.__class__(target) | ||||||||||||||||||||||||||||
return self.makepath(target) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def symlink_to(self, target, target_is_directory=False): | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Add :meth:`pathlib.PurePath.makepath`, which creates a path object from | ||
arguments. This method is called whenever a derivative path is created, such | ||
as from :attr:`pathlib.PurePath.parent`. Subclasses may override this method | ||
to pass information to derivative paths. | ||
barneygale marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.