diff options
Diffstat (limited to 'Lib/pathlib')
-rw-r--r-- | Lib/pathlib/__init__.py | 7 | ||||
-rw-r--r-- | Lib/pathlib/_os.py | 28 | ||||
-rw-r--r-- | Lib/pathlib/types.py | 51 |
3 files changed, 63 insertions, 23 deletions
diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 12cf9f579cb..2dc1f7f7126 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -28,8 +28,9 @@ except ImportError: from pathlib._os import ( PathInfo, DirEntryInfo, + magic_open, vfspath, ensure_different_files, ensure_distinct_paths, - copyfile2, copyfileobj, magic_open, copy_info, + copyfile2, copyfileobj, copy_info, ) @@ -1164,12 +1165,12 @@ class Path(PurePath): # os.symlink() incorrectly creates a file-symlink on Windows. Avoid # this by passing *target_is_dir* to os.symlink() on Windows. def _copy_from_symlink(self, source, preserve_metadata=False): - os.symlink(str(source.readlink()), self, source.info.is_dir()) + os.symlink(vfspath(source.readlink()), self, source.info.is_dir()) if preserve_metadata: copy_info(source.info, self, follow_symlinks=False) else: def _copy_from_symlink(self, source, preserve_metadata=False): - os.symlink(str(source.readlink()), self) + os.symlink(vfspath(source.readlink()), self) if preserve_metadata: copy_info(source.info, self, follow_symlinks=False) diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index 039836941dd..62a4adb555e 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -210,6 +210,26 @@ def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None, raise TypeError(f"{cls.__name__} can't be opened with mode {mode!r}") +def vfspath(path): + """ + Return the string representation of a virtual path object. + """ + try: + return os.fsdecode(path) + except TypeError: + pass + + path_type = type(path) + try: + return path_type.__vfspath__(path) + except AttributeError: + if hasattr(path_type, '__vfspath__'): + raise + + raise TypeError("expected str, bytes, os.PathLike or JoinablePath " + "object, not " + path_type.__name__) + + def ensure_distinct_paths(source, target): """ Raise OSError(EINVAL) if the other path is within this path. @@ -225,8 +245,8 @@ def ensure_distinct_paths(source, target): err = OSError(EINVAL, "Source path is a parent of target path") else: return - err.filename = str(source) - err.filename2 = str(target) + err.filename = vfspath(source) + err.filename2 = vfspath(target) raise err @@ -247,8 +267,8 @@ def ensure_different_files(source, target): except (OSError, ValueError): return err = OSError(EINVAL, "Source and target are the same file") - err.filename = str(source) - err.filename2 = str(target) + err.filename = vfspath(source) + err.filename2 = vfspath(target) raise err diff --git a/Lib/pathlib/types.py b/Lib/pathlib/types.py index d8f5c34a1a7..42b80221608 100644 --- a/Lib/pathlib/types.py +++ b/Lib/pathlib/types.py @@ -11,9 +11,10 @@ Protocols for supporting classes in pathlib. from abc import ABC, abstractmethod -from glob import _PathGlobber +from glob import _GlobberBase from io import text_encoding -from pathlib._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj +from pathlib._os import (magic_open, vfspath, ensure_distinct_paths, + ensure_different_files, copyfileobj) from pathlib import PurePath, Path from typing import Optional, Protocol, runtime_checkable @@ -60,6 +61,25 @@ class PathInfo(Protocol): def is_symlink(self) -> bool: ... +class _PathGlobber(_GlobberBase): + """Provides shell-style pattern matching and globbing for ReadablePath. + """ + + @staticmethod + def lexists(path): + return path.info.exists(follow_symlinks=False) + + @staticmethod + def scandir(path): + return ((child.info, child.name, child) for child in path.iterdir()) + + @staticmethod + def concat_path(path, text): + return path.with_segments(vfspath(path) + text) + + stringify_path = staticmethod(vfspath) + + class _JoinablePath(ABC): """Abstract base class for pure path objects. @@ -86,20 +106,19 @@ class _JoinablePath(ABC): raise NotImplementedError @abstractmethod - def __str__(self): - """Return the string representation of the path, suitable for - passing to system calls.""" + def __vfspath__(self): + """Return the string representation of the path.""" raise NotImplementedError @property def anchor(self): """The concatenation of the drive and root, or ''.""" - return _explode_path(str(self), self.parser.split)[0] + return _explode_path(vfspath(self), self.parser.split)[0] @property def name(self): """The final path component, if any.""" - return self.parser.split(str(self))[1] + return self.parser.split(vfspath(self))[1] @property def suffix(self): @@ -135,7 +154,7 @@ class _JoinablePath(ABC): split = self.parser.split if split(name)[0]: raise ValueError(f"Invalid name {name!r}") - path = str(self) + path = vfspath(self) path = path.removesuffix(split(path)[1]) + name return self.with_segments(path) @@ -168,7 +187,7 @@ class _JoinablePath(ABC): def parts(self): """An object providing sequence-like access to the components in the filesystem path.""" - anchor, parts = _explode_path(str(self), self.parser.split) + anchor, parts = _explode_path(vfspath(self), self.parser.split) if anchor: parts.append(anchor) return tuple(reversed(parts)) @@ -179,24 +198,24 @@ class _JoinablePath(ABC): paths) or a totally different path (if one of the arguments is anchored). """ - return self.with_segments(str(self), *pathsegments) + return self.with_segments(vfspath(self), *pathsegments) def __truediv__(self, key): try: - return self.with_segments(str(self), key) + return self.with_segments(vfspath(self), key) except TypeError: return NotImplemented def __rtruediv__(self, key): try: - return self.with_segments(key, str(self)) + return self.with_segments(key, vfspath(self)) except TypeError: return NotImplemented @property def parent(self): """The logical parent of the path.""" - path = str(self) + path = vfspath(self) parent = self.parser.split(path)[0] if path != parent: return self.with_segments(parent) @@ -206,7 +225,7 @@ class _JoinablePath(ABC): def parents(self): """A sequence of this path's logical parents.""" split = self.parser.split - path = str(self) + path = vfspath(self) parent = split(path)[0] parents = [] while path != parent: @@ -223,7 +242,7 @@ class _JoinablePath(ABC): case_sensitive = self.parser.normcase('Aa') == 'Aa' globber = _PathGlobber(self.parser.sep, case_sensitive, recursive=True) match = globber.compile(pattern, altsep=self.parser.altsep) - return match(str(self)) is not None + return match(vfspath(self)) is not None class _ReadablePath(_JoinablePath): @@ -412,7 +431,7 @@ class _WritablePath(_JoinablePath): while stack: src, dst = stack.pop() if not follow_symlinks and src.info.is_symlink(): - dst.symlink_to(str(src.readlink()), src.info.is_dir()) + dst.symlink_to(vfspath(src.readlink()), src.info.is_dir()) elif src.info.is_dir(): children = src.iterdir() dst.mkdir() |