"""
import functools
-import posixpath
+from abc import ABC, abstractmethod
from glob import _PathGlobber, _no_recurse_symlinks
+from pathlib import PurePath, Path
from pathlib._os import magic_open, CopyReader, CopyWriter
return path, names
-class JoinablePath:
- """Base class for pure path objects.
+class JoinablePath(ABC):
+ """Abstract base class for pure path objects.
This class *does not* provide several magic methods that are defined in
- its subclass PurePath. They are: __init__, __fspath__, __bytes__,
+ its implementation PurePath. They are: __init__, __fspath__, __bytes__,
__reduce__, __hash__, __eq__, __lt__, __le__, __gt__, __ge__.
"""
-
__slots__ = ()
- parser = posixpath
+ @property
+ @abstractmethod
+ def parser(self):
+ """Implementation of pathlib._types.Parser used for low-level path
+ parsing and manipulation.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
def with_segments(self, *pathsegments):
"""Construct a new path object from any number of path-like objects.
Subclasses may override this method to customize how new path objects
"""
raise NotImplementedError
+ @abstractmethod
def __str__(self):
"""Return the string representation of the path, suitable for
passing to system calls."""
return match(str(self)) is not None
-
class ReadablePath(JoinablePath):
- """Base class for concrete path objects.
+ """Abstract base class for readable path objects.
- This class provides dummy implementations for many methods that derived
- classes can override selectively; the default implementations raise
- NotImplementedError. The most basic methods, such as stat() and open(),
- directly raise NotImplementedError; these basic methods are called by
- other methods such as is_dir() and read_text().
-
- The Path class derives this class to implement local filesystem paths.
- Users may derive their own classes to implement virtual filesystem paths,
- such as paths in archive files or on remote storage systems.
+ The Path class implements this ABC for local filesystem paths. Users may
+ create subclasses to implement readable virtual filesystem paths, such as
+ paths in archive files or on remote storage systems.
"""
__slots__ = ()
@property
+ @abstractmethod
def info(self):
"""
A PathInfo object that exposes the file type and other file attributes
info = self.joinpath().info
return info.is_symlink()
+ @abstractmethod
def __open_rb__(self, buffering=-1):
"""
Open the file pointed to by this path for reading in binary mode and
with magic_open(self, mode='r', encoding=encoding, errors=errors, newline=newline) as f:
return f.read()
+ @abstractmethod
def iterdir(self):
"""Yield path objects of the directory contents.
yield path, dirnames, filenames
paths += [path.joinpath(d) for d in reversed(dirnames)]
+ @abstractmethod
def readlink(self):
"""
Return the path to which the symbolic link points.
class WritablePath(JoinablePath):
+ """Abstract base class for writable path objects.
+
+ The Path class implements this ABC for local filesystem paths. Users may
+ create subclasses to implement writable virtual filesystem paths, such as
+ paths in archive files or on remote storage systems.
+ """
__slots__ = ()
+ @abstractmethod
def symlink_to(self, target, target_is_directory=False):
"""
Make this path a symlink pointing to the target path.
"""
raise NotImplementedError
+ @abstractmethod
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
raise NotImplementedError
+ @abstractmethod
def __open_wb__(self, buffering=-1):
"""
Open the file pointed to by this path for writing in binary mode and
return f.write(data)
_copy_writer = property(CopyWriter)
+
+
+JoinablePath.register(PurePath)
+ReadablePath.register(Path)
+WritablePath.register(Path)
grp = None
from pathlib._os import LocalCopyReader, LocalCopyWriter, PathInfo, DirEntryInfo
-from pathlib._abc import JoinablePath, ReadablePath, WritablePath
__all__ = [
return "<{}.parents>".format(type(self._path).__name__)
-class PurePath(JoinablePath):
+class PurePath:
"""Base class for manipulating paths without I/O.
PurePath represents a filesystem path and offers operations which
tail[-1] = name
return self._from_parsed_parts(self.drive, self.root, tail)
+ def with_stem(self, stem):
+ """Return a new path with the stem changed."""
+ suffix = self.suffix
+ if not suffix:
+ return self.with_name(stem)
+ elif not stem:
+ # If the suffix is non-empty, we can't make the stem empty.
+ raise ValueError(f"{self!r} has a non-empty suffix")
+ else:
+ return self.with_name(stem + suffix)
+
+ def with_suffix(self, suffix):
+ """Return a new path with the file suffix changed. If the path
+ 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 stem:
+ # If the stem is empty, we can't make the suffix non-empty.
+ raise ValueError(f"{self!r} has an empty name")
+ elif suffix and not suffix.startswith('.'):
+ raise ValueError(f"Invalid suffix {suffix!r}")
+ else:
+ return self.with_name(stem + suffix)
+
@property
def stem(self):
"""The final path component, minus its last suffix."""
__slots__ = ()
-class Path(WritablePath, ReadablePath, PurePath):
+class Path(PurePath):
"""PurePath subclass that can make system calls.
Path represents a filesystem path but unlike PurePath, also offers
_copy_reader = property(LocalCopyReader)
_copy_writer = property(LocalCopyWriter)
+ def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
+ preserve_metadata=False):
+ """
+ Recursively copy this file or directory tree to the given destination.
+ """
+ if not hasattr(target, '_copy_writer'):
+ target = self.with_segments(target)
+
+ # Delegate to the target path's CopyWriter object.
+ try:
+ create = target._copy_writer._create
+ except AttributeError:
+ raise TypeError(f"Target is not writable: {target}") from None
+ return create(self, follow_symlinks, dirs_exist_ok, preserve_metadata)
+
+ def copy_into(self, target_dir, *, follow_symlinks=True,
+ dirs_exist_ok=False, preserve_metadata=False):
+ """
+ Copy this file or directory tree into the given existing directory.
+ """
+ name = self.name
+ if not name:
+ raise ValueError(f"{self!r} has an empty name")
+ elif hasattr(target_dir, '_copy_writer'):
+ target = target_dir / name
+ else:
+ target = self.with_segments(target_dir, name)
+ return self.copy(target, follow_symlinks=follow_symlinks,
+ dirs_exist_ok=dirs_exist_ok,
+ preserve_metadata=preserve_metadata)
+
def move(self, target):
"""
Recursively move this file or directory tree to the given destination.
# Tests for the pure classes.
#
-class PurePathTest(test_pathlib_abc.DummyJoinablePathTest):
+class PurePathTest(test_pathlib_abc.JoinablePathTest):
cls = pathlib.PurePath
# Make sure any symbolic links in the base test path are resolved.
# Tests for the concrete classes.
#
-class PathTest(test_pathlib_abc.DummyRWPathTest, PurePathTest):
+class PathTest(test_pathlib_abc.RWPathTest, PurePathTest):
"""Tests for the FS-accessing functionalities of the Path classes."""
cls = pathlib.Path
can_symlink = os_helper.can_symlink()
P('c:/').group()
-class PathWalkTest(test_pathlib_abc.DummyReadablePathWalkTest):
+class PathWalkTest(test_pathlib_abc.ReadablePathWalkTest):
cls = pathlib.Path
base = PathTest.base
can_symlink = PathTest.can_symlink
#
-class JoinablePathTest(unittest.TestCase):
- cls = JoinablePath
-
- def test_magic_methods(self):
- P = self.cls
- self.assertFalse(hasattr(P, '__fspath__'))
- self.assertFalse(hasattr(P, '__bytes__'))
- self.assertIs(P.__reduce__, object.__reduce__)
- self.assertIs(P.__repr__, object.__repr__)
- self.assertIs(P.__hash__, object.__hash__)
- self.assertIs(P.__eq__, object.__eq__)
- self.assertIs(P.__lt__, object.__lt__)
- self.assertIs(P.__le__, object.__le__)
- self.assertIs(P.__gt__, object.__gt__)
- self.assertIs(P.__ge__, object.__ge__)
-
- def test_parser(self):
- self.assertIs(self.cls.parser, posixpath)
-
-
class DummyJoinablePath(JoinablePath):
__slots__ = ('_segments',)
+ parser = posixpath
+
def __init__(self, *segments):
self._segments = segments
return type(self)(*pathsegments)
-class DummyJoinablePathTest(unittest.TestCase):
+class JoinablePathTest(unittest.TestCase):
cls = DummyJoinablePath
# Use a base path that's unrelated to any real filesystem path.
self.sep = self.parser.sep
self.altsep = self.parser.altsep
+ def test_is_joinable(self):
+ p = self.cls(self.base)
+ self.assertIsInstance(p, JoinablePath)
+
def test_parser(self):
self.assertIsInstance(self.cls.parser, _PathParser)
_files = {}
_directories = {}
+ parser = posixpath
def __init__(self, *segments):
super().__init__(*segments)
else:
raise FileNotFoundError(errno.ENOENT, "File not found", path)
+ def readlink(self):
+ raise NotImplementedError
+
class DummyWritablePath(WritablePath, DummyJoinablePath):
__slots__ = ()
self.parent.mkdir(parents=True, exist_ok=True)
self.mkdir(mode, parents=False, exist_ok=exist_ok)
+ def symlink_to(self, target, target_is_directory=False):
+ raise NotImplementedError
+
-class DummyReadablePathTest(DummyJoinablePathTest):
+class ReadablePathTest(JoinablePathTest):
"""Tests for ReadablePathTest methods that use stat(), open() and iterdir()."""
cls = DummyReadablePath
normcase = self.parser.normcase
self.assertEqual(normcase(path_a), normcase(path_b))
+ def test_is_readable(self):
+ p = self.cls(self.base)
+ self.assertIsInstance(p, ReadablePath)
+
def test_exists(self):
P = self.cls
p = P(self.base)
self.assertIs((P / 'linkA\x00').is_file(), False)
-class DummyWritablePathTest(DummyJoinablePathTest):
+class WritablePathTest(JoinablePathTest):
cls = DummyWritablePath
+ def test_is_writable(self):
+ p = self.cls(self.base)
+ self.assertIsInstance(p, WritablePath)
+
class DummyRWPath(DummyWritablePath, DummyReadablePath):
__slots__ = ()
-class DummyRWPathTest(DummyWritablePathTest, DummyReadablePathTest):
+class RWPathTest(WritablePathTest, ReadablePathTest):
cls = DummyRWPath
can_symlink = False
self.assertRaises(ValueError, source.copy_into, target_dir)
-class DummyReadablePathWalkTest(unittest.TestCase):
+class ReadablePathWalkTest(unittest.TestCase):
cls = DummyReadablePath
- base = DummyReadablePathTest.base
+ base = ReadablePathTest.base
can_symlink = False
def setUp(self):