Copying, renaming and deleting
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. method:: Path.copy(target, *, follow_symlinks=True, preserve_metadata=False)
+.. method:: Path.copy(target, *, follow_symlinks=True, dirs_exist_ok=False, \
+ preserve_metadata=False, ignore=None, on_error=None)
- Copy the contents of this file to the *target* file. If *target* specifies
- a file that already exists, it will be replaced.
+ Copy this file or directory tree to the given *target*, and return a new
+ :class:`!Path` instance pointing to *target*.
- If *follow_symlinks* is false, and this file is a symbolic link, *target*
- will be created as a symbolic link. If *follow_symlinks* is true and this
- file is a symbolic link, *target* will be a copy of the symlink target.
+ If the source is a file, the target will be replaced if it is an existing
+ file. If the source is a symlink and *follow_symlinks* is true (the
+ default), the symlink's target is copied. Otherwise, the symlink is
+ recreated at the destination.
- If *preserve_metadata* is false (the default), only the file data is
- guaranteed to be copied. Set *preserve_metadata* to true to ensure that the
- file mode (permissions), flags, last access and modification times, and
- extended attributes are copied where supported. This argument has no effect
- on Windows, where metadata is always preserved when copying.
+ If the source is a directory and *dirs_exist_ok* is false (the default), a
+ :exc:`FileExistsError` is raised if the target is an existing directory.
+ If *dirs_exists_ok* is true, the copying operation will overwrite
+ existing files within the destination tree with corresponding files
+ from the source tree.
- .. versionadded:: 3.14
-
-
-.. method:: Path.copytree(target, *, follow_symlinks=True, \
- preserve_metadata=False, dirs_exist_ok=False, \
- ignore=None, on_error=None)
-
- Recursively copy this directory tree to the given destination.
-
- If a symlink is encountered in the source tree, and *follow_symlinks* is
- true (the default), the symlink's target is copied. Otherwise, the symlink
- is recreated in the destination tree.
-
- If *preserve_metadata* is false (the default), only the directory structure
+ If *preserve_metadata* is false (the default), only directory structures
and file data are guaranteed to be copied. Set *preserve_metadata* to true
to ensure that file and directory permissions, flags, last access and
modification times, and extended attributes are copied where supported.
- This argument has no effect on Windows, where metadata is always preserved
- when copying.
-
- If the destination is an existing directory and *dirs_exist_ok* is false
- (the default), a :exc:`FileExistsError` is raised. Otherwise, the copying
- operation will continue if it encounters existing directories, and files
- within the destination tree will be overwritten by corresponding files from
- the source tree.
+ This argument has no effect when copying files on Windows (where
+ metadata is always preserved).
If *ignore* is given, it should be a callable accepting one argument: a
- file or directory path within the source tree. The callable may return true
- to suppress copying of the path.
+ source file or directory path. The callable may return true to suppress
+ copying of the path.
If *on_error* is given, it should be a callable accepting one argument: an
instance of :exc:`OSError`. The callable may re-raise the exception or do
* Add methods to :class:`pathlib.Path` to recursively copy or remove files:
- * :meth:`~pathlib.Path.copy` copies the content of one file to another, like
- :func:`shutil.copyfile`.
- * :meth:`~pathlib.Path.copytree` copies one directory tree to another, like
- :func:`shutil.copytree`.
+ * :meth:`~pathlib.Path.copy` copies a file or directory tree to a given
+ destination.
* :meth:`~pathlib.Path.delete` removes a file or directory tree.
(Contributed by Barney Gale in :gh:`73991`.)
operating systems.
"""
-from ._os import *
-from ._local import *
+from pathlib._abc import *
+from pathlib._local import *
-__all__ = (_os.__all__ +
+__all__ = (_abc.__all__ +
_local.__all__)
import posixpath
from glob import _GlobberBase, _no_recurse_symlinks
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
-from ._os import UnsupportedOperation, copyfileobj
+from pathlib._os import copyfileobj
+
+
+__all__ = ["UnsupportedOperation"]
+
+
+class UnsupportedOperation(NotImplementedError):
+ """An exception that is raised when an unsupported operation is attempted.
+ """
+ pass
@functools.cache
"""
raise UnsupportedOperation(self._unsupported_msg('symlink_to()'))
+ def _symlink_to_target_of(self, link):
+ """
+ Make this path a symlink with the same target as the given link. This
+ is used by copy().
+ """
+ self.symlink_to(link.readlink())
+
def hardlink_to(self, target):
"""
Make this path a hard link pointing to the same file as *target*.
metadata = self._read_metadata(keys, follow_symlinks=follow_symlinks)
target._write_metadata(metadata, follow_symlinks=follow_symlinks)
- def copy(self, target, *, follow_symlinks=True, preserve_metadata=False):
+ def _copy_file(self, target):
"""
- Copy the contents of this file to the given target. If this file is a
- symlink and follow_symlinks is false, a symlink will be created at the
- target.
+ Copy the contents of this file to the given target.
"""
- if not isinstance(target, PathBase):
- target = self.with_segments(target)
if self._samefile_safe(target):
raise OSError(f"{self!r} and {target!r} are the same file")
- if not follow_symlinks and self.is_symlink():
- target.symlink_to(self.readlink())
- if preserve_metadata:
- self._copy_metadata(target, follow_symlinks=False)
- return
with self.open('rb') as source_f:
try:
with target.open('wb') as target_f:
f'Directory does not exist: {target}') from e
else:
raise
- if preserve_metadata:
- self._copy_metadata(target)
- def copytree(self, target, *, follow_symlinks=True,
- preserve_metadata=False, dirs_exist_ok=False,
- ignore=None, on_error=None):
+ def copy(self, target, *, follow_symlinks=True, dirs_exist_ok=False,
+ preserve_metadata=False, ignore=None, on_error=None):
"""
- Recursively copy this directory tree to the given destination.
+ Recursively copy this file or directory tree to the given destination.
"""
if not isinstance(target, PathBase):
target = self.with_segments(target)
- if on_error is None:
- def on_error(err):
- raise err
stack = [(self, target)]
while stack:
- source_dir, target_dir = stack.pop()
+ src, dst = stack.pop()
try:
- sources = source_dir.iterdir()
- target_dir.mkdir(exist_ok=dirs_exist_ok)
- if preserve_metadata:
- source_dir._copy_metadata(target_dir)
- for source in sources:
- if ignore and ignore(source):
- continue
- try:
- if source.is_dir(follow_symlinks=follow_symlinks):
- stack.append((source, target_dir.joinpath(source.name)))
- else:
- source.copy(target_dir.joinpath(source.name),
- follow_symlinks=follow_symlinks,
- preserve_metadata=preserve_metadata)
- except OSError as err:
- on_error(err)
+ if not follow_symlinks and src.is_symlink():
+ dst._symlink_to_target_of(src)
+ if preserve_metadata:
+ src._copy_metadata(dst, follow_symlinks=False)
+ elif src.is_dir():
+ children = src.iterdir()
+ dst.mkdir(exist_ok=dirs_exist_ok)
+ for child in children:
+ if not (ignore and ignore(child)):
+ stack.append((child, dst.joinpath(child.name)))
+ if preserve_metadata:
+ src._copy_metadata(dst)
+ else:
+ src._copy_file(dst)
+ if preserve_metadata:
+ src._copy_metadata(dst)
except OSError as err:
+ if on_error is None:
+ raise
on_error(err)
+ return target
def rename(self, target):
"""
except ImportError:
grp = None
-from ._os import (UnsupportedOperation, copyfile, file_metadata_keys,
- read_file_metadata, write_file_metadata)
-from ._abc import PurePathBase, PathBase
+from pathlib._os import (copyfile, file_metadata_keys, read_file_metadata,
+ write_file_metadata)
+from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
__all__ = [
_write_metadata = write_file_metadata
if copyfile:
- def copy(self, target, *, follow_symlinks=True, preserve_metadata=False):
+ def _copy_file(self, target):
"""
- Copy the contents of this file to the given target. If this file is a
- symlink and follow_symlinks is false, a symlink will be created at the
- target.
+ Copy the contents of this file to the given target.
"""
try:
target = os.fspath(target)
except TypeError:
if not isinstance(target, PathBase):
raise
+ PathBase._copy_file(self, target)
else:
- try:
- copyfile(os.fspath(self), target, follow_symlinks)
- return
- except UnsupportedOperation:
- pass # Fall through to generic code.
- PathBase.copy(self, target, follow_symlinks=follow_symlinks,
- preserve_metadata=preserve_metadata)
+ copyfile(os.fspath(self), target)
def chmod(self, mode, *, follow_symlinks=True):
"""
"""
os.symlink(target, self, target_is_directory)
+ if os.name == 'nt':
+ def _symlink_to_target_of(self, link):
+ """
+ Make this path a symlink with the same target as the given link.
+ This is used by copy().
+ """
+ self.symlink_to(link.readlink(), link.is_dir())
+
if hasattr(os, "link"):
def hardlink_to(self, target):
"""
_winapi = None
-__all__ = ["UnsupportedOperation"]
-
-
-class UnsupportedOperation(NotImplementedError):
- """An exception that is raised when an unsupported operation is attempted.
- """
- pass
-
-
def get_copy_blocksize(infd):
"""Determine blocksize for fastcopying on Linux.
Hopefully the whole file will be copied in a single call.
copyfd = None
-if _winapi and hasattr(_winapi, 'CopyFile2') and hasattr(os.stat_result, 'st_file_attributes'):
- def _is_dirlink(path):
- try:
- st = os.lstat(path)
- except (OSError, ValueError):
- return False
- return (st.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY and
- st.st_reparse_tag == stat.IO_REPARSE_TAG_SYMLINK)
-
- def copyfile(source, target, follow_symlinks):
+if _winapi and hasattr(_winapi, 'CopyFile2'):
+ def copyfile(source, target):
"""
Copy from one file to another using CopyFile2 (Windows only).
"""
- if follow_symlinks:
- _winapi.CopyFile2(source, target, 0)
- else:
- # Use COPY_FILE_COPY_SYMLINK to copy a file symlink.
- flags = _winapi.COPY_FILE_COPY_SYMLINK
- try:
- _winapi.CopyFile2(source, target, flags)
- return
- except OSError as err:
- # Check for ERROR_ACCESS_DENIED
- if err.winerror == 5 and _is_dirlink(source):
- pass
- else:
- raise
-
- # Add COPY_FILE_DIRECTORY to copy a directory symlink.
- flags |= _winapi.COPY_FILE_DIRECTORY
- try:
- _winapi.CopyFile2(source, target, flags)
- except OSError as err:
- # Check for ERROR_INVALID_PARAMETER
- if err.winerror == 87:
- raise UnsupportedOperation(err) from None
- else:
- raise
+ _winapi.CopyFile2(source, target, 0)
else:
copyfile = None
@unittest.skipIf(sys.platform == "win32" or sys.platform == "wasi", "directories are always readable on Windows and WASI")
@unittest.skipIf(root_in_posix, "test fails with root privilege")
- def test_copytree_no_read_permission(self):
+ def test_copy_dir_no_read_permission(self):
base = self.cls(self.base)
source = base / 'dirE'
target = base / 'copyE'
- self.assertRaises(PermissionError, source.copytree, target)
+ self.assertRaises(PermissionError, source.copy, target)
self.assertFalse(target.exists())
errors = []
- source.copytree(target, on_error=errors.append)
+ source.copy(target, on_error=errors.append)
self.assertEqual(len(errors), 1)
self.assertIsInstance(errors[0], PermissionError)
self.assertFalse(target.exists())
- def test_copytree_preserve_metadata(self):
+ def test_copy_dir_preserve_metadata(self):
base = self.cls(self.base)
source = base / 'dirC'
if hasattr(os, 'chmod'):
if hasattr(os, 'chflags') and hasattr(stat, 'UF_NODUMP'):
os.chflags(source / 'fileC', stat.UF_NODUMP)
target = base / 'copyA'
- source.copytree(target, preserve_metadata=True)
+ source.copy(target, preserve_metadata=True)
for subpath in ['.', 'fileC', 'dirD', 'dirD/fileD']:
source_st = source.joinpath(subpath).stat()
self.assertEqual(source_st.st_flags, target_st.st_flags)
@os_helper.skip_unless_xattr
- def test_copytree_preserve_metadata_xattrs(self):
+ def test_copy_dir_preserve_metadata_xattrs(self):
base = self.cls(self.base)
source = base / 'dirC'
source_file = source.joinpath('dirD', 'fileD')
os.setxattr(source_file, b'user.foo', b'42')
target = base / 'copyA'
- source.copytree(target, preserve_metadata=True)
+ source.copy(target, preserve_metadata=True)
target_file = target.joinpath('dirD', 'fileD')
self.assertEqual(os.getxattr(target_file, b'user.foo'), b'42')
import stat
import unittest
-from pathlib._os import UnsupportedOperation
-from pathlib._abc import ParserBase, PurePathBase, PathBase
+from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
import posixpath
from test.support import is_wasi
base = self.cls(self.base)
source = base / 'fileA'
target = base / 'copyA'
- source.copy(target)
+ result = source.copy(target)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertEqual(source.read_text(), target.read_text())
- def test_copy_directory(self):
- base = self.cls(self.base)
- source = base / 'dirA'
- target = base / 'copyA'
- with self.assertRaises(OSError):
- source.copy(target)
-
@needs_symlinks
def test_copy_symlink_follow_symlinks_true(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
- source.copy(target)
+ result = source.copy(target)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertFalse(target.is_symlink())
self.assertEqual(source.read_text(), target.read_text())
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
- source.copy(target, follow_symlinks=False)
+ result = source.copy(target, follow_symlinks=False)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())
base = self.cls(self.base)
source = base / 'linkB'
target = base / 'copyA'
- source.copy(target, follow_symlinks=False)
+ result = source.copy(target, follow_symlinks=False)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())
- def test_copy_to_existing_file(self):
+ def test_copy_file_to_existing_file(self):
base = self.cls(self.base)
source = base / 'fileA'
target = base / 'dirB' / 'fileB'
- source.copy(target)
+ result = source.copy(target)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertEqual(source.read_text(), target.read_text())
- def test_copy_to_existing_directory(self):
+ def test_copy_file_to_existing_directory(self):
base = self.cls(self.base)
source = base / 'fileA'
target = base / 'dirA'
source.copy(target)
@needs_symlinks
- def test_copy_to_existing_symlink(self):
+ def test_copy_file_to_existing_symlink(self):
base = self.cls(self.base)
source = base / 'dirB' / 'fileB'
target = base / 'linkA'
real_target = base / 'fileA'
- source.copy(target)
+ result = source.copy(target)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertTrue(real_target.exists())
self.assertEqual(source.read_text(), real_target.read_text())
@needs_symlinks
- def test_copy_to_existing_symlink_follow_symlinks_false(self):
+ def test_copy_file_to_existing_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'dirB' / 'fileB'
target = base / 'linkA'
real_target = base / 'fileA'
- source.copy(target, follow_symlinks=False)
+ result = source.copy(target, follow_symlinks=False)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertTrue(real_target.exists())
self.assertFalse(real_target.is_symlink())
self.assertEqual(source.read_text(), real_target.read_text())
- def test_copy_empty(self):
+ def test_copy_file_empty(self):
base = self.cls(self.base)
source = base / 'empty'
target = base / 'copyA'
source.write_bytes(b'')
- source.copy(target)
+ result = source.copy(target)
+ self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertEqual(target.read_bytes(), b'')
- def test_copytree_simple(self):
+ def test_copy_dir_simple(self):
base = self.cls(self.base)
source = base / 'dirC'
target = base / 'copyC'
- source.copytree(target)
+ result = source.copy(target)
+ self.assertEqual(result, target)
self.assertTrue(target.is_dir())
self.assertTrue(target.joinpath('dirD').is_dir())
self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
self.assertTrue(target.joinpath('fileC').read_text(),
"this is file C\n")
- def test_copytree_complex(self, follow_symlinks=True):
+ def test_copy_dir_complex(self, follow_symlinks=True):
def ordered_walk(path):
for dirpath, dirnames, filenames in path.walk(follow_symlinks=follow_symlinks):
dirnames.sort()
# Perform the copy
target = base / 'copyC'
- source.copytree(target, follow_symlinks=follow_symlinks)
+ result = source.copy(target, follow_symlinks=follow_symlinks)
+ self.assertEqual(result, target)
# Compare the source and target trees
source_walk = ordered_walk(source)
self.assertEqual(source_file.read_bytes(), target_file.read_bytes())
self.assertEqual(source_file.readlink(), target_file.readlink())
- def test_copytree_complex_follow_symlinks_false(self):
- self.test_copytree_complex(follow_symlinks=False)
+ def test_copy_dir_complex_follow_symlinks_false(self):
+ self.test_copy_dir_complex(follow_symlinks=False)
- def test_copytree_to_existing_directory(self):
+ def test_copy_dir_to_existing_directory(self):
base = self.cls(self.base)
source = base / 'dirC'
target = base / 'copyC'
target.mkdir()
target.joinpath('dirD').mkdir()
- self.assertRaises(FileExistsError, source.copytree, target)
+ self.assertRaises(FileExistsError, source.copy, target)
- def test_copytree_to_existing_directory_dirs_exist_ok(self):
+ def test_copy_dir_to_existing_directory_dirs_exist_ok(self):
base = self.cls(self.base)
source = base / 'dirC'
target = base / 'copyC'
target.mkdir()
target.joinpath('dirD').mkdir()
- source.copytree(target, dirs_exist_ok=True)
+ result = source.copy(target, dirs_exist_ok=True)
+ self.assertEqual(result, target)
self.assertTrue(target.is_dir())
self.assertTrue(target.joinpath('dirD').is_dir())
self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
self.assertTrue(target.joinpath('fileC').read_text(),
"this is file C\n")
- def test_copytree_file(self):
+ def test_copy_missing_on_error(self):
base = self.cls(self.base)
- source = base / 'fileA'
- target = base / 'copyA'
- self.assertRaises(NotADirectoryError, source.copytree, target)
-
- def test_copytree_file_on_error(self):
- base = self.cls(self.base)
- source = base / 'fileA'
+ source = base / 'foo'
target = base / 'copyA'
errors = []
- source.copytree(target, on_error=errors.append)
+ result = source.copy(target, on_error=errors.append)
+ self.assertEqual(result, target)
self.assertEqual(len(errors), 1)
- self.assertIsInstance(errors[0], NotADirectoryError)
+ self.assertIsInstance(errors[0], FileNotFoundError)
- def test_copytree_ignore_false(self):
+ def test_copy_dir_ignore_false(self):
base = self.cls(self.base)
source = base / 'dirC'
target = base / 'copyC'
def ignore_false(path):
ignores.append(path)
return False
- source.copytree(target, ignore=ignore_false)
+ result = source.copy(target, ignore=ignore_false)
+ self.assertEqual(result, target)
self.assertEqual(set(ignores), {
source / 'dirD',
source / 'dirD' / 'fileD',
self.assertTrue(target.joinpath('fileC').read_text(),
"this is file C\n")
- def test_copytree_ignore_true(self):
+ def test_copy_dir_ignore_true(self):
base = self.cls(self.base)
source = base / 'dirC'
target = base / 'copyC'
def ignore_true(path):
ignores.append(path)
return True
- source.copytree(target, ignore=ignore_true)
+ result = source.copy(target, ignore=ignore_true)
+ self.assertEqual(result, target)
self.assertEqual(set(ignores), {
source / 'dirD',
source / 'fileC',
self.assertFalse(target.joinpath('novel.txt').exists())
@needs_symlinks
- def test_copytree_dangling_symlink(self):
+ def test_copy_dangling_symlink(self):
base = self.cls(self.base)
source = base / 'source'
target = base / 'target'
source.mkdir()
source.joinpath('link').symlink_to('nonexistent')
- self.assertRaises(FileNotFoundError, source.copytree, target)
+ self.assertRaises(FileNotFoundError, source.copy, target)
target2 = base / 'target2'
- source.copytree(target2, follow_symlinks=False)
+ result = source.copy(target2, follow_symlinks=False)
+ self.assertEqual(result, target2)
self.assertTrue(target2.joinpath('link').is_symlink())
self.assertEqual(target2.joinpath('link').readlink(), self.cls('nonexistent'))
-Add :meth:`pathlib.Path.copy`, which copies the content of one file to another,
-like :func:`shutil.copyfile`.
+Add :meth:`pathlib.Path.copy`, which copies a file or directory to another.
+++ /dev/null
-Add :meth:`pathlib.Path.copytree`, which recursively copies a directory.