mirror of
https://github.com/python/cpython.git
synced 2025-07-18 08:45:20 +00:00
GH-89812: Churn pathlib.Path
methods (GH-104243)
Re-arrange `pathlib.Path` methods in source code. No other changes. The methods are arranged as follows: 1. `stat()` and dependants (`exists()`, `is_dir()`, etc) 2. `open()` and dependants (`read_text()`, `write_bytes()`, etc) 3. `iterdir()` and dependants (`glob()`, `walk()`, etc) 4. All other `Path` methods This patch prepares the ground for a new `_AbstractPath` class, which will support the methods in groups 1, 2 and 3 above. By churning the methods here, subsequent patches will be easier to review and less likely to break things.
This commit is contained in:
parent
2c2dc61e8d
commit
e8d77b03e0
1 changed files with 370 additions and 370 deletions
740
Lib/pathlib.py
740
Lib/pathlib.py
|
@ -740,177 +740,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
if kwargs:
|
|
||||||
msg = ("support for supplying keyword arguments to pathlib.PurePath "
|
|
||||||
"is deprecated and scheduled for removal in Python {remove}")
|
|
||||||
warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14))
|
|
||||||
super().__init__(*args)
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
if cls is Path:
|
|
||||||
cls = WindowsPath if os.name == 'nt' else PosixPath
|
|
||||||
return object.__new__(cls)
|
|
||||||
|
|
||||||
def _make_child_relpath(self, name):
|
|
||||||
path_str = str(self)
|
|
||||||
tail = self._tail
|
|
||||||
if tail:
|
|
||||||
path_str = f'{path_str}{self._flavour.sep}{name}'
|
|
||||||
elif path_str != '.':
|
|
||||||
path_str = f'{path_str}{name}'
|
|
||||||
else:
|
|
||||||
path_str = name
|
|
||||||
path = self.with_segments(path_str)
|
|
||||||
path._str = path_str
|
|
||||||
path._drv = self.drive
|
|
||||||
path._root = self.root
|
|
||||||
path._tail_cached = tail + [name]
|
|
||||||
return path
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
# In previous versions of pathlib, __exit__() marked this path as
|
|
||||||
# closed; subsequent attempts to perform I/O would raise an IOError.
|
|
||||||
# This functionality was never documented, and had the effect of
|
|
||||||
# making Path objects mutable, contrary to PEP 428.
|
|
||||||
# In Python 3.9 __exit__() was made a no-op.
|
|
||||||
# In Python 3.11 __enter__() began emitting DeprecationWarning.
|
|
||||||
# In Python 3.13 __enter__() and __exit__() should be removed.
|
|
||||||
warnings.warn("pathlib.Path.__enter__() is deprecated and scheduled "
|
|
||||||
"for removal in Python 3.13; Path objects as a context "
|
|
||||||
"manager is a no-op",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, t, v, tb):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Public API
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def cwd(cls):
|
|
||||||
"""Return a new path pointing to the current working directory."""
|
|
||||||
# We call 'absolute()' rather than using 'os.getcwd()' directly to
|
|
||||||
# enable users to replace the implementation of 'absolute()' in a
|
|
||||||
# subclass and benefit from the new behaviour here. This works because
|
|
||||||
# os.path.abspath('.') == os.getcwd().
|
|
||||||
return cls().absolute()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def home(cls):
|
|
||||||
"""Return a new path pointing to the user's home directory (as
|
|
||||||
returned by os.path.expanduser('~')).
|
|
||||||
"""
|
|
||||||
return cls("~").expanduser()
|
|
||||||
|
|
||||||
def samefile(self, other_path):
|
|
||||||
"""Return whether other_path is the same or not as this file
|
|
||||||
(as returned by os.path.samefile()).
|
|
||||||
"""
|
|
||||||
st = self.stat()
|
|
||||||
try:
|
|
||||||
other_st = other_path.stat()
|
|
||||||
except AttributeError:
|
|
||||||
other_st = self.with_segments(other_path).stat()
|
|
||||||
return self._flavour.samestat(st, other_st)
|
|
||||||
|
|
||||||
def iterdir(self):
|
|
||||||
"""Yield path objects of the directory contents.
|
|
||||||
|
|
||||||
The children are yielded in arbitrary order, and the
|
|
||||||
special entries '.' and '..' are not included.
|
|
||||||
"""
|
|
||||||
for name in os.listdir(self):
|
|
||||||
yield self._make_child_relpath(name)
|
|
||||||
|
|
||||||
def _scandir(self):
|
|
||||||
# bpo-24132: a future version of pathlib will support subclassing of
|
|
||||||
# pathlib.Path to customize how the filesystem is accessed. This
|
|
||||||
# includes scandir(), which is used to implement glob().
|
|
||||||
return os.scandir(self)
|
|
||||||
|
|
||||||
def glob(self, pattern, *, case_sensitive=None):
|
|
||||||
"""Iterate over this subtree and yield all existing files (of any
|
|
||||||
kind, including directories) matching the given relative pattern.
|
|
||||||
"""
|
|
||||||
sys.audit("pathlib.Path.glob", self, pattern)
|
|
||||||
if not pattern:
|
|
||||||
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
|
|
||||||
drv, root, pattern_parts = self._parse_path(pattern)
|
|
||||||
if drv or root:
|
|
||||||
raise NotImplementedError("Non-relative patterns are unsupported")
|
|
||||||
if pattern[-1] in (self._flavour.sep, self._flavour.altsep):
|
|
||||||
pattern_parts.append('')
|
|
||||||
selector = _make_selector(tuple(pattern_parts), self._flavour, case_sensitive)
|
|
||||||
for p in selector.select_from(self):
|
|
||||||
yield p
|
|
||||||
|
|
||||||
def rglob(self, pattern, *, case_sensitive=None):
|
|
||||||
"""Recursively yield all existing files (of any kind, including
|
|
||||||
directories) matching the given relative pattern, anywhere in
|
|
||||||
this subtree.
|
|
||||||
"""
|
|
||||||
sys.audit("pathlib.Path.rglob", self, pattern)
|
|
||||||
drv, root, pattern_parts = self._parse_path(pattern)
|
|
||||||
if drv or root:
|
|
||||||
raise NotImplementedError("Non-relative patterns are unsupported")
|
|
||||||
if pattern and pattern[-1] in (self._flavour.sep, self._flavour.altsep):
|
|
||||||
pattern_parts.append('')
|
|
||||||
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour, case_sensitive)
|
|
||||||
for p in selector.select_from(self):
|
|
||||||
yield p
|
|
||||||
|
|
||||||
def absolute(self):
|
|
||||||
"""Return an absolute version of this path by prepending the current
|
|
||||||
working directory. No normalization or symlink resolution is performed.
|
|
||||||
|
|
||||||
Use resolve() to get the canonical path to a file.
|
|
||||||
"""
|
|
||||||
if self.is_absolute():
|
|
||||||
return self
|
|
||||||
elif self.drive:
|
|
||||||
# There is a CWD on each drive-letter drive.
|
|
||||||
cwd = self._flavour.abspath(self.drive)
|
|
||||||
else:
|
|
||||||
cwd = os.getcwd()
|
|
||||||
# Fast path for "empty" paths, e.g. Path("."), Path("") or Path().
|
|
||||||
# We pass only one argument to with_segments() to avoid the cost
|
|
||||||
# of joining, and we exploit the fact that getcwd() returns a
|
|
||||||
# fully-normalized string by storing it in _str. This is used to
|
|
||||||
# implement Path.cwd().
|
|
||||||
if not self.root and not self._tail:
|
|
||||||
result = self.with_segments(cwd)
|
|
||||||
result._str = cwd
|
|
||||||
return result
|
|
||||||
return self.with_segments(cwd, self)
|
|
||||||
|
|
||||||
def resolve(self, strict=False):
|
|
||||||
"""
|
|
||||||
Make the path absolute, resolving all symlinks on the way and also
|
|
||||||
normalizing it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def check_eloop(e):
|
|
||||||
winerror = getattr(e, 'winerror', 0)
|
|
||||||
if e.errno == ELOOP or winerror == _WINERROR_CANT_RESOLVE_FILENAME:
|
|
||||||
raise RuntimeError("Symlink loop from %r" % e.filename)
|
|
||||||
|
|
||||||
try:
|
|
||||||
s = self._flavour.realpath(self, strict=strict)
|
|
||||||
except OSError as e:
|
|
||||||
check_eloop(e)
|
|
||||||
raise
|
|
||||||
p = self.with_segments(s)
|
|
||||||
|
|
||||||
# In non-strict mode, realpath() doesn't raise on symlink loops.
|
|
||||||
# Ensure we get an exception by calling stat()
|
|
||||||
if not strict:
|
|
||||||
try:
|
|
||||||
p.stat()
|
|
||||||
except OSError as e:
|
|
||||||
check_eloop(e)
|
|
||||||
return p
|
|
||||||
|
|
||||||
def stat(self, *, follow_symlinks=True):
|
def stat(self, *, follow_symlinks=True):
|
||||||
"""
|
"""
|
||||||
Return the result of the stat() system call on this path, like
|
Return the result of the stat() system call on this path, like
|
||||||
|
@ -918,149 +747,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
return os.stat(self, follow_symlinks=follow_symlinks)
|
return os.stat(self, follow_symlinks=follow_symlinks)
|
||||||
|
|
||||||
def owner(self):
|
|
||||||
"""
|
|
||||||
Return the login name of the file owner.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import pwd
|
|
||||||
return pwd.getpwuid(self.stat().st_uid).pw_name
|
|
||||||
except ImportError:
|
|
||||||
raise NotImplementedError("Path.owner() is unsupported on this system")
|
|
||||||
|
|
||||||
def group(self):
|
|
||||||
"""
|
|
||||||
Return the group name of the file gid.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import grp
|
|
||||||
return grp.getgrgid(self.stat().st_gid).gr_name
|
|
||||||
except ImportError:
|
|
||||||
raise NotImplementedError("Path.group() is unsupported on this system")
|
|
||||||
|
|
||||||
def open(self, mode='r', buffering=-1, encoding=None,
|
|
||||||
errors=None, newline=None):
|
|
||||||
"""
|
|
||||||
Open the file pointed by this path and return a file object, as
|
|
||||||
the built-in open() function does.
|
|
||||||
"""
|
|
||||||
if "b" not in mode:
|
|
||||||
encoding = io.text_encoding(encoding)
|
|
||||||
return io.open(self, mode, buffering, encoding, errors, newline)
|
|
||||||
|
|
||||||
def read_bytes(self):
|
|
||||||
"""
|
|
||||||
Open the file in bytes mode, read it, and close the file.
|
|
||||||
"""
|
|
||||||
with self.open(mode='rb') as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
def read_text(self, encoding=None, errors=None):
|
|
||||||
"""
|
|
||||||
Open the file in text mode, read it, and close the file.
|
|
||||||
"""
|
|
||||||
encoding = io.text_encoding(encoding)
|
|
||||||
with self.open(mode='r', encoding=encoding, errors=errors) as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
def write_bytes(self, data):
|
|
||||||
"""
|
|
||||||
Open the file in bytes mode, write to it, and close the file.
|
|
||||||
"""
|
|
||||||
# type-check for the buffer interface before truncating the file
|
|
||||||
view = memoryview(data)
|
|
||||||
with self.open(mode='wb') as f:
|
|
||||||
return f.write(view)
|
|
||||||
|
|
||||||
def write_text(self, data, encoding=None, errors=None, newline=None):
|
|
||||||
"""
|
|
||||||
Open the file in text mode, write to it, and close the file.
|
|
||||||
"""
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError('data must be str, not %s' %
|
|
||||||
data.__class__.__name__)
|
|
||||||
encoding = io.text_encoding(encoding)
|
|
||||||
with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
|
|
||||||
return f.write(data)
|
|
||||||
|
|
||||||
def readlink(self):
|
|
||||||
"""
|
|
||||||
Return the path to which the symbolic link points.
|
|
||||||
"""
|
|
||||||
if not hasattr(os, "readlink"):
|
|
||||||
raise NotImplementedError("os.readlink() not available on this system")
|
|
||||||
return self.with_segments(os.readlink(self))
|
|
||||||
|
|
||||||
def touch(self, mode=0o666, exist_ok=True):
|
|
||||||
"""
|
|
||||||
Create this file with the given access mode, if it doesn't exist.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if exist_ok:
|
|
||||||
# First try to bump modification time
|
|
||||||
# Implementation note: GNU touch uses the UTIME_NOW option of
|
|
||||||
# the utimensat() / futimens() functions.
|
|
||||||
try:
|
|
||||||
os.utime(self, None)
|
|
||||||
except OSError:
|
|
||||||
# Avoid exception chaining
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
flags = os.O_CREAT | os.O_WRONLY
|
|
||||||
if not exist_ok:
|
|
||||||
flags |= os.O_EXCL
|
|
||||||
fd = os.open(self, flags, mode)
|
|
||||||
os.close(fd)
|
|
||||||
|
|
||||||
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
|
||||||
"""
|
|
||||||
Create a new directory at this given path.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
os.mkdir(self, mode)
|
|
||||||
except FileNotFoundError:
|
|
||||||
if not parents or self.parent == self:
|
|
||||||
raise
|
|
||||||
self.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
self.mkdir(mode, parents=False, exist_ok=exist_ok)
|
|
||||||
except OSError:
|
|
||||||
# Cannot rely on checking for EEXIST, since the operating system
|
|
||||||
# could give priority to other errors like EACCES or EROFS
|
|
||||||
if not exist_ok or not self.is_dir():
|
|
||||||
raise
|
|
||||||
|
|
||||||
def chmod(self, mode, *, follow_symlinks=True):
|
|
||||||
"""
|
|
||||||
Change the permissions of the path, like os.chmod().
|
|
||||||
"""
|
|
||||||
os.chmod(self, mode, follow_symlinks=follow_symlinks)
|
|
||||||
|
|
||||||
def lchmod(self, mode):
|
|
||||||
"""
|
|
||||||
Like chmod(), except if the path points to a symlink, the symlink's
|
|
||||||
permissions are changed, rather than its target's.
|
|
||||||
"""
|
|
||||||
self.chmod(mode, follow_symlinks=False)
|
|
||||||
|
|
||||||
def unlink(self, missing_ok=False):
|
|
||||||
"""
|
|
||||||
Remove this file or link.
|
|
||||||
If the path is a directory, use rmdir() instead.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
os.unlink(self)
|
|
||||||
except FileNotFoundError:
|
|
||||||
if not missing_ok:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def rmdir(self):
|
|
||||||
"""
|
|
||||||
Remove this directory. The directory must be empty.
|
|
||||||
"""
|
|
||||||
os.rmdir(self)
|
|
||||||
|
|
||||||
def lstat(self):
|
def lstat(self):
|
||||||
"""
|
"""
|
||||||
Like stat(), except if the path points to a symlink, the symlink's
|
Like stat(), except if the path points to a symlink, the symlink's
|
||||||
|
@ -1068,51 +754,6 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
return self.stat(follow_symlinks=False)
|
return self.stat(follow_symlinks=False)
|
||||||
|
|
||||||
def rename(self, target):
|
|
||||||
"""
|
|
||||||
Rename this path to the target path.
|
|
||||||
|
|
||||||
The target path may be absolute or relative. Relative paths are
|
|
||||||
interpreted relative to the current working directory, *not* the
|
|
||||||
directory of the Path object.
|
|
||||||
|
|
||||||
Returns the new Path instance pointing to the target path.
|
|
||||||
"""
|
|
||||||
os.rename(self, target)
|
|
||||||
return self.with_segments(target)
|
|
||||||
|
|
||||||
def replace(self, target):
|
|
||||||
"""
|
|
||||||
Rename this path to the target path, overwriting if that path exists.
|
|
||||||
|
|
||||||
The target path may be absolute or relative. Relative paths are
|
|
||||||
interpreted relative to the current working directory, *not* the
|
|
||||||
directory of the Path object.
|
|
||||||
|
|
||||||
Returns the new Path instance pointing to the target path.
|
|
||||||
"""
|
|
||||||
os.replace(self, target)
|
|
||||||
return self.with_segments(target)
|
|
||||||
|
|
||||||
def symlink_to(self, target, target_is_directory=False):
|
|
||||||
"""
|
|
||||||
Make this path a symlink pointing to the target path.
|
|
||||||
Note the order of arguments (link, target) is the reverse of os.symlink.
|
|
||||||
"""
|
|
||||||
if not hasattr(os, "symlink"):
|
|
||||||
raise NotImplementedError("os.symlink() not available on this system")
|
|
||||||
os.symlink(target, self, target_is_directory)
|
|
||||||
|
|
||||||
def hardlink_to(self, target):
|
|
||||||
"""
|
|
||||||
Make this path a hard link pointing to the same file as *target*.
|
|
||||||
|
|
||||||
Note the order of arguments (self, target) is the reverse of os.link's.
|
|
||||||
"""
|
|
||||||
if not hasattr(os, "link"):
|
|
||||||
raise NotImplementedError("os.link() not available on this system")
|
|
||||||
os.link(target, self)
|
|
||||||
|
|
||||||
|
|
||||||
# Convenience functions for querying the stat results
|
# Convenience functions for querying the stat results
|
||||||
|
|
||||||
|
@ -1258,19 +899,123 @@ class Path(PurePath):
|
||||||
# Non-encodable path
|
# Non-encodable path
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def expanduser(self):
|
def samefile(self, other_path):
|
||||||
""" Return a new path with expanded ~ and ~user constructs
|
"""Return whether other_path is the same or not as this file
|
||||||
(as returned by os.path.expanduser)
|
(as returned by os.path.samefile()).
|
||||||
"""
|
"""
|
||||||
if (not (self.drive or self.root) and
|
st = self.stat()
|
||||||
self._tail and self._tail[0][:1] == '~'):
|
try:
|
||||||
homedir = self._flavour.expanduser(self._tail[0])
|
other_st = other_path.stat()
|
||||||
if homedir[:1] == "~":
|
except AttributeError:
|
||||||
raise RuntimeError("Could not determine home directory.")
|
other_st = self.with_segments(other_path).stat()
|
||||||
drv, root, tail = self._parse_path(homedir)
|
return self._flavour.samestat(st, other_st)
|
||||||
return self._from_parsed_parts(drv, root, tail + self._tail[1:])
|
|
||||||
|
|
||||||
return self
|
def open(self, mode='r', buffering=-1, encoding=None,
|
||||||
|
errors=None, newline=None):
|
||||||
|
"""
|
||||||
|
Open the file pointed by this path and return a file object, as
|
||||||
|
the built-in open() function does.
|
||||||
|
"""
|
||||||
|
if "b" not in mode:
|
||||||
|
encoding = io.text_encoding(encoding)
|
||||||
|
return io.open(self, mode, buffering, encoding, errors, newline)
|
||||||
|
|
||||||
|
def read_bytes(self):
|
||||||
|
"""
|
||||||
|
Open the file in bytes mode, read it, and close the file.
|
||||||
|
"""
|
||||||
|
with self.open(mode='rb') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def read_text(self, encoding=None, errors=None):
|
||||||
|
"""
|
||||||
|
Open the file in text mode, read it, and close the file.
|
||||||
|
"""
|
||||||
|
encoding = io.text_encoding(encoding)
|
||||||
|
with self.open(mode='r', encoding=encoding, errors=errors) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def write_bytes(self, data):
|
||||||
|
"""
|
||||||
|
Open the file in bytes mode, write to it, and close the file.
|
||||||
|
"""
|
||||||
|
# type-check for the buffer interface before truncating the file
|
||||||
|
view = memoryview(data)
|
||||||
|
with self.open(mode='wb') as f:
|
||||||
|
return f.write(view)
|
||||||
|
|
||||||
|
def write_text(self, data, encoding=None, errors=None, newline=None):
|
||||||
|
"""
|
||||||
|
Open the file in text mode, write to it, and close the file.
|
||||||
|
"""
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError('data must be str, not %s' %
|
||||||
|
data.__class__.__name__)
|
||||||
|
encoding = io.text_encoding(encoding)
|
||||||
|
with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
|
||||||
|
return f.write(data)
|
||||||
|
|
||||||
|
def iterdir(self):
|
||||||
|
"""Yield path objects of the directory contents.
|
||||||
|
|
||||||
|
The children are yielded in arbitrary order, and the
|
||||||
|
special entries '.' and '..' are not included.
|
||||||
|
"""
|
||||||
|
for name in os.listdir(self):
|
||||||
|
yield self._make_child_relpath(name)
|
||||||
|
|
||||||
|
def _scandir(self):
|
||||||
|
# bpo-24132: a future version of pathlib will support subclassing of
|
||||||
|
# pathlib.Path to customize how the filesystem is accessed. This
|
||||||
|
# includes scandir(), which is used to implement glob().
|
||||||
|
return os.scandir(self)
|
||||||
|
|
||||||
|
def _make_child_relpath(self, name):
|
||||||
|
path_str = str(self)
|
||||||
|
tail = self._tail
|
||||||
|
if tail:
|
||||||
|
path_str = f'{path_str}{self._flavour.sep}{name}'
|
||||||
|
elif path_str != '.':
|
||||||
|
path_str = f'{path_str}{name}'
|
||||||
|
else:
|
||||||
|
path_str = name
|
||||||
|
path = self.with_segments(path_str)
|
||||||
|
path._str = path_str
|
||||||
|
path._drv = self.drive
|
||||||
|
path._root = self.root
|
||||||
|
path._tail_cached = tail + [name]
|
||||||
|
return path
|
||||||
|
|
||||||
|
def glob(self, pattern, *, case_sensitive=None):
|
||||||
|
"""Iterate over this subtree and yield all existing files (of any
|
||||||
|
kind, including directories) matching the given relative pattern.
|
||||||
|
"""
|
||||||
|
sys.audit("pathlib.Path.glob", self, pattern)
|
||||||
|
if not pattern:
|
||||||
|
raise ValueError("Unacceptable pattern: {!r}".format(pattern))
|
||||||
|
drv, root, pattern_parts = self._parse_path(pattern)
|
||||||
|
if drv or root:
|
||||||
|
raise NotImplementedError("Non-relative patterns are unsupported")
|
||||||
|
if pattern[-1] in (self._flavour.sep, self._flavour.altsep):
|
||||||
|
pattern_parts.append('')
|
||||||
|
selector = _make_selector(tuple(pattern_parts), self._flavour, case_sensitive)
|
||||||
|
for p in selector.select_from(self):
|
||||||
|
yield p
|
||||||
|
|
||||||
|
def rglob(self, pattern, *, case_sensitive=None):
|
||||||
|
"""Recursively yield all existing files (of any kind, including
|
||||||
|
directories) matching the given relative pattern, anywhere in
|
||||||
|
this subtree.
|
||||||
|
"""
|
||||||
|
sys.audit("pathlib.Path.rglob", self, pattern)
|
||||||
|
drv, root, pattern_parts = self._parse_path(pattern)
|
||||||
|
if drv or root:
|
||||||
|
raise NotImplementedError("Non-relative patterns are unsupported")
|
||||||
|
if pattern and pattern[-1] in (self._flavour.sep, self._flavour.altsep):
|
||||||
|
pattern_parts.append('')
|
||||||
|
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour, case_sensitive)
|
||||||
|
for p in selector.select_from(self):
|
||||||
|
yield p
|
||||||
|
|
||||||
def walk(self, top_down=True, on_error=None, follow_symlinks=False):
|
def walk(self, top_down=True, on_error=None, follow_symlinks=False):
|
||||||
"""Walk the directory tree from this directory, similar to os.walk()."""
|
"""Walk the directory tree from this directory, similar to os.walk()."""
|
||||||
|
@ -1317,6 +1062,261 @@ class Path(PurePath):
|
||||||
|
|
||||||
paths += [path._make_child_relpath(d) for d in reversed(dirnames)]
|
paths += [path._make_child_relpath(d) for d in reversed(dirnames)]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if kwargs:
|
||||||
|
msg = ("support for supplying keyword arguments to pathlib.PurePath "
|
||||||
|
"is deprecated and scheduled for removal in Python {remove}")
|
||||||
|
warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14))
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls is Path:
|
||||||
|
cls = WindowsPath if os.name == 'nt' else PosixPath
|
||||||
|
return object.__new__(cls)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
# In previous versions of pathlib, __exit__() marked this path as
|
||||||
|
# closed; subsequent attempts to perform I/O would raise an IOError.
|
||||||
|
# This functionality was never documented, and had the effect of
|
||||||
|
# making Path objects mutable, contrary to PEP 428.
|
||||||
|
# In Python 3.9 __exit__() was made a no-op.
|
||||||
|
# In Python 3.11 __enter__() began emitting DeprecationWarning.
|
||||||
|
# In Python 3.13 __enter__() and __exit__() should be removed.
|
||||||
|
warnings.warn("pathlib.Path.__enter__() is deprecated and scheduled "
|
||||||
|
"for removal in Python 3.13; Path objects as a context "
|
||||||
|
"manager is a no-op",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, t, v, tb):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Public API
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cwd(cls):
|
||||||
|
"""Return a new path pointing to the current working directory."""
|
||||||
|
# We call 'absolute()' rather than using 'os.getcwd()' directly to
|
||||||
|
# enable users to replace the implementation of 'absolute()' in a
|
||||||
|
# subclass and benefit from the new behaviour here. This works because
|
||||||
|
# os.path.abspath('.') == os.getcwd().
|
||||||
|
return cls().absolute()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def home(cls):
|
||||||
|
"""Return a new path pointing to the user's home directory (as
|
||||||
|
returned by os.path.expanduser('~')).
|
||||||
|
"""
|
||||||
|
return cls("~").expanduser()
|
||||||
|
|
||||||
|
def absolute(self):
|
||||||
|
"""Return an absolute version of this path by prepending the current
|
||||||
|
working directory. No normalization or symlink resolution is performed.
|
||||||
|
|
||||||
|
Use resolve() to get the canonical path to a file.
|
||||||
|
"""
|
||||||
|
if self.is_absolute():
|
||||||
|
return self
|
||||||
|
elif self.drive:
|
||||||
|
# There is a CWD on each drive-letter drive.
|
||||||
|
cwd = self._flavour.abspath(self.drive)
|
||||||
|
else:
|
||||||
|
cwd = os.getcwd()
|
||||||
|
# Fast path for "empty" paths, e.g. Path("."), Path("") or Path().
|
||||||
|
# We pass only one argument to with_segments() to avoid the cost
|
||||||
|
# of joining, and we exploit the fact that getcwd() returns a
|
||||||
|
# fully-normalized string by storing it in _str. This is used to
|
||||||
|
# implement Path.cwd().
|
||||||
|
if not self.root and not self._tail:
|
||||||
|
result = self.with_segments(cwd)
|
||||||
|
result._str = cwd
|
||||||
|
return result
|
||||||
|
return self.with_segments(cwd, self)
|
||||||
|
|
||||||
|
def resolve(self, strict=False):
|
||||||
|
"""
|
||||||
|
Make the path absolute, resolving all symlinks on the way and also
|
||||||
|
normalizing it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_eloop(e):
|
||||||
|
winerror = getattr(e, 'winerror', 0)
|
||||||
|
if e.errno == ELOOP or winerror == _WINERROR_CANT_RESOLVE_FILENAME:
|
||||||
|
raise RuntimeError("Symlink loop from %r" % e.filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s = self._flavour.realpath(self, strict=strict)
|
||||||
|
except OSError as e:
|
||||||
|
check_eloop(e)
|
||||||
|
raise
|
||||||
|
p = self.with_segments(s)
|
||||||
|
|
||||||
|
# In non-strict mode, realpath() doesn't raise on symlink loops.
|
||||||
|
# Ensure we get an exception by calling stat()
|
||||||
|
if not strict:
|
||||||
|
try:
|
||||||
|
p.stat()
|
||||||
|
except OSError as e:
|
||||||
|
check_eloop(e)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def owner(self):
|
||||||
|
"""
|
||||||
|
Return the login name of the file owner.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import pwd
|
||||||
|
return pwd.getpwuid(self.stat().st_uid).pw_name
|
||||||
|
except ImportError:
|
||||||
|
raise NotImplementedError("Path.owner() is unsupported on this system")
|
||||||
|
|
||||||
|
def group(self):
|
||||||
|
"""
|
||||||
|
Return the group name of the file gid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import grp
|
||||||
|
return grp.getgrgid(self.stat().st_gid).gr_name
|
||||||
|
except ImportError:
|
||||||
|
raise NotImplementedError("Path.group() is unsupported on this system")
|
||||||
|
|
||||||
|
def readlink(self):
|
||||||
|
"""
|
||||||
|
Return the path to which the symbolic link points.
|
||||||
|
"""
|
||||||
|
if not hasattr(os, "readlink"):
|
||||||
|
raise NotImplementedError("os.readlink() not available on this system")
|
||||||
|
return self.with_segments(os.readlink(self))
|
||||||
|
|
||||||
|
def touch(self, mode=0o666, exist_ok=True):
|
||||||
|
"""
|
||||||
|
Create this file with the given access mode, if it doesn't exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if exist_ok:
|
||||||
|
# First try to bump modification time
|
||||||
|
# Implementation note: GNU touch uses the UTIME_NOW option of
|
||||||
|
# the utimensat() / futimens() functions.
|
||||||
|
try:
|
||||||
|
os.utime(self, None)
|
||||||
|
except OSError:
|
||||||
|
# Avoid exception chaining
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
flags = os.O_CREAT | os.O_WRONLY
|
||||||
|
if not exist_ok:
|
||||||
|
flags |= os.O_EXCL
|
||||||
|
fd = os.open(self, flags, mode)
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
||||||
|
"""
|
||||||
|
Create a new directory at this given path.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
os.mkdir(self, mode)
|
||||||
|
except FileNotFoundError:
|
||||||
|
if not parents or self.parent == self:
|
||||||
|
raise
|
||||||
|
self.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.mkdir(mode, parents=False, exist_ok=exist_ok)
|
||||||
|
except OSError:
|
||||||
|
# Cannot rely on checking for EEXIST, since the operating system
|
||||||
|
# could give priority to other errors like EACCES or EROFS
|
||||||
|
if not exist_ok or not self.is_dir():
|
||||||
|
raise
|
||||||
|
|
||||||
|
def chmod(self, mode, *, follow_symlinks=True):
|
||||||
|
"""
|
||||||
|
Change the permissions of the path, like os.chmod().
|
||||||
|
"""
|
||||||
|
os.chmod(self, mode, follow_symlinks=follow_symlinks)
|
||||||
|
|
||||||
|
def lchmod(self, mode):
|
||||||
|
"""
|
||||||
|
Like chmod(), except if the path points to a symlink, the symlink's
|
||||||
|
permissions are changed, rather than its target's.
|
||||||
|
"""
|
||||||
|
self.chmod(mode, follow_symlinks=False)
|
||||||
|
|
||||||
|
def unlink(self, missing_ok=False):
|
||||||
|
"""
|
||||||
|
Remove this file or link.
|
||||||
|
If the path is a directory, use rmdir() instead.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
os.unlink(self)
|
||||||
|
except FileNotFoundError:
|
||||||
|
if not missing_ok:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def rmdir(self):
|
||||||
|
"""
|
||||||
|
Remove this directory. The directory must be empty.
|
||||||
|
"""
|
||||||
|
os.rmdir(self)
|
||||||
|
|
||||||
|
def rename(self, target):
|
||||||
|
"""
|
||||||
|
Rename this path to the target path.
|
||||||
|
|
||||||
|
The target path may be absolute or relative. Relative paths are
|
||||||
|
interpreted relative to the current working directory, *not* the
|
||||||
|
directory of the Path object.
|
||||||
|
|
||||||
|
Returns the new Path instance pointing to the target path.
|
||||||
|
"""
|
||||||
|
os.rename(self, target)
|
||||||
|
return self.with_segments(target)
|
||||||
|
|
||||||
|
def replace(self, target):
|
||||||
|
"""
|
||||||
|
Rename this path to the target path, overwriting if that path exists.
|
||||||
|
|
||||||
|
The target path may be absolute or relative. Relative paths are
|
||||||
|
interpreted relative to the current working directory, *not* the
|
||||||
|
directory of the Path object.
|
||||||
|
|
||||||
|
Returns the new Path instance pointing to the target path.
|
||||||
|
"""
|
||||||
|
os.replace(self, target)
|
||||||
|
return self.with_segments(target)
|
||||||
|
|
||||||
|
def symlink_to(self, target, target_is_directory=False):
|
||||||
|
"""
|
||||||
|
Make this path a symlink pointing to the target path.
|
||||||
|
Note the order of arguments (link, target) is the reverse of os.symlink.
|
||||||
|
"""
|
||||||
|
if not hasattr(os, "symlink"):
|
||||||
|
raise NotImplementedError("os.symlink() not available on this system")
|
||||||
|
os.symlink(target, self, target_is_directory)
|
||||||
|
|
||||||
|
def hardlink_to(self, target):
|
||||||
|
"""
|
||||||
|
Make this path a hard link pointing to the same file as *target*.
|
||||||
|
|
||||||
|
Note the order of arguments (self, target) is the reverse of os.link's.
|
||||||
|
"""
|
||||||
|
if not hasattr(os, "link"):
|
||||||
|
raise NotImplementedError("os.link() not available on this system")
|
||||||
|
os.link(target, self)
|
||||||
|
|
||||||
|
def expanduser(self):
|
||||||
|
""" Return a new path with expanded ~ and ~user constructs
|
||||||
|
(as returned by os.path.expanduser)
|
||||||
|
"""
|
||||||
|
if (not (self.drive or self.root) and
|
||||||
|
self._tail and self._tail[0][:1] == '~'):
|
||||||
|
homedir = self._flavour.expanduser(self._tail[0])
|
||||||
|
if homedir[:1] == "~":
|
||||||
|
raise RuntimeError("Could not determine home directory.")
|
||||||
|
drv, root, tail = self._parse_path(homedir)
|
||||||
|
return self._from_parsed_parts(drv, root, tail + self._tail[1:])
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
class PosixPath(Path, PurePosixPath):
|
class PosixPath(Path, PurePosixPath):
|
||||||
"""Path subclass for non-Windows systems.
|
"""Path subclass for non-Windows systems.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue