mirror of
https://github.com/python/cpython.git
synced 2025-10-21 06:02:21 +00:00
GH-125413: Move pathlib.Path.copy()
implementation alongside Path.info
(#129856)
Move pathlib's private `CopyReader`, `LocalCopyReader`, `CopyWriter` and `LocalCopyWriter` classes into `pathlib._os`, where they can live alongside the low-level copying functions (`copyfileobj()` etc) and high-level path querying interface (`PathInfo`). This sets the stage for merging `LocalCopyReader` into `PathInfo`. No change of behaviour; just moving some code around.
This commit is contained in:
parent
d5796e64e0
commit
c88dacb391
3 changed files with 297 additions and 300 deletions
|
@ -12,11 +12,9 @@ WritablePath.
|
|||
"""
|
||||
|
||||
import functools
|
||||
import io
|
||||
import posixpath
|
||||
from errno import EINVAL
|
||||
from glob import _PathGlobber, _no_recurse_symlinks
|
||||
from pathlib._os import copyfileobj
|
||||
from pathlib._os import magic_open, CopyReader, CopyWriter
|
||||
|
||||
|
||||
@functools.cache
|
||||
|
@ -41,162 +39,6 @@ def _explode_path(path):
|
|||
return path, names
|
||||
|
||||
|
||||
def magic_open(path, mode='r', buffering=-1, encoding=None, errors=None,
|
||||
newline=None):
|
||||
"""
|
||||
Open the file pointed to by this path and return a file object, as
|
||||
the built-in open() function does.
|
||||
"""
|
||||
try:
|
||||
return io.open(path, mode, buffering, encoding, errors, newline)
|
||||
except TypeError:
|
||||
pass
|
||||
cls = type(path)
|
||||
text = 'b' not in mode
|
||||
mode = ''.join(sorted(c for c in mode if c not in 'bt'))
|
||||
if text:
|
||||
try:
|
||||
attr = getattr(cls, f'__open_{mode}__')
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return attr(path, buffering, encoding, errors, newline)
|
||||
|
||||
try:
|
||||
attr = getattr(cls, f'__open_{mode}b__')
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
stream = attr(path, buffering)
|
||||
if text:
|
||||
stream = io.TextIOWrapper(stream, encoding, errors, newline)
|
||||
return stream
|
||||
|
||||
raise TypeError(f"{cls.__name__} can't be opened with mode {mode!r}")
|
||||
|
||||
|
||||
class CopyReader:
|
||||
"""
|
||||
Class that implements the "read" part of copying between path objects.
|
||||
An instance of this class is available from the ReadablePath._copy_reader
|
||||
property.
|
||||
"""
|
||||
__slots__ = ('_path',)
|
||||
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
|
||||
_readable_metakeys = frozenset()
|
||||
|
||||
def _read_metadata(self, metakeys, *, follow_symlinks=True):
|
||||
"""
|
||||
Returns path metadata as a dict with string keys.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CopyWriter:
|
||||
"""
|
||||
Class that implements the "write" part of copying between path objects. An
|
||||
instance of this class is available from the WritablePath._copy_writer
|
||||
property.
|
||||
"""
|
||||
__slots__ = ('_path',)
|
||||
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
|
||||
_writable_metakeys = frozenset()
|
||||
|
||||
def _write_metadata(self, metadata, *, follow_symlinks=True):
|
||||
"""
|
||||
Sets path metadata from the given dict with string keys.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _create(self, source, follow_symlinks, dirs_exist_ok, preserve_metadata):
|
||||
self._ensure_distinct_path(source)
|
||||
if preserve_metadata:
|
||||
metakeys = self._writable_metakeys & source._copy_reader._readable_metakeys
|
||||
else:
|
||||
metakeys = None
|
||||
if not follow_symlinks and source.is_symlink():
|
||||
self._create_symlink(source, metakeys)
|
||||
elif source.is_dir():
|
||||
self._create_dir(source, metakeys, follow_symlinks, dirs_exist_ok)
|
||||
else:
|
||||
self._create_file(source, metakeys)
|
||||
return self._path
|
||||
|
||||
def _create_dir(self, source, metakeys, follow_symlinks, dirs_exist_ok):
|
||||
"""Copy the given directory to our path."""
|
||||
children = list(source.iterdir())
|
||||
self._path.mkdir(exist_ok=dirs_exist_ok)
|
||||
for src in children:
|
||||
dst = self._path.joinpath(src.name)
|
||||
if not follow_symlinks and src.is_symlink():
|
||||
dst._copy_writer._create_symlink(src, metakeys)
|
||||
elif src.is_dir():
|
||||
dst._copy_writer._create_dir(src, metakeys, follow_symlinks, dirs_exist_ok)
|
||||
else:
|
||||
dst._copy_writer._create_file(src, metakeys)
|
||||
if metakeys:
|
||||
metadata = source._copy_reader._read_metadata(metakeys)
|
||||
if metadata:
|
||||
self._write_metadata(metadata)
|
||||
|
||||
def _create_file(self, source, metakeys):
|
||||
"""Copy the given file to our path."""
|
||||
self._ensure_different_file(source)
|
||||
with magic_open(source, 'rb') as source_f:
|
||||
try:
|
||||
with magic_open(self._path, 'wb') as target_f:
|
||||
copyfileobj(source_f, target_f)
|
||||
except IsADirectoryError as e:
|
||||
if not self._path.exists():
|
||||
# Raise a less confusing exception.
|
||||
raise FileNotFoundError(
|
||||
f'Directory does not exist: {self._path}') from e
|
||||
raise
|
||||
if metakeys:
|
||||
metadata = source._copy_reader._read_metadata(metakeys)
|
||||
if metadata:
|
||||
self._write_metadata(metadata)
|
||||
|
||||
def _create_symlink(self, source, metakeys):
|
||||
"""Copy the given symbolic link to our path."""
|
||||
self._path.symlink_to(source.readlink())
|
||||
if metakeys:
|
||||
metadata = source._copy_reader._read_metadata(metakeys, follow_symlinks=False)
|
||||
if metadata:
|
||||
self._write_metadata(metadata, follow_symlinks=False)
|
||||
|
||||
def _ensure_different_file(self, source):
|
||||
"""
|
||||
Raise OSError(EINVAL) if both paths refer to the same file.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _ensure_distinct_path(self, source):
|
||||
"""
|
||||
Raise OSError(EINVAL) if the other path is within this path.
|
||||
"""
|
||||
# Note: there is no straightforward, foolproof algorithm to determine
|
||||
# if one directory is within another (a particularly perverse example
|
||||
# would be a single network share mounted in one location via NFS, and
|
||||
# in another location via CIFS), so we simply checks whether the
|
||||
# other path is lexically equal to, or within, this path.
|
||||
if source == self._path:
|
||||
err = OSError(EINVAL, "Source and target are the same path")
|
||||
elif source in self._path.parents:
|
||||
err = OSError(EINVAL, "Source path is a parent of target path")
|
||||
else:
|
||||
return
|
||||
err.filename = str(source)
|
||||
err.filename2 = str(self._path)
|
||||
raise err
|
||||
|
||||
|
||||
class JoinablePath:
|
||||
"""Base class for pure path objects.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue