Skip to content

Commit

Permalink
Sync from upstream.
Browse files Browse the repository at this point in the history
  • Loading branch information
barneygale committed Jan 31, 2024
1 parent d5b2bcb commit 8f3a6f8
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unreleased

- Add ``PurePathBase.full_match()``.
- Revert ``match()`` back to 3.12 behaviour (no recursive wildcards).
- Disallow passing ``bytes`` to initialisers.
- Improve walking and globbing performance.
- Expand test coverage.
- Clarify that we're using the PSF license.
Expand Down
2 changes: 1 addition & 1 deletion cpython
Submodule cpython updated 138 files
73 changes: 37 additions & 36 deletions pathlib_abc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,39 +152,39 @@ class PathModuleBase:
"""

@classmethod
def _unsupported(cls, attr):
raise UnsupportedOperation(f"{cls.__name__}.{attr} is unsupported")
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported"

@property
def sep(self):
"""The character used to separate path components."""
self._unsupported('sep')
raise UnsupportedOperation(self._unsupported_msg('sep'))

def join(self, path, *paths):
"""Join path segments."""
self._unsupported('join()')
raise UnsupportedOperation(self._unsupported_msg('join()'))

def split(self, path):
"""Split the path into a pair (head, tail), where *head* is everything
before the final path separator, and *tail* is everything after.
Either part may be empty.
"""
self._unsupported('split()')
raise UnsupportedOperation(self._unsupported_msg('split()'))

def splitdrive(self, path):
"""Split the path into a 2-item tuple (drive, tail), where *drive* is
a device name or mount point, and *tail* is everything after the
drive. Either part may be empty."""
self._unsupported('splitdrive()')
raise UnsupportedOperation(self._unsupported_msg('splitdrive()'))

def normcase(self, path):
"""Normalize the case of the path."""
self._unsupported('normcase()')
raise UnsupportedOperation(self._unsupported_msg('normcase()'))

def isabs(self, path):
"""Returns whether the path is absolute, i.e. unaffected by the
current directory or drive."""
self._unsupported('isabs()')
raise UnsupportedOperation(self._unsupported_msg('isabs()'))


class PurePathBase(abc.ABC):
Expand All @@ -210,6 +210,9 @@ class PurePathBase(abc.ABC):

def __init__(self, path, *paths):
self._raw_path = self.pathmod.join(path, *paths) if paths else path
if not isinstance(self._raw_path, str):
raise TypeError(
f"path should be a str, not {type(self._raw_path).__name__!r}")
self._resolving = False

def with_segments(self, *pathsegments):
Expand Down Expand Up @@ -302,10 +305,13 @@ def with_suffix(self, suffix):
has no suffix, add given suffix. If the given suffix is an empty
string, remove the suffix from the path.
"""
stem = self.stem
if not suffix:
return self.with_name(self.stem)
return self.with_name(stem)
elif not stem:
raise ValueError(f"{self!r} has an empty name")
elif suffix.startswith('.') and len(suffix) > 1:
return self.with_name(self.stem + suffix)
return self.with_name(stem + suffix)
else:
raise ValueError(f"Invalid suffix {suffix!r}")

Expand All @@ -321,8 +327,6 @@ def relative_to(self, other, *, walk_up=False):
other = self.with_segments(other)
anchor0, parts0 = self._stack
anchor1, parts1 = other._stack
if isinstance(anchor0, str) != isinstance(anchor1, str):
raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types")
if anchor0 != anchor1:
raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors")
while parts0 and parts1 and parts0[-1] == parts1[-1]:
Expand All @@ -346,8 +350,6 @@ def is_relative_to(self, other):
other = self.with_segments(other)
anchor0, parts0 = self._stack
anchor1, parts1 = other._stack
if isinstance(anchor0, str) != isinstance(anchor1, str):
raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types")
if anchor0 != anchor1:
return False
while parts0 and parts1 and parts0[-1] == parts1[-1]:
Expand Down Expand Up @@ -505,16 +507,15 @@ class PathBase(PurePathBase):
_max_symlinks = 40

@classmethod
def _unsupported(cls, method_name):
msg = f"{cls.__name__}.{method_name}() is unsupported"
raise UnsupportedOperation(msg)
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported"

def stat(self, *, follow_symlinks=True):
"""
Return the result of the stat() system call on this path, like
os.stat() does.
"""
self._unsupported("stat")
raise UnsupportedOperation(self._unsupported_msg('stat()'))

def lstat(self):
"""
Expand Down Expand Up @@ -703,7 +704,7 @@ def open(self, mode='r', buffering=-1, encoding=None,
Open the file pointed by this path and return a file object, as
the built-in open() function does.
"""
self._unsupported("open")
raise UnsupportedOperation(self._unsupported_msg('open()'))

def read_bytes(self):
"""
Expand Down Expand Up @@ -744,7 +745,7 @@ def iterdir(self):
The children are yielded in arbitrary order, and the
special entries '.' and '..' are not included.
"""
self._unsupported("iterdir")
raise UnsupportedOperation(self._unsupported_msg('iterdir()'))

def _scandir(self):
# Emulate os.scandir(), which returns an object that can be used as a
Expand Down Expand Up @@ -871,7 +872,7 @@ def absolute(self):
Use resolve() to resolve symlinks and remove '..' segments.
"""
self._unsupported("absolute")
raise UnsupportedOperation(self._unsupported_msg('absolute()'))

@classmethod
def cwd(cls):
Expand All @@ -886,7 +887,7 @@ def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
(as returned by os.path.expanduser)
"""
self._unsupported("expanduser")
raise UnsupportedOperation(self._unsupported_msg('expanduser()'))

@classmethod
def home(cls):
Expand All @@ -898,7 +899,7 @@ def readlink(self):
"""
Return the path to which the symbolic link points.
"""
self._unsupported("readlink")
raise UnsupportedOperation(self._unsupported_msg('readlink()'))
readlink._supported = False

def resolve(self, strict=False):
Expand Down Expand Up @@ -973,27 +974,27 @@ 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.
"""
self._unsupported("symlink_to")
raise UnsupportedOperation(self._unsupported_msg('symlink_to()'))

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.
"""
self._unsupported("hardlink_to")
raise UnsupportedOperation(self._unsupported_msg('hardlink_to()'))

def touch(self, mode=0o666, exist_ok=True):
"""
Create this file with the given access mode, if it doesn't exist.
"""
self._unsupported("touch")
raise UnsupportedOperation(self._unsupported_msg('touch()'))

def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
self._unsupported("mkdir")
raise UnsupportedOperation(self._unsupported_msg('mkdir()'))

def rename(self, target):
"""
Expand All @@ -1005,7 +1006,7 @@ def rename(self, target):
Returns the new Path instance pointing to the target path.
"""
self._unsupported("rename")
raise UnsupportedOperation(self._unsupported_msg('rename()'))

def replace(self, target):
"""
Expand All @@ -1017,13 +1018,13 @@ def replace(self, target):
Returns the new Path instance pointing to the target path.
"""
self._unsupported("replace")
raise UnsupportedOperation(self._unsupported_msg('replace()'))

def chmod(self, mode, *, follow_symlinks=True):
"""
Change the permissions of the path, like os.chmod().
"""
self._unsupported("chmod")
raise UnsupportedOperation(self._unsupported_msg('chmod()'))

def lchmod(self, mode):
"""
Expand All @@ -1037,34 +1038,34 @@ def unlink(self, missing_ok=False):
Remove this file or link.
If the path is a directory, use rmdir() instead.
"""
self._unsupported("unlink")
raise UnsupportedOperation(self._unsupported_msg('unlink()'))

def rmdir(self):
"""
Remove this directory. The directory must be empty.
"""
self._unsupported("rmdir")
raise UnsupportedOperation(self._unsupported_msg('rmdir()'))

def owner(self, *, follow_symlinks=True):
"""
Return the login name of the file owner.
"""
self._unsupported("owner")
raise UnsupportedOperation(self._unsupported_msg('owner()'))

def group(self, *, follow_symlinks=True):
"""
Return the group name of the file gid.
"""
self._unsupported("group")
raise UnsupportedOperation(self._unsupported_msg('group()'))

@classmethod
def from_uri(cls, uri):
"""Return a new path from the given 'file' URI."""
cls._unsupported("from_uri")
raise UnsupportedOperation(cls._unsupported_msg('from_uri()'))

def as_uri(self):
"""Return the path as a URI."""
self._unsupported("as_uri")
raise UnsupportedOperation(self._unsupported_msg('as_uri()'))


PurePathBase.register(pathlib.PurePath)
Expand Down
51 changes: 48 additions & 3 deletions tests/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,31 @@ def test_constructor_common(self):
P('a/b/c')
P('/a/b/c')

def test_bytes(self):
P = self.cls
with self.assertRaises(TypeError):
P(b'a')
with self.assertRaises(TypeError):
P(b'a', 'b')
with self.assertRaises(TypeError):
P('a', b'b')
with self.assertRaises(TypeError):
P('a').joinpath(b'b')
with self.assertRaises(TypeError):
P('a') / b'b'
with self.assertRaises(TypeError):
b'a' / P('b')
with self.assertRaises(TypeError):
P('a').match(b'b')
with self.assertRaises(TypeError):
P('a').relative_to(b'b')
with self.assertRaises(TypeError):
P('a').with_name(b'b')
with self.assertRaises(TypeError):
P('a').with_stem(b'b')
with self.assertRaises(TypeError):
P('a').with_suffix(b'b')

def _check_str_subclass(self, *args):
# Issue #21127: it should be possible to construct a PurePath object
# from a str subclass instance, and it then gets converted to
Expand Down Expand Up @@ -977,9 +1002,8 @@ def test_with_suffix_windows(self):
def test_with_suffix_empty(self):
P = self.cls
# Path doesn't have a "filename" component.
self.assertEqual(P('').with_suffix('.gz'), P('.gz'))
self.assertEqual(P('.').with_suffix('.gz'), P('..gz'))
self.assertEqual(P('/').with_suffix('.gz'), P('/.gz'))
self.assertRaises(ValueError, P('').with_suffix, '.gz')
self.assertRaises(ValueError, P('/').with_suffix, '.gz')

def test_with_suffix_seps(self):
P = self.cls
Expand Down Expand Up @@ -1766,16 +1790,26 @@ def _check(path, glob, expected):
_check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
_check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."])
_check(p, "dir*/**", [
"dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB",
"dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB",
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
"dirE"])
_check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
"dirC/", "dirC/dirD/", "dirE/"])
_check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
"dirB/linkD/..", "dirA/linkC/linkD/..",
"dirC/..", "dirC/dirD/..", "dirE/.."])
_check(p, "dir*/*/**", [
"dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB",
"dirB/linkD", "dirB/linkD/fileB",
"dirC/dirD", "dirC/dirD/fileD"])
_check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
_check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..",
"dirB/linkD/..", "dirC/dirD/.."])
_check(p, "dir*/**/fileC", ["dirC/fileC"])
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
_check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
_check(p, "*/dirD/**/", ["dirC/dirD/"])

@needs_symlinks
Expand All @@ -1792,12 +1826,20 @@ def _check(path, glob, expected):
_check(p, "*/fileB", ["dirB/fileB"])
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"])
_check(p, "dir*/*/..", ["dirC/dirD/.."])
_check(p, "dir*/**", [
"dirA", "dirA/linkC",
"dirB", "dirB/fileB", "dirB/linkD",
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
"dirE"])
_check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
_check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."])
_check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"])
_check(p, "dir*/*/**/", ["dirC/dirD/"])
_check(p, "dir*/*/**/..", ["dirC/dirD/.."])
_check(p, "dir*/**/fileC", ["dirC/fileC"])
_check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"])
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
_check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
_check(p, "*/dirD/**/", ["dirC/dirD/"])

def test_rglob_common(self):
Expand Down Expand Up @@ -1834,10 +1876,13 @@ def _check(glob, expected):
"dirC/dirD", "dirC/dirD/fileD"])
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
_check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"])
_check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"])
_check(p.rglob("dir*/**/"), ["dirC/dirD/"])
_check(p.rglob("*/*"), ["dirC/dirD/fileD"])
_check(p.rglob("*/"), ["dirC/dirD/"])
_check(p.rglob(""), ["dirC/", "dirC/dirD/"])
_check(p.rglob("**"), [
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"])
_check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
# gh-91616, a re module regression
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
Expand Down

0 comments on commit 8f3a6f8

Please sign in to comment.