aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2025-03-03 17:56:57 +0000
committerGitHub <noreply@github.com>2025-03-03 17:56:57 +0000
commitd0eb01c9de9a455df36f885506314d8d00645c9f (patch)
tree45774e474db3d21ce35268e4aa49e150739fd72c
parent321bf595121f9327368af8bf5e850d9d145360c2 (diff)
downloadcpython-d0eb01c9de9a455df36f885506314d8d00645c9f.tar.gz
cpython-d0eb01c9de9a455df36f885506314d8d00645c9f.zip
GH-128520: Merge `pathlib._abc` into `pathlib.types` (#130747)
There used to be a meaningful distinction between these modules: `pathlib` imported `pathlib._abc` but not `pathlib.types`. This is no longer the case (neither module is imported), so we move the ABCs as follows: - `pathlib._abc.JoinablePath` --> `pathlib.types._JoinablePath` - `pathlib._abc.ReadablePath` --> `pathlib.types._ReadablePath` - `pathlib._abc.WritablePath` --> `pathlib.types._WritablePath`
-rw-r--r--Lib/pathlib/_abc.py397
-rw-r--r--Lib/pathlib/types.py393
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py4
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py22
4 files changed, 406 insertions, 410 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
deleted file mode 100644
index d9fb018d75f..00000000000
--- a/Lib/pathlib/_abc.py
+++ /dev/null
@@ -1,397 +0,0 @@
-"""
-Abstract base classes for rich path objects.
-
-This module is published as a PyPI package called "pathlib-abc".
-
-This module is also a *PRIVATE* part of the Python standard library, where
-it's developed alongside pathlib. If it finds success and maturity as a PyPI
-package, it could become a public part of the standard library.
-
-Three base classes are defined here -- JoinablePath, ReadablePath and
-WritablePath.
-"""
-
-from abc import ABC, abstractmethod
-from glob import _PathGlobber, _no_recurse_symlinks
-from pathlib import PurePath, Path
-from pathlib._os import magic_open, ensure_distinct_paths, copy_file
-
-
-def _explode_path(path):
- """
- Split the path into a 2-tuple (anchor, parts), where *anchor* is the
- uppermost parent of the path (equivalent to path.parents[-1]), and
- *parts* is a reversed list of parts following the anchor.
- """
- split = path.parser.split
- path = str(path)
- parent, name = split(path)
- names = []
- while path != parent:
- names.append(name)
- path = parent
- parent, name = split(path)
- return path, names
-
-
-class JoinablePath(ABC):
- """Abstract base class for pure path objects.
-
- This class *does not* provide several magic methods that are defined in
- its implementation PurePath. They are: __init__, __fspath__, __bytes__,
- __reduce__, __hash__, __eq__, __lt__, __le__, __gt__, __ge__.
- """
- __slots__ = ()
-
- @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
- are created from methods like `iterdir()`.
- """
- raise NotImplementedError
-
- @abstractmethod
- def __str__(self):
- """Return the string representation of the path, suitable for
- passing to system calls."""
- raise NotImplementedError
-
- @property
- def anchor(self):
- """The concatenation of the drive and root, or ''."""
- return _explode_path(self)[0]
-
- @property
- def name(self):
- """The final path component, if any."""
- return self.parser.split(str(self))[1]
-
- @property
- def suffix(self):
- """
- The final component's last suffix, if any.
-
- This includes the leading period. For example: '.txt'
- """
- return self.parser.splitext(self.name)[1]
-
- @property
- def suffixes(self):
- """
- A list of the final component's suffixes, if any.
-
- These include the leading periods. For example: ['.tar', '.gz']
- """
- split = self.parser.splitext
- stem, suffix = split(self.name)
- suffixes = []
- while suffix:
- suffixes.append(suffix)
- stem, suffix = split(stem)
- return suffixes[::-1]
-
- @property
- def stem(self):
- """The final path component, minus its last suffix."""
- return self.parser.splitext(self.name)[0]
-
- def with_name(self, name):
- """Return a new path with the file name changed."""
- split = self.parser.split
- if split(name)[0]:
- raise ValueError(f"Invalid name {name!r}")
- return self.with_segments(split(str(self))[0], name)
-
- 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 parts(self):
- """An object providing sequence-like access to the
- components in the filesystem path."""
- anchor, parts = _explode_path(self)
- if anchor:
- parts.append(anchor)
- return tuple(reversed(parts))
-
- def joinpath(self, *pathsegments):
- """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.with_segments(str(self), *pathsegments)
-
- def __truediv__(self, key):
- try:
- return self.with_segments(str(self), key)
- except TypeError:
- return NotImplemented
-
- def __rtruediv__(self, key):
- try:
- return self.with_segments(key, str(self))
- except TypeError:
- return NotImplemented
-
- @property
- def parent(self):
- """The logical parent of the path."""
- path = str(self)
- parent = self.parser.split(path)[0]
- if path != parent:
- return self.with_segments(parent)
- return self
-
- @property
- def parents(self):
- """A sequence of this path's logical parents."""
- split = self.parser.split
- path = str(self)
- parent = split(path)[0]
- parents = []
- while path != parent:
- parents.append(self.with_segments(parent))
- path = parent
- parent = split(path)[0]
- return tuple(parents)
-
- def full_match(self, pattern, *, case_sensitive=None):
- """
- Return True if this path matches the given glob-style pattern. The
- pattern is matched against the entire path.
- """
- if not hasattr(pattern, 'with_segments'):
- pattern = self.with_segments(pattern)
- if case_sensitive is None:
- case_sensitive = self.parser.normcase('Aa') == 'Aa'
- globber = _PathGlobber(pattern.parser.sep, case_sensitive, recursive=True)
- match = globber.compile(str(pattern))
- return match(str(self)) is not None
-
-
-class ReadablePath(JoinablePath):
- """Abstract base class for readable path objects.
-
- 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
- of this path.
- """
- raise NotImplementedError
-
- @abstractmethod
- def __open_rb__(self, buffering=-1):
- """
- Open the file pointed to by this path for reading in binary mode and
- return a file object, like open(mode='rb').
- """
- raise NotImplementedError
-
- def read_bytes(self):
- """
- Open the file in bytes mode, read it, and close the file.
- """
- with magic_open(self, mode='rb', buffering=0) as f:
- return f.read()
-
- def read_text(self, encoding=None, errors=None, newline=None):
- """
- Open the file in text mode, read it, and close the file.
- """
- 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.
-
- The children are yielded in arbitrary order, and the
- special entries '.' and '..' are not included.
- """
- raise NotImplementedError
-
- def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
- """Iterate over this subtree and yield all existing files (of any
- kind, including directories) matching the given relative pattern.
- """
- if not hasattr(pattern, 'with_segments'):
- pattern = self.with_segments(pattern)
- anchor, parts = _explode_path(pattern)
- if anchor:
- raise NotImplementedError("Non-relative patterns are unsupported")
- case_sensitive_default = self.parser.normcase('Aa') == 'Aa'
- if case_sensitive is None:
- case_sensitive = case_sensitive_default
- case_pedantic = False
- else:
- case_pedantic = case_sensitive_default != case_sensitive
- recursive = True if recurse_symlinks else _no_recurse_symlinks
- globber = _PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
- select = globber.selector(parts)
- return select(self.joinpath(''))
-
- def walk(self, top_down=True, on_error=None, follow_symlinks=False):
- """Walk the directory tree from this directory, similar to os.walk()."""
- paths = [self]
- while paths:
- path = paths.pop()
- if isinstance(path, tuple):
- yield path
- continue
- dirnames = []
- filenames = []
- if not top_down:
- paths.append((path, dirnames, filenames))
- try:
- for child in path.iterdir():
- if child.info.is_dir(follow_symlinks=follow_symlinks):
- if not top_down:
- paths.append(child)
- dirnames.append(child.name)
- else:
- filenames.append(child.name)
- except OSError as error:
- if on_error is not None:
- on_error(error)
- if not top_down:
- while not isinstance(paths.pop(), tuple):
- pass
- continue
- if top_down:
- 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.
- """
- raise NotImplementedError
-
- def copy(self, target, follow_symlinks=True, preserve_metadata=False):
- """
- Recursively copy this file or directory tree to the given destination.
- """
- if not hasattr(target, 'with_segments'):
- target = self.with_segments(target)
- ensure_distinct_paths(self, target)
- copy_file(self, target, follow_symlinks, preserve_metadata)
- return target.joinpath() # Empty join to ensure fresh metadata.
-
- def copy_into(self, target_dir, *, follow_symlinks=True,
- 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, 'with_segments'):
- target = target_dir / name
- else:
- target = self.with_segments(target_dir, name)
- return self.copy(target, follow_symlinks=follow_symlinks,
- preserve_metadata=preserve_metadata)
-
-
-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.
- Note the order of arguments (link, target) is the reverse of os.symlink.
- """
- raise NotImplementedError
-
- @abstractmethod
- def mkdir(self):
- """
- 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 a file object, like open(mode='wb').
- """
- raise NotImplementedError
-
- 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 magic_open(self, 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__)
- with magic_open(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
- return f.write(data)
-
- def _write_info(self, info, follow_symlinks=True):
- """
- Write the given PathInfo to this path.
- """
- pass
-
-
-JoinablePath.register(PurePath)
-ReadablePath.register(Path)
-WritablePath.register(Path)
diff --git a/Lib/pathlib/types.py b/Lib/pathlib/types.py
index b781264796b..cfdbc89644a 100644
--- a/Lib/pathlib/types.py
+++ b/Lib/pathlib/types.py
@@ -1,9 +1,39 @@
"""
Protocols for supporting classes in pathlib.
"""
+
+# This module also provides abstract base classes for rich path objects.
+# These ABCs are a *private* part of the Python standard library, but they're
+# made available as a PyPI package called "pathlib-abc". It's possible they'll
+# become an official part of the standard library in future.
+#
+# Three ABCs are provided -- _JoinablePath, _ReadablePath and _WritablePath
+
+
+from abc import ABC, abstractmethod
+from glob import _PathGlobber, _no_recurse_symlinks
+from pathlib import PurePath, Path
+from pathlib._os import magic_open, ensure_distinct_paths, copy_file
from typing import Protocol, runtime_checkable
+def _explode_path(path):
+ """
+ Split the path into a 2-tuple (anchor, parts), where *anchor* is the
+ uppermost parent of the path (equivalent to path.parents[-1]), and
+ *parts* is a reversed list of parts following the anchor.
+ """
+ split = path.parser.split
+ path = str(path)
+ parent, name = split(path)
+ names = []
+ while path != parent:
+ names.append(name)
+ path = parent
+ parent, name = split(path)
+ return path, names
+
+
@runtime_checkable
class _PathParser(Protocol):
"""Protocol for path parsers, which do low-level path manipulation.
@@ -28,3 +58,366 @@ class PathInfo(Protocol):
def is_dir(self, *, follow_symlinks: bool = True) -> bool: ...
def is_file(self, *, follow_symlinks: bool = True) -> bool: ...
def is_symlink(self) -> bool: ...
+
+
+class _JoinablePath(ABC):
+ """Abstract base class for pure path objects.
+
+ This class *does not* provide several magic methods that are defined in
+ its implementation PurePath. They are: __init__, __fspath__, __bytes__,
+ __reduce__, __hash__, __eq__, __lt__, __le__, __gt__, __ge__.
+ """
+ __slots__ = ()
+
+ @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
+ are created from methods like `iterdir()`.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def __str__(self):
+ """Return the string representation of the path, suitable for
+ passing to system calls."""
+ raise NotImplementedError
+
+ @property
+ def anchor(self):
+ """The concatenation of the drive and root, or ''."""
+ return _explode_path(self)[0]
+
+ @property
+ def name(self):
+ """The final path component, if any."""
+ return self.parser.split(str(self))[1]
+
+ @property
+ def suffix(self):
+ """
+ The final component's last suffix, if any.
+
+ This includes the leading period. For example: '.txt'
+ """
+ return self.parser.splitext(self.name)[1]
+
+ @property
+ def suffixes(self):
+ """
+ A list of the final component's suffixes, if any.
+
+ These include the leading periods. For example: ['.tar', '.gz']
+ """
+ split = self.parser.splitext
+ stem, suffix = split(self.name)
+ suffixes = []
+ while suffix:
+ suffixes.append(suffix)
+ stem, suffix = split(stem)
+ return suffixes[::-1]
+
+ @property
+ def stem(self):
+ """The final path component, minus its last suffix."""
+ return self.parser.splitext(self.name)[0]
+
+ def with_name(self, name):
+ """Return a new path with the file name changed."""
+ split = self.parser.split
+ if split(name)[0]:
+ raise ValueError(f"Invalid name {name!r}")
+ return self.with_segments(split(str(self))[0], name)
+
+ 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 parts(self):
+ """An object providing sequence-like access to the
+ components in the filesystem path."""
+ anchor, parts = _explode_path(self)
+ if anchor:
+ parts.append(anchor)
+ return tuple(reversed(parts))
+
+ def joinpath(self, *pathsegments):
+ """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.with_segments(str(self), *pathsegments)
+
+ def __truediv__(self, key):
+ try:
+ return self.with_segments(str(self), key)
+ except TypeError:
+ return NotImplemented
+
+ def __rtruediv__(self, key):
+ try:
+ return self.with_segments(key, str(self))
+ except TypeError:
+ return NotImplemented
+
+ @property
+ def parent(self):
+ """The logical parent of the path."""
+ path = str(self)
+ parent = self.parser.split(path)[0]
+ if path != parent:
+ return self.with_segments(parent)
+ return self
+
+ @property
+ def parents(self):
+ """A sequence of this path's logical parents."""
+ split = self.parser.split
+ path = str(self)
+ parent = split(path)[0]
+ parents = []
+ while path != parent:
+ parents.append(self.with_segments(parent))
+ path = parent
+ parent = split(path)[0]
+ return tuple(parents)
+
+ def full_match(self, pattern, *, case_sensitive=None):
+ """
+ Return True if this path matches the given glob-style pattern. The
+ pattern is matched against the entire path.
+ """
+ if not hasattr(pattern, 'with_segments'):
+ pattern = self.with_segments(pattern)
+ if case_sensitive is None:
+ case_sensitive = self.parser.normcase('Aa') == 'Aa'
+ globber = _PathGlobber(pattern.parser.sep, case_sensitive, recursive=True)
+ match = globber.compile(str(pattern))
+ return match(str(self)) is not None
+
+
+class _ReadablePath(_JoinablePath):
+ """Abstract base class for readable path objects.
+
+ 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
+ of this path.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def __open_rb__(self, buffering=-1):
+ """
+ Open the file pointed to by this path for reading in binary mode and
+ return a file object, like open(mode='rb').
+ """
+ raise NotImplementedError
+
+ def read_bytes(self):
+ """
+ Open the file in bytes mode, read it, and close the file.
+ """
+ with magic_open(self, mode='rb', buffering=0) as f:
+ return f.read()
+
+ def read_text(self, encoding=None, errors=None, newline=None):
+ """
+ Open the file in text mode, read it, and close the file.
+ """
+ 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.
+
+ The children are yielded in arbitrary order, and the
+ special entries '.' and '..' are not included.
+ """
+ raise NotImplementedError
+
+ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
+ """Iterate over this subtree and yield all existing files (of any
+ kind, including directories) matching the given relative pattern.
+ """
+ if not hasattr(pattern, 'with_segments'):
+ pattern = self.with_segments(pattern)
+ anchor, parts = _explode_path(pattern)
+ if anchor:
+ raise NotImplementedError("Non-relative patterns are unsupported")
+ case_sensitive_default = self.parser.normcase('Aa') == 'Aa'
+ if case_sensitive is None:
+ case_sensitive = case_sensitive_default
+ case_pedantic = False
+ else:
+ case_pedantic = case_sensitive_default != case_sensitive
+ recursive = True if recurse_symlinks else _no_recurse_symlinks
+ globber = _PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
+ select = globber.selector(parts)
+ return select(self.joinpath(''))
+
+ def walk(self, top_down=True, on_error=None, follow_symlinks=False):
+ """Walk the directory tree from this directory, similar to os.walk()."""
+ paths = [self]
+ while paths:
+ path = paths.pop()
+ if isinstance(path, tuple):
+ yield path
+ continue
+ dirnames = []
+ filenames = []
+ if not top_down:
+ paths.append((path, dirnames, filenames))
+ try:
+ for child in path.iterdir():
+ if child.info.is_dir(follow_symlinks=follow_symlinks):
+ if not top_down:
+ paths.append(child)
+ dirnames.append(child.name)
+ else:
+ filenames.append(child.name)
+ except OSError as error:
+ if on_error is not None:
+ on_error(error)
+ if not top_down:
+ while not isinstance(paths.pop(), tuple):
+ pass
+ continue
+ if top_down:
+ 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.
+ """
+ raise NotImplementedError
+
+ def copy(self, target, follow_symlinks=True, preserve_metadata=False):
+ """
+ Recursively copy this file or directory tree to the given destination.
+ """
+ if not hasattr(target, 'with_segments'):
+ target = self.with_segments(target)
+ ensure_distinct_paths(self, target)
+ copy_file(self, target, follow_symlinks, preserve_metadata)
+ return target.joinpath() # Empty join to ensure fresh metadata.
+
+ def copy_into(self, target_dir, *, follow_symlinks=True,
+ 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, 'with_segments'):
+ target = target_dir / name
+ else:
+ target = self.with_segments(target_dir, name)
+ return self.copy(target, follow_symlinks=follow_symlinks,
+ preserve_metadata=preserve_metadata)
+
+
+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.
+ Note the order of arguments (link, target) is the reverse of os.symlink.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def mkdir(self):
+ """
+ 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 a file object, like open(mode='wb').
+ """
+ raise NotImplementedError
+
+ 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 magic_open(self, 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__)
+ with magic_open(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
+ return f.write(data)
+
+ def _write_info(self, info, follow_symlinks=True):
+ """
+ Write the given PathInfo to this path.
+ """
+ pass
+
+
+_JoinablePath.register(PurePath)
+_ReadablePath.register(Path)
+_WritablePath.register(Path)
diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py
index 4bb62daecb6..830bfa4ca78 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -1059,14 +1059,14 @@ class PathTest(test_pathlib_abc.RWPathTest, PurePathTest):
return d
def test_matches_writablepath_docstrings(self):
- path_names = {name for name in dir(pathlib._abc.WritablePath) if name[0] != '_'}
+ path_names = {name for name in dir(pathlib.types._WritablePath) if name[0] != '_'}
for attr_name in path_names:
if attr_name == 'parser':
# On Windows, Path.parser is ntpath, but WritablePath.parser is
# posixpath, and so their docstrings differ.
continue
our_attr = getattr(self.cls, attr_name)
- path_attr = getattr(pathlib._abc.WritablePath, attr_name)
+ path_attr = getattr(pathlib.types._WritablePath, attr_name)
self.assertEqual(our_attr.__doc__, path_attr.__doc__)
def test_concrete_class(self):
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
index 1c1797ff04f..dea16e63512 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -4,8 +4,8 @@ import os
import errno
import unittest
-from pathlib._abc import JoinablePath, ReadablePath, WritablePath, magic_open
-from pathlib.types import _PathParser, PathInfo
+from pathlib._os import magic_open
+from pathlib.types import _PathParser, PathInfo, _JoinablePath, _ReadablePath, _WritablePath
import posixpath
from test.support.os_helper import TESTFN
@@ -31,7 +31,7 @@ def needs_windows(fn):
#
-class DummyJoinablePath(JoinablePath):
+class DummyJoinablePath(_JoinablePath):
__slots__ = ('_segments',)
parser = posixpath
@@ -78,7 +78,7 @@ class JoinablePathTest(unittest.TestCase):
def test_is_joinable(self):
p = self.cls(self.base)
- self.assertIsInstance(p, JoinablePath)
+ self.assertIsInstance(p, _JoinablePath)
def test_parser(self):
self.assertIsInstance(self.cls.parser, _PathParser)
@@ -855,7 +855,7 @@ class DummyReadablePathInfo:
return False
-class DummyReadablePath(ReadablePath, DummyJoinablePath):
+class DummyReadablePath(_ReadablePath, DummyJoinablePath):
"""
Simple implementation of DummyReadablePath that keeps files and
directories in memory.
@@ -900,7 +900,7 @@ class DummyReadablePath(ReadablePath, DummyJoinablePath):
raise NotImplementedError
-class DummyWritablePath(WritablePath, DummyJoinablePath):
+class DummyWritablePath(_WritablePath, DummyJoinablePath):
__slots__ = ()
def __open_wb__(self, buffering=-1):
@@ -999,7 +999,7 @@ class ReadablePathTest(JoinablePathTest):
def test_is_readable(self):
p = self.cls(self.base)
- self.assertIsInstance(p, ReadablePath)
+ self.assertIsInstance(p, _ReadablePath)
def test_magic_open(self):
p = self.cls(self.base)
@@ -1130,7 +1130,7 @@ class ReadablePathTest(JoinablePathTest):
q = p / 'myfile'
self.assertFalse(q.info.exists())
self.assertFalse(q.info.exists(follow_symlinks=False))
- if isinstance(self.cls, WritablePath):
+ if isinstance(self.cls, _WritablePath):
q.write_text('hullo')
self.assertFalse(q.info.exists())
self.assertFalse(q.info.exists(follow_symlinks=False))
@@ -1162,7 +1162,7 @@ class ReadablePathTest(JoinablePathTest):
q = p / 'mydir'
self.assertFalse(q.info.is_dir())
self.assertFalse(q.info.is_dir(follow_symlinks=False))
- if isinstance(self.cls, WritablePath):
+ if isinstance(self.cls, _WritablePath):
q.mkdir()
self.assertFalse(q.info.is_dir())
self.assertFalse(q.info.is_dir(follow_symlinks=False))
@@ -1194,7 +1194,7 @@ class ReadablePathTest(JoinablePathTest):
q = p / 'myfile'
self.assertFalse(q.info.is_file())
self.assertFalse(q.info.is_file(follow_symlinks=False))
- if isinstance(self.cls, WritablePath):
+ if isinstance(self.cls, _WritablePath):
q.write_text('hullo')
self.assertFalse(q.info.is_file())
self.assertFalse(q.info.is_file(follow_symlinks=False))
@@ -1220,7 +1220,7 @@ class WritablePathTest(JoinablePathTest):
def test_is_writable(self):
p = self.cls(self.base)
- self.assertIsInstance(p, WritablePath)
+ self.assertIsInstance(p, _WritablePath)
class DummyRWPath(DummyWritablePath, DummyReadablePath):