https://github.com/python/cpython/commit/d0eb01c9de9a455df36f885506314d8d00645c9f
commit: d0eb01c9de9a455df36f885506314d8d00645c9f
branch: main
author: Barney Gale <barney.g...@gmail.com>
committer: barneygale <barney.g...@gmail.com>
date: 2025-03-03T17:56:57Z
summary:

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`

files:
D Lib/pathlib/_abc.py
M Lib/pathlib/types.py
M Lib/test/test_pathlib/test_pathlib.py
M Lib/test/test_pathlib/test_pathlib_abc.py

diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
deleted file mode 100644
index d9fb018d75f538..00000000000000
--- 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 b781264796bf67..cfdbc89644a24a 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 @@ def exists(self, *, follow_symlinks: bool = True) -> bool: 
...
     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 4bb62daecb6356..830bfa4ca78138 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -1059,14 +1059,14 @@ def tempdir(self):
         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 1c1797ff04f653..dea16e6351265f 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -4,8 +4,8 @@
 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 @@ def setUp(self):
 
     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 @@ def is_symlink(self):
         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 @@ def readlink(self):
         raise NotImplementedError
 
 
-class DummyWritablePath(WritablePath, DummyJoinablePath):
+class DummyWritablePath(_WritablePath, DummyJoinablePath):
     __slots__ = ()
 
     def __open_wb__(self, buffering=-1):
@@ -999,7 +999,7 @@ def assertEqualNormCase(self, path_a, path_b):
 
     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 @@ def test_info_exists_caching(self):
         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 @@ def test_info_is_dir_caching(self):
         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 @@ def test_info_is_file_caching(self):
         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):

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to