]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-110109: Move pathlib ABCs to new `pathlib._abc` module. (#112881)
authorBarney Gale <barney.gale@gmail.com>
Sat, 9 Dec 2023 15:07:40 +0000 (15:07 +0000)
committerGitHub <noreply@github.com>
Sat, 9 Dec 2023 15:07:40 +0000 (16:07 +0100)
Move `_PurePathBase` and `_PathBase` to a new `pathlib._abc` module, and
drop the underscores from the class names.

Tests are mostly left alone in this commit, but they'll be similarly split
in a subsequent commit.

The `pathlib._abc` module will be published as an independent PyPI package
(similar to how `zipfile._path` is published as `zipp`), to be refined
and stabilised prior to its possible addition to the standard library.

Lib/pathlib/__init__.py [new file with mode: 0644]
Lib/pathlib/_abc.py [moved from Lib/pathlib.py with 70% similarity]
Lib/test/test_pathlib.py

diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py
new file mode 100644 (file)
index 0000000..f4668ab
--- /dev/null
@@ -0,0 +1,507 @@
+"""Object-oriented filesystem paths.
+
+This module provides classes to represent abstract paths and concrete
+paths with operations that have semantics appropriate for different
+operating systems.
+"""
+
+import io
+import ntpath
+import os
+import posixpath
+
+try:
+    import pwd
+except ImportError:
+    pwd = None
+try:
+    import grp
+except ImportError:
+    grp = None
+
+from . import _abc
+
+
+__all__ = [
+    "UnsupportedOperation",
+    "PurePath", "PurePosixPath", "PureWindowsPath",
+    "Path", "PosixPath", "WindowsPath",
+    ]
+
+
+UnsupportedOperation = _abc.UnsupportedOperation
+
+
+class PurePath(_abc.PurePathBase):
+    """Base class for manipulating paths without I/O.
+
+    PurePath represents a filesystem path and offers operations which
+    don't imply any actual filesystem I/O.  Depending on your system,
+    instantiating a PurePath will return either a PurePosixPath or a
+    PureWindowsPath object.  You can also instantiate either of these classes
+    directly, regardless of your system.
+    """
+
+    __slots__ = (
+        # The `_str_normcase_cached` slot stores the string path with
+        # normalized case. It is set when the `_str_normcase` property is
+        # accessed for the first time. It's used to implement `__eq__()`
+        # `__hash__()`, and `_parts_normcase`
+        '_str_normcase_cached',
+
+        # The `_parts_normcase_cached` slot stores the case-normalized
+        # string path after splitting on path separators. It's set when the
+        # `_parts_normcase` property is accessed for the first time. It's used
+        # to implement comparison methods like `__lt__()`.
+        '_parts_normcase_cached',
+
+        # The `_hash` slot stores the hash of the case-normalized string
+        # path. It's set when `__hash__()` is called for the first time.
+        '_hash',
+    )
+
+    def __new__(cls, *args, **kwargs):
+        """Construct a PurePath from one or several strings and or existing
+        PurePath objects.  The strings and path objects are combined so as
+        to yield a canonicalized path, which is incorporated into the
+        new PurePath object.
+        """
+        if cls is PurePath:
+            cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
+        return object.__new__(cls)
+
+    def __init__(self, *args):
+        paths = []
+        for arg in args:
+            if isinstance(arg, PurePath):
+                if arg.pathmod is ntpath and self.pathmod is posixpath:
+                    # GH-103631: Convert separators for backwards compatibility.
+                    paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
+                else:
+                    paths.extend(arg._raw_paths)
+            else:
+                try:
+                    path = os.fspath(arg)
+                except TypeError:
+                    path = arg
+                if not isinstance(path, str):
+                    raise TypeError(
+                        "argument should be a str or an os.PathLike "
+                        "object where __fspath__ returns a str, "
+                        f"not {type(path).__name__!r}")
+                paths.append(path)
+        super().__init__(*paths)
+
+    def __reduce__(self):
+        # Using the parts tuple helps share interned path parts
+        # when pickling related paths.
+        return (self.__class__, self.parts)
+
+    def __fspath__(self):
+        return str(self)
+
+    def __bytes__(self):
+        """Return the bytes representation of the path.  This is only
+        recommended to use under Unix."""
+        return os.fsencode(self)
+
+    @property
+    def _str_normcase(self):
+        # String with normalized case, for hashing and equality checks
+        try:
+            return self._str_normcase_cached
+        except AttributeError:
+            if _abc._is_case_sensitive(self.pathmod):
+                self._str_normcase_cached = str(self)
+            else:
+                self._str_normcase_cached = str(self).lower()
+            return self._str_normcase_cached
+
+    def __hash__(self):
+        try:
+            return self._hash
+        except AttributeError:
+            self._hash = hash(self._str_normcase)
+            return self._hash
+
+    def __eq__(self, other):
+        if not isinstance(other, PurePath):
+            return NotImplemented
+        return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod
+
+    @property
+    def _parts_normcase(self):
+        # Cached parts with normalized case, for comparisons.
+        try:
+            return self._parts_normcase_cached
+        except AttributeError:
+            self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep)
+            return self._parts_normcase_cached
+
+    def __lt__(self, other):
+        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
+            return NotImplemented
+        return self._parts_normcase < other._parts_normcase
+
+    def __le__(self, other):
+        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
+            return NotImplemented
+        return self._parts_normcase <= other._parts_normcase
+
+    def __gt__(self, other):
+        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
+            return NotImplemented
+        return self._parts_normcase > other._parts_normcase
+
+    def __ge__(self, other):
+        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
+            return NotImplemented
+        return self._parts_normcase >= other._parts_normcase
+
+    def as_uri(self):
+        """Return the path as a URI."""
+        if not self.is_absolute():
+            raise ValueError("relative path can't be expressed as a file URI")
+
+        drive = self.drive
+        if len(drive) == 2 and drive[1] == ':':
+            # It's a path on a local drive => 'file:///c:/a/b'
+            prefix = 'file:///' + drive
+            path = self.as_posix()[2:]
+        elif drive:
+            # It's a path on a network drive => 'file://host/share/a/b'
+            prefix = 'file:'
+            path = self.as_posix()
+        else:
+            # It's a posix path => 'file:///etc/hosts'
+            prefix = 'file://'
+            path = str(self)
+        from urllib.parse import quote_from_bytes
+        return prefix + quote_from_bytes(os.fsencode(path))
+
+
+# Subclassing os.PathLike makes isinstance() checks slower,
+# which in turn makes Path construction slower. Register instead!
+os.PathLike.register(PurePath)
+
+
+class PurePosixPath(PurePath):
+    """PurePath subclass for non-Windows systems.
+
+    On a POSIX system, instantiating a PurePath should return this object.
+    However, you can also instantiate it directly on any system.
+    """
+    pathmod = posixpath
+    __slots__ = ()
+
+
+class PureWindowsPath(PurePath):
+    """PurePath subclass for Windows systems.
+
+    On a Windows system, instantiating a PurePath should return this object.
+    However, you can also instantiate it directly on any system.
+    """
+    pathmod = ntpath
+    __slots__ = ()
+
+
+class Path(_abc.PathBase, PurePath):
+    """PurePath subclass that can make system calls.
+
+    Path represents a filesystem path but unlike PurePath, also offers
+    methods to do system calls on path objects. Depending on your system,
+    instantiating a Path will return either a PosixPath or a WindowsPath
+    object. You can also instantiate a PosixPath or WindowsPath directly,
+    but cannot instantiate a WindowsPath on a POSIX system or vice versa.
+    """
+    __slots__ = ()
+    as_uri = PurePath.as_uri
+
+    @classmethod
+    def _unsupported(cls, method_name):
+        msg = f"{cls.__name__}.{method_name}() is unsupported on this system"
+        raise UnsupportedOperation(msg)
+
+    def __init__(self, *args, **kwargs):
+        if kwargs:
+            import warnings
+            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 stat(self, *, follow_symlinks=True):
+        """
+        Return the result of the stat() system call on this path, like
+        os.stat() does.
+        """
+        return os.stat(self, follow_symlinks=follow_symlinks)
+
+    def is_mount(self):
+        """
+        Check if this path is a mount point
+        """
+        return os.path.ismount(self)
+
+    def is_junction(self):
+        """
+        Whether this path is a junction.
+        """
+        return os.path.isjunction(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 iterdir(self):
+        """Yield path objects of the directory contents.
+
+        The children are yielded in arbitrary order, and the
+        special entries '.' and '..' are not included.
+        """
+        return (self._make_child_relpath(name) for name in os.listdir(self))
+
+    def _scandir(self):
+        return os.scandir(self)
+
+    def absolute(self):
+        """Return an absolute version of this path
+        No normalization or symlink resolution is performed.
+
+        Use resolve() to resolve symlinks and remove '..' segments.
+        """
+        if self.is_absolute():
+            return self
+        if self.root:
+            drive = os.path.splitroot(os.getcwd())[0]
+            return self._from_parsed_parts(drive, self.root, self._tail)
+        if self.drive:
+            # There is a CWD on each drive-letter drive.
+            cwd = os.path.abspath(self.drive)
+        else:
+            cwd = os.getcwd()
+        if not self._tail:
+            # 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().
+            result = self.with_segments(cwd)
+            result._str = cwd
+            return result
+        drive, root, rel = os.path.splitroot(cwd)
+        if not rel:
+            return self._from_parsed_parts(drive, root, self._tail)
+        tail = rel.split(self.pathmod.sep)
+        tail.extend(self._tail)
+        return self._from_parsed_parts(drive, root, tail)
+
+    def resolve(self, strict=False):
+        """
+        Make the path absolute, resolving all symlinks on the way and also
+        normalizing it.
+        """
+
+        return self.with_segments(os.path.realpath(self, strict=strict))
+
+    if pwd:
+        def owner(self, *, follow_symlinks=True):
+            """
+            Return the login name of the file owner.
+            """
+            uid = self.stat(follow_symlinks=follow_symlinks).st_uid
+            return pwd.getpwuid(uid).pw_name
+
+    if grp:
+        def group(self, *, follow_symlinks=True):
+            """
+            Return the group name of the file gid.
+            """
+            gid = self.stat(follow_symlinks=follow_symlinks).st_gid
+            return grp.getgrgid(gid).gr_name
+
+    if hasattr(os, "readlink"):
+        def readlink(self):
+            """
+            Return the path to which the symbolic link points.
+            """
+            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 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)
+
+    if hasattr(os, "symlink"):
+        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.
+            """
+            os.symlink(target, self, target_is_directory)
+
+    if hasattr(os, "link"):
+        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.
+            """
+            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 = os.path.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
+
+    @classmethod
+    def from_uri(cls, uri):
+        """Return a new path from the given 'file' URI."""
+        if not uri.startswith('file:'):
+            raise ValueError(f"URI does not start with 'file:': {uri!r}")
+        path = uri[5:]
+        if path[:3] == '///':
+            # Remove empty authority
+            path = path[2:]
+        elif path[:12] == '//localhost/':
+            # Remove 'localhost' authority
+            path = path[11:]
+        if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'):
+            # Remove slash before DOS device/UNC path
+            path = path[1:]
+        if path[1:2] == '|':
+            # Replace bar with colon in DOS drive
+            path = path[:1] + ':' + path[2:]
+        from urllib.parse import unquote_to_bytes
+        path = cls(os.fsdecode(unquote_to_bytes(path)))
+        if not path.is_absolute():
+            raise ValueError(f"URI is not absolute: {uri!r}")
+        return path
+
+
+class PosixPath(Path, PurePosixPath):
+    """Path subclass for non-Windows systems.
+
+    On a POSIX system, instantiating a Path should return this object.
+    """
+    __slots__ = ()
+
+    if os.name == 'nt':
+        def __new__(cls, *args, **kwargs):
+            raise UnsupportedOperation(
+                f"cannot instantiate {cls.__name__!r} on your system")
+
+class WindowsPath(Path, PureWindowsPath):
+    """Path subclass for Windows systems.
+
+    On a Windows system, instantiating a Path should return this object.
+    """
+    __slots__ = ()
+
+    if os.name != 'nt':
+        def __new__(cls, *args, **kwargs):
+            raise UnsupportedOperation(
+                f"cannot instantiate {cls.__name__!r} on your system")
similarity index 70%
rename from Lib/pathlib.py
rename to Lib/pathlib/_abc.py
index 87d1f6b58ec52ed99c3d16e474f7c4afc4002b22..4808d0e61f70380b7d7c16d1fff4b8f37355b689 100644 (file)
@@ -1,10 +1,3 @@
-"""Object-oriented filesystem paths.
-
-This module provides classes to represent abstract paths and concrete
-paths with operations that have semantics appropriate for different
-operating systems.
-"""
-
 import functools
 import io
 import ntpath
@@ -17,27 +10,11 @@ from errno import ENOENT, ENOTDIR, EBADF, ELOOP, EINVAL
 from itertools import chain
 from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
 
-try:
-    import pwd
-except ImportError:
-    pwd = None
-try:
-    import grp
-except ImportError:
-    grp = None
-
-
-__all__ = [
-    "UnsupportedOperation",
-    "PurePath", "PurePosixPath", "PureWindowsPath",
-    "Path", "PosixPath", "WindowsPath",
-    ]
-
 #
 # Internals
 #
 
-# Maximum number of symlinks to follow in _PathBase.resolve()
+# Maximum number of symlinks to follow in PathBase.resolve()
 _MAX_SYMLINKS = 40
 
 # Reference for Windows paths can be found at
@@ -158,10 +135,6 @@ def _select_unique(paths):
         yielded.clear()
 
 
-#
-# Public API
-#
-
 class UnsupportedOperation(NotImplementedError):
     """An exception that is raised when an unsupported operation is called on
     a path object.
@@ -198,7 +171,7 @@ class _PathParents(Sequence):
         return "<{}.parents>".format(type(self._path).__name__)
 
 
-class _PurePathBase:
+class PurePathBase:
     """Base class for pure path objects.
 
     This class *does not* provide several magic methods that are defined in
@@ -227,7 +200,7 @@ class _PurePathBase:
         '_str',
 
         # The '_resolving' slot stores a boolean indicating whether the path
-        # is being processed by `_PathBase.resolve()`. This prevents duplicate
+        # is being processed by `PathBase.resolve()`. This prevents duplicate
         # work from occurring when `resolve()` calls `stat()` or `readlink()`.
         '_resolving',
     )
@@ -431,7 +404,7 @@ class _PurePathBase:
             warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg,
                                  remove=(3, 14))
             other = self.with_segments(other, *_deprecated)
-        elif not isinstance(other, _PurePathBase):
+        elif not isinstance(other, PurePathBase):
             other = self.with_segments(other)
         for step, path in enumerate(chain([other], other.parents)):
             if path == self or path in self.parents:
@@ -455,7 +428,7 @@ class _PurePathBase:
             warnings._deprecated("pathlib.PurePath.is_relative_to(*args)",
                                  msg, remove=(3, 14))
             other = self.with_segments(other, *_deprecated)
-        elif not isinstance(other, _PurePathBase):
+        elif not isinstance(other, PurePathBase):
             other = self.with_segments(other)
         return other == self or other in self.parents
 
@@ -542,7 +515,7 @@ class _PurePathBase:
         """
         Return True if this path matches the given pattern.
         """
-        if not isinstance(path_pattern, _PurePathBase):
+        if not isinstance(path_pattern, PurePathBase):
             path_pattern = self.with_segments(path_pattern)
         if case_sensitive is None:
             case_sensitive = _is_case_sensitive(self.pathmod)
@@ -558,183 +531,8 @@ class _PurePathBase:
         return match(str(self)) is not None
 
 
-class PurePath(_PurePathBase):
-    """Base class for manipulating paths without I/O.
-
-    PurePath represents a filesystem path and offers operations which
-    don't imply any actual filesystem I/O.  Depending on your system,
-    instantiating a PurePath will return either a PurePosixPath or a
-    PureWindowsPath object.  You can also instantiate either of these classes
-    directly, regardless of your system.
-    """
-
-    __slots__ = (
-        # The `_str_normcase_cached` slot stores the string path with
-        # normalized case. It is set when the `_str_normcase` property is
-        # accessed for the first time. It's used to implement `__eq__()`
-        # `__hash__()`, and `_parts_normcase`
-        '_str_normcase_cached',
-
-        # The `_parts_normcase_cached` slot stores the case-normalized
-        # string path after splitting on path separators. It's set when the
-        # `_parts_normcase` property is accessed for the first time. It's used
-        # to implement comparison methods like `__lt__()`.
-        '_parts_normcase_cached',
-
-        # The `_hash` slot stores the hash of the case-normalized string
-        # path. It's set when `__hash__()` is called for the first time.
-        '_hash',
-    )
-
-    def __new__(cls, *args, **kwargs):
-        """Construct a PurePath from one or several strings and or existing
-        PurePath objects.  The strings and path objects are combined so as
-        to yield a canonicalized path, which is incorporated into the
-        new PurePath object.
-        """
-        if cls is PurePath:
-            cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
-        return object.__new__(cls)
-
-    def __init__(self, *args):
-        paths = []
-        for arg in args:
-            if isinstance(arg, PurePath):
-                if arg.pathmod is ntpath and self.pathmod is posixpath:
-                    # GH-103631: Convert separators for backwards compatibility.
-                    paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
-                else:
-                    paths.extend(arg._raw_paths)
-            else:
-                try:
-                    path = os.fspath(arg)
-                except TypeError:
-                    path = arg
-                if not isinstance(path, str):
-                    raise TypeError(
-                        "argument should be a str or an os.PathLike "
-                        "object where __fspath__ returns a str, "
-                        f"not {type(path).__name__!r}")
-                paths.append(path)
-        super().__init__(*paths)
-
-    def __reduce__(self):
-        # Using the parts tuple helps share interned path parts
-        # when pickling related paths.
-        return (self.__class__, self.parts)
-
-    def __fspath__(self):
-        return str(self)
-
-    def __bytes__(self):
-        """Return the bytes representation of the path.  This is only
-        recommended to use under Unix."""
-        return os.fsencode(self)
-
-    @property
-    def _str_normcase(self):
-        # String with normalized case, for hashing and equality checks
-        try:
-            return self._str_normcase_cached
-        except AttributeError:
-            if _is_case_sensitive(self.pathmod):
-                self._str_normcase_cached = str(self)
-            else:
-                self._str_normcase_cached = str(self).lower()
-            return self._str_normcase_cached
-
-    def __hash__(self):
-        try:
-            return self._hash
-        except AttributeError:
-            self._hash = hash(self._str_normcase)
-            return self._hash
-
-    def __eq__(self, other):
-        if not isinstance(other, PurePath):
-            return NotImplemented
-        return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod
-
-    @property
-    def _parts_normcase(self):
-        # Cached parts with normalized case, for comparisons.
-        try:
-            return self._parts_normcase_cached
-        except AttributeError:
-            self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep)
-            return self._parts_normcase_cached
-
-    def __lt__(self, other):
-        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
-            return NotImplemented
-        return self._parts_normcase < other._parts_normcase
-
-    def __le__(self, other):
-        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
-            return NotImplemented
-        return self._parts_normcase <= other._parts_normcase
-
-    def __gt__(self, other):
-        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
-            return NotImplemented
-        return self._parts_normcase > other._parts_normcase
-
-    def __ge__(self, other):
-        if not isinstance(other, PurePath) or self.pathmod is not other.pathmod:
-            return NotImplemented
-        return self._parts_normcase >= other._parts_normcase
-
-    def as_uri(self):
-        """Return the path as a URI."""
-        if not self.is_absolute():
-            raise ValueError("relative path can't be expressed as a file URI")
-
-        drive = self.drive
-        if len(drive) == 2 and drive[1] == ':':
-            # It's a path on a local drive => 'file:///c:/a/b'
-            prefix = 'file:///' + drive
-            path = self.as_posix()[2:]
-        elif drive:
-            # It's a path on a network drive => 'file://host/share/a/b'
-            prefix = 'file:'
-            path = self.as_posix()
-        else:
-            # It's a posix path => 'file:///etc/hosts'
-            prefix = 'file://'
-            path = str(self)
-        from urllib.parse import quote_from_bytes
-        return prefix + quote_from_bytes(os.fsencode(path))
-
-
-# Subclassing os.PathLike makes isinstance() checks slower,
-# which in turn makes Path construction slower. Register instead!
-os.PathLike.register(PurePath)
-
-
-class PurePosixPath(PurePath):
-    """PurePath subclass for non-Windows systems.
-
-    On a POSIX system, instantiating a PurePath should return this object.
-    However, you can also instantiate it directly on any system.
-    """
-    pathmod = posixpath
-    __slots__ = ()
-
-
-class PureWindowsPath(PurePath):
-    """PurePath subclass for Windows systems.
-
-    On a Windows system, instantiating a PurePath should return this object.
-    However, you can also instantiate it directly on any system.
-    """
-    pathmod = ntpath
-    __slots__ = ()
-
-
-# Filesystem-accessing classes
-
 
-class _PathBase(_PurePathBase):
+class PathBase(PurePathBase):
     """Base class for concrete path objects.
 
     This class provides dummy implementations for many methods that derived
@@ -752,8 +550,6 @@ class _PathBase(_PurePathBase):
     @classmethod
     def _unsupported(cls, method_name):
         msg = f"{cls.__name__}.{method_name}() is unsupported"
-        if issubclass(cls, Path):
-            msg += " on this system"
         raise UnsupportedOperation(msg)
 
     def stat(self, *, follow_symlinks=True):
@@ -1352,299 +1148,3 @@ class _PathBase(_PurePathBase):
     def as_uri(self):
         """Return the path as a URI."""
         self._unsupported("as_uri")
-
-
-class Path(_PathBase, PurePath):
-    """PurePath subclass that can make system calls.
-
-    Path represents a filesystem path but unlike PurePath, also offers
-    methods to do system calls on path objects. Depending on your system,
-    instantiating a Path will return either a PosixPath or a WindowsPath
-    object. You can also instantiate a PosixPath or WindowsPath directly,
-    but cannot instantiate a WindowsPath on a POSIX system or vice versa.
-    """
-    __slots__ = ()
-    as_uri = PurePath.as_uri
-
-    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 stat(self, *, follow_symlinks=True):
-        """
-        Return the result of the stat() system call on this path, like
-        os.stat() does.
-        """
-        return os.stat(self, follow_symlinks=follow_symlinks)
-
-    def is_mount(self):
-        """
-        Check if this path is a mount point
-        """
-        return os.path.ismount(self)
-
-    def is_junction(self):
-        """
-        Whether this path is a junction.
-        """
-        return os.path.isjunction(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 iterdir(self):
-        """Yield path objects of the directory contents.
-
-        The children are yielded in arbitrary order, and the
-        special entries '.' and '..' are not included.
-        """
-        return (self._make_child_relpath(name) for name in os.listdir(self))
-
-    def _scandir(self):
-        return os.scandir(self)
-
-    def absolute(self):
-        """Return an absolute version of this path
-        No normalization or symlink resolution is performed.
-
-        Use resolve() to resolve symlinks and remove '..' segments.
-        """
-        if self.is_absolute():
-            return self
-        if self.root:
-            drive = os.path.splitroot(os.getcwd())[0]
-            return self._from_parsed_parts(drive, self.root, self._tail)
-        if self.drive:
-            # There is a CWD on each drive-letter drive.
-            cwd = os.path.abspath(self.drive)
-        else:
-            cwd = os.getcwd()
-        if not self._tail:
-            # 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().
-            result = self.with_segments(cwd)
-            result._str = cwd
-            return result
-        drive, root, rel = os.path.splitroot(cwd)
-        if not rel:
-            return self._from_parsed_parts(drive, root, self._tail)
-        tail = rel.split(self.pathmod.sep)
-        tail.extend(self._tail)
-        return self._from_parsed_parts(drive, root, tail)
-
-    def resolve(self, strict=False):
-        """
-        Make the path absolute, resolving all symlinks on the way and also
-        normalizing it.
-        """
-
-        return self.with_segments(os.path.realpath(self, strict=strict))
-
-    if pwd:
-        def owner(self, *, follow_symlinks=True):
-            """
-            Return the login name of the file owner.
-            """
-            uid = self.stat(follow_symlinks=follow_symlinks).st_uid
-            return pwd.getpwuid(uid).pw_name
-
-    if grp:
-        def group(self, *, follow_symlinks=True):
-            """
-            Return the group name of the file gid.
-            """
-            gid = self.stat(follow_symlinks=follow_symlinks).st_gid
-            return grp.getgrgid(gid).gr_name
-
-    if hasattr(os, "readlink"):
-        def readlink(self):
-            """
-            Return the path to which the symbolic link points.
-            """
-            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 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)
-
-    if hasattr(os, "symlink"):
-        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.
-            """
-            os.symlink(target, self, target_is_directory)
-
-    if hasattr(os, "link"):
-        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.
-            """
-            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 = os.path.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
-
-    @classmethod
-    def from_uri(cls, uri):
-        """Return a new path from the given 'file' URI."""
-        if not uri.startswith('file:'):
-            raise ValueError(f"URI does not start with 'file:': {uri!r}")
-        path = uri[5:]
-        if path[:3] == '///':
-            # Remove empty authority
-            path = path[2:]
-        elif path[:12] == '//localhost/':
-            # Remove 'localhost' authority
-            path = path[11:]
-        if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'):
-            # Remove slash before DOS device/UNC path
-            path = path[1:]
-        if path[1:2] == '|':
-            # Replace bar with colon in DOS drive
-            path = path[:1] + ':' + path[2:]
-        from urllib.parse import unquote_to_bytes
-        path = cls(os.fsdecode(unquote_to_bytes(path)))
-        if not path.is_absolute():
-            raise ValueError(f"URI is not absolute: {uri!r}")
-        return path
-
-
-class PosixPath(Path, PurePosixPath):
-    """Path subclass for non-Windows systems.
-
-    On a POSIX system, instantiating a Path should return this object.
-    """
-    __slots__ = ()
-
-    if os.name == 'nt':
-        def __new__(cls, *args, **kwargs):
-            raise UnsupportedOperation(
-                f"cannot instantiate {cls.__name__!r} on your system")
-
-class WindowsPath(Path, PureWindowsPath):
-    """Path subclass for Windows systems.
-
-    On a Windows system, instantiating a Path should return this object.
-    """
-    __slots__ = ()
-
-    if os.name != 'nt':
-        def __new__(cls, *args, **kwargs):
-            raise UnsupportedOperation(
-                f"cannot instantiate {cls.__name__!r} on your system")
index d35516a5c8fd9bef79c2ed781c2856a7727efd35..a1acba406ea37c088f5718351f4a251fd8e0b25e 100644 (file)
@@ -51,7 +51,7 @@ if hasattr(os, 'geteuid'):
 
 
 class PurePathBaseTest(unittest.TestCase):
-    cls = pathlib._PurePathBase
+    cls = pathlib._abc.PurePathBase
 
     def test_magic_methods(self):
         P = self.cls
@@ -66,7 +66,7 @@ class PurePathBaseTest(unittest.TestCase):
         self.assertIs(P.__ge__, object.__ge__)
 
 
-class DummyPurePath(pathlib._PurePathBase):
+class DummyPurePath(pathlib._abc.PurePathBase):
     def __eq__(self, other):
         if not isinstance(other, DummyPurePath):
             return NotImplemented
@@ -1586,7 +1586,7 @@ class PurePathSubclassTest(PurePathTest):
 #
 
 class PathBaseTest(PurePathBaseTest):
-    cls = pathlib._PathBase
+    cls = pathlib._abc.PathBase
 
     def test_unsupported_operation(self):
         P = self.cls
@@ -1667,7 +1667,7 @@ class DummyPathIO(io.BytesIO):
         super().close()
 
 
-class DummyPath(pathlib._PathBase):
+class DummyPath(pathlib._abc.PathBase):
     """
     Simple implementation of PathBase that keeps files and directories in
     memory.