mirror of
https://github.com/python/cpython
synced 2024-10-14 08:51: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
740
Lib/pathlib.py
740
Lib/pathlib.py
|
@ -740,177 +740,6 @@ class Path(PurePath):
|
|||
"""
|
||||
__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):
|
||||
"""
|
||||
Return the result of the stat() system call on this path, like
|
||||
|
@ -918,149 +747,6 @@ def stat(self, *, follow_symlinks=True):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
Like stat(), except if the path points to a symlink, the symlink's
|
||||
|
@ -1068,51 +754,6 @@ def lstat(self):
|
|||
"""
|
||||
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
|
||||
|
||||
|
@ -1258,19 +899,123 @@ def is_socket(self):
|
|||
# Non-encodable path
|
||||
return False
|
||||
|
||||
def expanduser(self):
|
||||
""" Return a new path with expanded ~ and ~user constructs
|
||||
(as returned by os.path.expanduser)
|
||||
def samefile(self, other_path):
|
||||
"""Return whether other_path is the same or not as this file
|
||||
(as returned by os.path.samefile()).
|
||||
"""
|
||||
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:])
|
||||
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)
|
||||
|
||||
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):
|
||||
"""Walk the directory tree from this directory, similar to os.walk()."""
|
||||
|
@ -1317,6 +1062,261 @@ def walk(self, top_down=True, on_error=None, follow_symlinks=False):
|
|||
|
||||
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):
|
||||
"""Path subclass for non-Windows systems.
|
||||
|
|
Loading…
Reference in a new issue