mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
GH-128520: pathlib ABCs: add JoinablePath.__vfspath__()
(#133437)
In the abstract interface of `JoinablePath`, replace `__str__()` with `__vfspath__()`. This frees user implementations of `JoinablePath` to implement `__str__()` however they like (or not at all.) Also add `pathlib._os.vfspath()`, which calls `__fspath__()` or `__vfspath__()`.
This commit is contained in:
parent
9f69a58623
commit
5dbd27db7d
8 changed files with 118 additions and 81 deletions
30
Lib/glob.py
30
Lib/glob.py
|
@ -358,6 +358,12 @@ class _GlobberBase:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def stringify_path(path):
|
||||||
|
"""Converts the path to a string object
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
# High-level methods
|
# High-level methods
|
||||||
|
|
||||||
def compile(self, pat, altsep=None):
|
def compile(self, pat, altsep=None):
|
||||||
|
@ -466,8 +472,9 @@ class _GlobberBase:
|
||||||
select_next = self.selector(parts)
|
select_next = self.selector(parts)
|
||||||
|
|
||||||
def select_recursive(path, exists=False):
|
def select_recursive(path, exists=False):
|
||||||
match_pos = len(str(path))
|
path_str = self.stringify_path(path)
|
||||||
if match is None or match(str(path), match_pos):
|
match_pos = len(path_str)
|
||||||
|
if match is None or match(path_str, match_pos):
|
||||||
yield from select_next(path, exists)
|
yield from select_next(path, exists)
|
||||||
stack = [path]
|
stack = [path]
|
||||||
while stack:
|
while stack:
|
||||||
|
@ -489,7 +496,7 @@ class _GlobberBase:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if is_dir or not dir_only:
|
if is_dir or not dir_only:
|
||||||
entry_path_str = str(entry_path)
|
entry_path_str = self.stringify_path(entry_path)
|
||||||
if dir_only:
|
if dir_only:
|
||||||
entry_path = self.concat_path(entry_path, self.sep)
|
entry_path = self.concat_path(entry_path, self.sep)
|
||||||
if match is None or match(entry_path_str, match_pos):
|
if match is None or match(entry_path_str, match_pos):
|
||||||
|
@ -529,19 +536,6 @@ class _StringGlobber(_GlobberBase):
|
||||||
entries = list(scandir_it)
|
entries = list(scandir_it)
|
||||||
return ((entry, entry.name, entry.path) for entry in entries)
|
return ((entry, entry.name, entry.path) for entry in entries)
|
||||||
|
|
||||||
|
|
||||||
class _PathGlobber(_GlobberBase):
|
|
||||||
"""Provides shell-style pattern matching and globbing for pathlib paths.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lexists(path):
|
def stringify_path(path):
|
||||||
return path.info.exists(follow_symlinks=False)
|
return path # Already a string.
|
||||||
|
|
||||||
@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(str(path) + text)
|
|
||||||
|
|
|
@ -28,8 +28,9 @@ except ImportError:
|
||||||
|
|
||||||
from pathlib._os import (
|
from pathlib._os import (
|
||||||
PathInfo, DirEntryInfo,
|
PathInfo, DirEntryInfo,
|
||||||
|
magic_open, vfspath,
|
||||||
ensure_different_files, ensure_distinct_paths,
|
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
|
# os.symlink() incorrectly creates a file-symlink on Windows. Avoid
|
||||||
# this by passing *target_is_dir* to os.symlink() on Windows.
|
# this by passing *target_is_dir* to os.symlink() on Windows.
|
||||||
def _copy_from_symlink(self, source, preserve_metadata=False):
|
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:
|
if preserve_metadata:
|
||||||
copy_info(source.info, self, follow_symlinks=False)
|
copy_info(source.info, self, follow_symlinks=False)
|
||||||
else:
|
else:
|
||||||
def _copy_from_symlink(self, source, preserve_metadata=False):
|
def _copy_from_symlink(self, source, preserve_metadata=False):
|
||||||
os.symlink(str(source.readlink()), self)
|
os.symlink(vfspath(source.readlink()), self)
|
||||||
if preserve_metadata:
|
if preserve_metadata:
|
||||||
copy_info(source.info, self, follow_symlinks=False)
|
copy_info(source.info, self, follow_symlinks=False)
|
||||||
|
|
||||||
|
|
|
@ -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}")
|
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):
|
def ensure_distinct_paths(source, target):
|
||||||
"""
|
"""
|
||||||
Raise OSError(EINVAL) if the other path is within this path.
|
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")
|
err = OSError(EINVAL, "Source path is a parent of target path")
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
err.filename = str(source)
|
err.filename = vfspath(source)
|
||||||
err.filename2 = str(target)
|
err.filename2 = vfspath(target)
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
|
@ -247,8 +267,8 @@ def ensure_different_files(source, target):
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
return
|
return
|
||||||
err = OSError(EINVAL, "Source and target are the same file")
|
err = OSError(EINVAL, "Source and target are the same file")
|
||||||
err.filename = str(source)
|
err.filename = vfspath(source)
|
||||||
err.filename2 = str(target)
|
err.filename2 = vfspath(target)
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ Protocols for supporting classes in pathlib.
|
||||||
|
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from glob import _PathGlobber
|
from glob import _GlobberBase
|
||||||
from io import text_encoding
|
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 pathlib import PurePath, Path
|
||||||
from typing import Optional, Protocol, runtime_checkable
|
from typing import Optional, Protocol, runtime_checkable
|
||||||
|
|
||||||
|
@ -60,6 +61,25 @@ class PathInfo(Protocol):
|
||||||
def is_symlink(self) -> bool: ...
|
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):
|
class _JoinablePath(ABC):
|
||||||
"""Abstract base class for pure path objects.
|
"""Abstract base class for pure path objects.
|
||||||
|
|
||||||
|
@ -86,20 +106,19 @@ class _JoinablePath(ABC):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __str__(self):
|
def __vfspath__(self):
|
||||||
"""Return the string representation of the path, suitable for
|
"""Return the string representation of the path."""
|
||||||
passing to system calls."""
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def anchor(self):
|
def anchor(self):
|
||||||
"""The concatenation of the drive and root, or ''."""
|
"""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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""The final path component, if any."""
|
"""The final path component, if any."""
|
||||||
return self.parser.split(str(self))[1]
|
return self.parser.split(vfspath(self))[1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def suffix(self):
|
def suffix(self):
|
||||||
|
@ -135,7 +154,7 @@ class _JoinablePath(ABC):
|
||||||
split = self.parser.split
|
split = self.parser.split
|
||||||
if split(name)[0]:
|
if split(name)[0]:
|
||||||
raise ValueError(f"Invalid name {name!r}")
|
raise ValueError(f"Invalid name {name!r}")
|
||||||
path = str(self)
|
path = vfspath(self)
|
||||||
path = path.removesuffix(split(path)[1]) + name
|
path = path.removesuffix(split(path)[1]) + name
|
||||||
return self.with_segments(path)
|
return self.with_segments(path)
|
||||||
|
|
||||||
|
@ -168,7 +187,7 @@ class _JoinablePath(ABC):
|
||||||
def parts(self):
|
def parts(self):
|
||||||
"""An object providing sequence-like access to the
|
"""An object providing sequence-like access to the
|
||||||
components in the filesystem path."""
|
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:
|
if anchor:
|
||||||
parts.append(anchor)
|
parts.append(anchor)
|
||||||
return tuple(reversed(parts))
|
return tuple(reversed(parts))
|
||||||
|
@ -179,24 +198,24 @@ class _JoinablePath(ABC):
|
||||||
paths) or a totally different path (if one of the arguments is
|
paths) or a totally different path (if one of the arguments is
|
||||||
anchored).
|
anchored).
|
||||||
"""
|
"""
|
||||||
return self.with_segments(str(self), *pathsegments)
|
return self.with_segments(vfspath(self), *pathsegments)
|
||||||
|
|
||||||
def __truediv__(self, key):
|
def __truediv__(self, key):
|
||||||
try:
|
try:
|
||||||
return self.with_segments(str(self), key)
|
return self.with_segments(vfspath(self), key)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __rtruediv__(self, key):
|
def __rtruediv__(self, key):
|
||||||
try:
|
try:
|
||||||
return self.with_segments(key, str(self))
|
return self.with_segments(key, vfspath(self))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
"""The logical parent of the path."""
|
"""The logical parent of the path."""
|
||||||
path = str(self)
|
path = vfspath(self)
|
||||||
parent = self.parser.split(path)[0]
|
parent = self.parser.split(path)[0]
|
||||||
if path != parent:
|
if path != parent:
|
||||||
return self.with_segments(parent)
|
return self.with_segments(parent)
|
||||||
|
@ -206,7 +225,7 @@ class _JoinablePath(ABC):
|
||||||
def parents(self):
|
def parents(self):
|
||||||
"""A sequence of this path's logical parents."""
|
"""A sequence of this path's logical parents."""
|
||||||
split = self.parser.split
|
split = self.parser.split
|
||||||
path = str(self)
|
path = vfspath(self)
|
||||||
parent = split(path)[0]
|
parent = split(path)[0]
|
||||||
parents = []
|
parents = []
|
||||||
while path != parent:
|
while path != parent:
|
||||||
|
@ -223,7 +242,7 @@ class _JoinablePath(ABC):
|
||||||
case_sensitive = self.parser.normcase('Aa') == 'Aa'
|
case_sensitive = self.parser.normcase('Aa') == 'Aa'
|
||||||
globber = _PathGlobber(self.parser.sep, case_sensitive, recursive=True)
|
globber = _PathGlobber(self.parser.sep, case_sensitive, recursive=True)
|
||||||
match = globber.compile(pattern, altsep=self.parser.altsep)
|
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):
|
class _ReadablePath(_JoinablePath):
|
||||||
|
@ -412,7 +431,7 @@ class _WritablePath(_JoinablePath):
|
||||||
while stack:
|
while stack:
|
||||||
src, dst = stack.pop()
|
src, dst = stack.pop()
|
||||||
if not follow_symlinks and src.info.is_symlink():
|
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():
|
elif src.info.is_dir():
|
||||||
children = src.iterdir()
|
children = src.iterdir()
|
||||||
dst.mkdir()
|
dst.mkdir()
|
||||||
|
|
|
@ -9,9 +9,10 @@ import posixpath
|
||||||
from . import is_pypi
|
from . import is_pypi
|
||||||
|
|
||||||
if is_pypi:
|
if is_pypi:
|
||||||
from pathlib_abc import _JoinablePath
|
from pathlib_abc import vfspath, _JoinablePath
|
||||||
else:
|
else:
|
||||||
from pathlib.types import _JoinablePath
|
from pathlib.types import _JoinablePath
|
||||||
|
from pathlib._os import vfspath
|
||||||
|
|
||||||
|
|
||||||
class LexicalPath(_JoinablePath):
|
class LexicalPath(_JoinablePath):
|
||||||
|
@ -22,20 +23,20 @@ class LexicalPath(_JoinablePath):
|
||||||
self._segments = pathsegments
|
self._segments = pathsegments
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(str(self))
|
return hash(vfspath(self))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, LexicalPath):
|
if not isinstance(other, LexicalPath):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return str(self) == str(other)
|
return vfspath(self) == vfspath(other)
|
||||||
|
|
||||||
def __str__(self):
|
def __vfspath__(self):
|
||||||
if not self._segments:
|
if not self._segments:
|
||||||
return ''
|
return ''
|
||||||
return self.parser.join(*self._segments)
|
return self.parser.join(*self._segments)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'{type(self).__name__}({str(self)!r})'
|
return f'{type(self).__name__}({vfspath(self)!r})'
|
||||||
|
|
||||||
def with_segments(self, *pathsegments):
|
def with_segments(self, *pathsegments):
|
||||||
return type(self)(*pathsegments)
|
return type(self)(*pathsegments)
|
||||||
|
|
|
@ -97,7 +97,7 @@ class LocalPathInfo(PathInfo):
|
||||||
__slots__ = ('_path', '_exists', '_is_dir', '_is_file', '_is_symlink')
|
__slots__ = ('_path', '_exists', '_is_dir', '_is_file', '_is_symlink')
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self._path = str(path)
|
self._path = os.fspath(path)
|
||||||
self._exists = None
|
self._exists = None
|
||||||
self._is_dir = None
|
self._is_dir = None
|
||||||
self._is_file = None
|
self._is_file = None
|
||||||
|
@ -139,14 +139,12 @@ class ReadableLocalPath(_ReadablePath, LexicalPath):
|
||||||
Simple implementation of a ReadablePath class for local filesystem paths.
|
Simple implementation of a ReadablePath class for local filesystem paths.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('info',)
|
__slots__ = ('info',)
|
||||||
|
__fspath__ = LexicalPath.__vfspath__
|
||||||
|
|
||||||
def __init__(self, *pathsegments):
|
def __init__(self, *pathsegments):
|
||||||
super().__init__(*pathsegments)
|
super().__init__(*pathsegments)
|
||||||
self.info = LocalPathInfo(self)
|
self.info = LocalPathInfo(self)
|
||||||
|
|
||||||
def __fspath__(self):
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
def __open_rb__(self, buffering=-1):
|
def __open_rb__(self, buffering=-1):
|
||||||
return open(self, 'rb')
|
return open(self, 'rb')
|
||||||
|
|
||||||
|
@ -163,9 +161,7 @@ class WritableLocalPath(_WritablePath, LexicalPath):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
__fspath__ = LexicalPath.__vfspath__
|
||||||
def __fspath__(self):
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
def __open_wb__(self, buffering=-1):
|
def __open_wb__(self, buffering=-1):
|
||||||
return open(self, 'wb')
|
return open(self, 'wb')
|
||||||
|
|
|
@ -16,9 +16,10 @@ from stat import S_IFMT, S_ISDIR, S_ISREG, S_ISLNK
|
||||||
from . import is_pypi
|
from . import is_pypi
|
||||||
|
|
||||||
if is_pypi:
|
if is_pypi:
|
||||||
from pathlib_abc import PathInfo, _ReadablePath, _WritablePath
|
from pathlib_abc import vfspath, PathInfo, _ReadablePath, _WritablePath
|
||||||
else:
|
else:
|
||||||
from pathlib.types import PathInfo, _ReadablePath, _WritablePath
|
from pathlib.types import PathInfo, _ReadablePath, _WritablePath
|
||||||
|
from pathlib._os import vfspath
|
||||||
|
|
||||||
|
|
||||||
class ZipPathGround:
|
class ZipPathGround:
|
||||||
|
@ -34,16 +35,16 @@ class ZipPathGround:
|
||||||
root.zip_file.close()
|
root.zip_file.close()
|
||||||
|
|
||||||
def create_file(self, path, data=b''):
|
def create_file(self, path, data=b''):
|
||||||
path.zip_file.writestr(str(path), data)
|
path.zip_file.writestr(vfspath(path), data)
|
||||||
|
|
||||||
def create_dir(self, path):
|
def create_dir(self, path):
|
||||||
zip_info = zipfile.ZipInfo(str(path) + '/')
|
zip_info = zipfile.ZipInfo(vfspath(path) + '/')
|
||||||
zip_info.external_attr |= stat.S_IFDIR << 16
|
zip_info.external_attr |= stat.S_IFDIR << 16
|
||||||
zip_info.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
|
zip_info.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
|
||||||
path.zip_file.writestr(zip_info, '')
|
path.zip_file.writestr(zip_info, '')
|
||||||
|
|
||||||
def create_symlink(self, path, target):
|
def create_symlink(self, path, target):
|
||||||
zip_info = zipfile.ZipInfo(str(path))
|
zip_info = zipfile.ZipInfo(vfspath(path))
|
||||||
zip_info.external_attr = stat.S_IFLNK << 16
|
zip_info.external_attr = stat.S_IFLNK << 16
|
||||||
path.zip_file.writestr(zip_info, target.encode())
|
path.zip_file.writestr(zip_info, target.encode())
|
||||||
|
|
||||||
|
@ -62,28 +63,28 @@ class ZipPathGround:
|
||||||
self.create_symlink(p.joinpath('brokenLinkLoop'), 'brokenLinkLoop')
|
self.create_symlink(p.joinpath('brokenLinkLoop'), 'brokenLinkLoop')
|
||||||
|
|
||||||
def readtext(self, p):
|
def readtext(self, p):
|
||||||
with p.zip_file.open(str(p), 'r') as f:
|
with p.zip_file.open(vfspath(p), 'r') as f:
|
||||||
f = io.TextIOWrapper(f, encoding='utf-8')
|
f = io.TextIOWrapper(f, encoding='utf-8')
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
def readbytes(self, p):
|
def readbytes(self, p):
|
||||||
with p.zip_file.open(str(p), 'r') as f:
|
with p.zip_file.open(vfspath(p), 'r') as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
readlink = readtext
|
readlink = readtext
|
||||||
|
|
||||||
def isdir(self, p):
|
def isdir(self, p):
|
||||||
path_str = str(p) + "/"
|
path_str = vfspath(p) + "/"
|
||||||
return path_str in p.zip_file.NameToInfo
|
return path_str in p.zip_file.NameToInfo
|
||||||
|
|
||||||
def isfile(self, p):
|
def isfile(self, p):
|
||||||
info = p.zip_file.NameToInfo.get(str(p))
|
info = p.zip_file.NameToInfo.get(vfspath(p))
|
||||||
if info is None:
|
if info is None:
|
||||||
return False
|
return False
|
||||||
return not stat.S_ISLNK(info.external_attr >> 16)
|
return not stat.S_ISLNK(info.external_attr >> 16)
|
||||||
|
|
||||||
def islink(self, p):
|
def islink(self, p):
|
||||||
info = p.zip_file.NameToInfo.get(str(p))
|
info = p.zip_file.NameToInfo.get(vfspath(p))
|
||||||
if info is None:
|
if info is None:
|
||||||
return False
|
return False
|
||||||
return stat.S_ISLNK(info.external_attr >> 16)
|
return stat.S_ISLNK(info.external_attr >> 16)
|
||||||
|
@ -240,20 +241,20 @@ class ReadableZipPath(_ReadablePath):
|
||||||
zip_file.filelist = ZipFileList(zip_file)
|
zip_file.filelist = ZipFileList(zip_file)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((str(self), self.zip_file))
|
return hash((vfspath(self), self.zip_file))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, ReadableZipPath):
|
if not isinstance(other, ReadableZipPath):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return str(self) == str(other) and self.zip_file is other.zip_file
|
return vfspath(self) == vfspath(other) and self.zip_file is other.zip_file
|
||||||
|
|
||||||
def __str__(self):
|
def __vfspath__(self):
|
||||||
if not self._segments:
|
if not self._segments:
|
||||||
return ''
|
return ''
|
||||||
return self.parser.join(*self._segments)
|
return self.parser.join(*self._segments)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'{type(self).__name__}({str(self)!r}, zip_file={self.zip_file!r})'
|
return f'{type(self).__name__}({vfspath(self)!r}, zip_file={self.zip_file!r})'
|
||||||
|
|
||||||
def with_segments(self, *pathsegments):
|
def with_segments(self, *pathsegments):
|
||||||
return type(self)(*pathsegments, zip_file=self.zip_file)
|
return type(self)(*pathsegments, zip_file=self.zip_file)
|
||||||
|
@ -261,7 +262,7 @@ class ReadableZipPath(_ReadablePath):
|
||||||
@property
|
@property
|
||||||
def info(self):
|
def info(self):
|
||||||
tree = self.zip_file.filelist.tree
|
tree = self.zip_file.filelist.tree
|
||||||
return tree.resolve(str(self), follow_symlinks=False)
|
return tree.resolve(vfspath(self), follow_symlinks=False)
|
||||||
|
|
||||||
def __open_rb__(self, buffering=-1):
|
def __open_rb__(self, buffering=-1):
|
||||||
info = self.info.resolve()
|
info = self.info.resolve()
|
||||||
|
@ -301,36 +302,36 @@ class WritableZipPath(_WritablePath):
|
||||||
self.zip_file = zip_file
|
self.zip_file = zip_file
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((str(self), self.zip_file))
|
return hash((vfspath(self), self.zip_file))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, WritableZipPath):
|
if not isinstance(other, WritableZipPath):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return str(self) == str(other) and self.zip_file is other.zip_file
|
return vfspath(self) == vfspath(other) and self.zip_file is other.zip_file
|
||||||
|
|
||||||
def __str__(self):
|
def __vfspath__(self):
|
||||||
if not self._segments:
|
if not self._segments:
|
||||||
return ''
|
return ''
|
||||||
return self.parser.join(*self._segments)
|
return self.parser.join(*self._segments)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'{type(self).__name__}({str(self)!r}, zip_file={self.zip_file!r})'
|
return f'{type(self).__name__}({vfspath(self)!r}, zip_file={self.zip_file!r})'
|
||||||
|
|
||||||
def with_segments(self, *pathsegments):
|
def with_segments(self, *pathsegments):
|
||||||
return type(self)(*pathsegments, zip_file=self.zip_file)
|
return type(self)(*pathsegments, zip_file=self.zip_file)
|
||||||
|
|
||||||
def __open_wb__(self, buffering=-1):
|
def __open_wb__(self, buffering=-1):
|
||||||
return self.zip_file.open(str(self), 'w')
|
return self.zip_file.open(vfspath(self), 'w')
|
||||||
|
|
||||||
def mkdir(self, mode=0o777):
|
def mkdir(self, mode=0o777):
|
||||||
zinfo = zipfile.ZipInfo(str(self) + '/')
|
zinfo = zipfile.ZipInfo(vfspath(self) + '/')
|
||||||
zinfo.external_attr |= stat.S_IFDIR << 16
|
zinfo.external_attr |= stat.S_IFDIR << 16
|
||||||
zinfo.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
|
zinfo.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
|
||||||
self.zip_file.writestr(zinfo, '')
|
self.zip_file.writestr(zinfo, '')
|
||||||
|
|
||||||
def symlink_to(self, target, target_is_directory=False):
|
def symlink_to(self, target, target_is_directory=False):
|
||||||
zinfo = zipfile.ZipInfo(str(self))
|
zinfo = zipfile.ZipInfo(vfspath(self))
|
||||||
zinfo.external_attr = stat.S_IFLNK << 16
|
zinfo.external_attr = stat.S_IFLNK << 16
|
||||||
if target_is_directory:
|
if target_is_directory:
|
||||||
zinfo.external_attr |= 0x10
|
zinfo.external_attr |= 0x10
|
||||||
self.zip_file.writestr(zinfo, str(target))
|
self.zip_file.writestr(zinfo, vfspath(target))
|
||||||
|
|
|
@ -8,6 +8,11 @@ import unittest
|
||||||
from .support import is_pypi
|
from .support import is_pypi
|
||||||
from .support.lexical_path import LexicalWindowsPath
|
from .support.lexical_path import LexicalWindowsPath
|
||||||
|
|
||||||
|
if is_pypi:
|
||||||
|
from pathlib_abc import vfspath
|
||||||
|
else:
|
||||||
|
from pathlib._os import vfspath
|
||||||
|
|
||||||
|
|
||||||
class JoinTestBase:
|
class JoinTestBase:
|
||||||
def test_join(self):
|
def test_join(self):
|
||||||
|
@ -70,17 +75,17 @@ class JoinTestBase:
|
||||||
self.assertEqual(p / './dd:s', P(r'C:/a/b\./dd:s'))
|
self.assertEqual(p / './dd:s', P(r'C:/a/b\./dd:s'))
|
||||||
self.assertEqual(p / 'E:d:s', P('E:d:s'))
|
self.assertEqual(p / 'E:d:s', P('E:d:s'))
|
||||||
|
|
||||||
def test_str(self):
|
def test_vfspath(self):
|
||||||
p = self.cls(r'a\b\c')
|
p = self.cls(r'a\b\c')
|
||||||
self.assertEqual(str(p), 'a\\b\\c')
|
self.assertEqual(vfspath(p), 'a\\b\\c')
|
||||||
p = self.cls(r'c:\a\b\c')
|
p = self.cls(r'c:\a\b\c')
|
||||||
self.assertEqual(str(p), 'c:\\a\\b\\c')
|
self.assertEqual(vfspath(p), 'c:\\a\\b\\c')
|
||||||
p = self.cls('\\\\a\\b\\')
|
p = self.cls('\\\\a\\b\\')
|
||||||
self.assertEqual(str(p), '\\\\a\\b\\')
|
self.assertEqual(vfspath(p), '\\\\a\\b\\')
|
||||||
p = self.cls(r'\\a\b\c')
|
p = self.cls(r'\\a\b\c')
|
||||||
self.assertEqual(str(p), '\\\\a\\b\\c')
|
self.assertEqual(vfspath(p), '\\\\a\\b\\c')
|
||||||
p = self.cls(r'\\a\b\c\d')
|
p = self.cls(r'\\a\b\c\d')
|
||||||
self.assertEqual(str(p), '\\\\a\\b\\c\\d')
|
self.assertEqual(vfspath(p), '\\\\a\\b\\c\\d')
|
||||||
|
|
||||||
def test_parts(self):
|
def test_parts(self):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue