Skip to content

GH-130608: Remove dirs_exist_ok argument from pathlib.Path.copy() #130610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1571,8 +1571,7 @@ Creating files and directories
Copying, moving and deleting
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. method:: Path.copy(target, *, follow_symlinks=True, dirs_exist_ok=False, \
preserve_metadata=False)
.. method:: Path.copy(target, *, follow_symlinks=True, preserve_metadata=False)

Copy this file or directory tree to the given *target*, and return a new
:class:`!Path` instance pointing to *target*.
Expand All @@ -1582,12 +1581,6 @@ Copying, moving and deleting
default), the symlink's target is copied. Otherwise, the symlink is
recreated at the destination.

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.

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
Expand All @@ -1604,7 +1597,7 @@ Copying, moving and deleting


.. method:: Path.copy_into(target_dir, *, follow_symlinks=True, \
dirs_exist_ok=False, preserve_metadata=False)
preserve_metadata=False)

Copy this file or directory tree into the given *target_dir*, which should
be an existing directory. Other arguments are handled identically to
Expand Down
8 changes: 3 additions & 5 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,19 +340,18 @@ def readlink(self):
"""
raise NotImplementedError

def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
preserve_metadata=False):
def copy(self, target, follow_symlinks=True, preserve_metadata=False):
"""
Recursively copy this file or directory tree to the given destination.
"""
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
ensure_distinct_paths(self, target)
copy_file(self, target, follow_symlinks, dirs_exist_ok, preserve_metadata)
copy_file(self, target, follow_symlinks, preserve_metadata)
return target.joinpath() # Empty join to ensure fresh metadata.

def copy_into(self, target_dir, *, follow_symlinks=True,
dirs_exist_ok=False, preserve_metadata=False):
preserve_metadata=False):
"""
Copy this file or directory tree into the given existing directory.
"""
Expand All @@ -364,7 +363,6 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
else:
target = self.with_segments(target_dir, name)
return self.copy(target, follow_symlinks=follow_symlinks,
dirs_exist_ok=dirs_exist_ok,
preserve_metadata=preserve_metadata)


Expand Down
8 changes: 3 additions & 5 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -1093,19 +1093,18 @@ def replace(self, target):
target = self.with_segments(target)
return target

def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
preserve_metadata=False):
def copy(self, target, follow_symlinks=True, preserve_metadata=False):
"""
Recursively copy this file or directory tree to the given destination.
"""
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
ensure_distinct_paths(self, target)
copy_file(self, target, follow_symlinks, dirs_exist_ok, preserve_metadata)
copy_file(self, target, follow_symlinks, preserve_metadata)
return target.joinpath() # Empty join to ensure fresh metadata.

def copy_into(self, target_dir, *, follow_symlinks=True,
dirs_exist_ok=False, preserve_metadata=False):
preserve_metadata=False):
"""
Copy this file or directory tree into the given existing directory.
"""
Expand All @@ -1117,7 +1116,6 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
else:
target = self.with_segments(target_dir, name)
return self.copy(target, follow_symlinks=follow_symlinks,
dirs_exist_ok=dirs_exist_ok,
preserve_metadata=preserve_metadata)

def move(self, target):
Expand Down
7 changes: 3 additions & 4 deletions Lib/pathlib/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,7 @@ def ensure_different_files(source, target):
raise err


def copy_file(source, target, follow_symlinks=True, dirs_exist_ok=False,
preserve_metadata=False):
def copy_file(source, target, follow_symlinks=True, preserve_metadata=False):
"""
Recursively copy the given source ReadablePath to the given target WritablePath.
"""
Expand All @@ -254,10 +253,10 @@ def copy_file(source, target, follow_symlinks=True, dirs_exist_ok=False,
target._write_info(info, follow_symlinks=False)
elif info.is_dir():
children = source.iterdir()
target.mkdir(exist_ok=dirs_exist_ok)
target.mkdir()
for src in children:
dst = target.joinpath(src.name)
copy_file(src, dst, follow_symlinks, dirs_exist_ok, preserve_metadata)
copy_file(src, dst, follow_symlinks, preserve_metadata)
if preserve_metadata:
target._write_info(info)
else:
Expand Down
17 changes: 0 additions & 17 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1495,23 +1495,6 @@ def test_copy_dir_to_existing_directory(self):
target.joinpath('dirD').mkdir()
self.assertRaises(FileExistsError, source.copy, target)

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()
result = source.copy(target, dirs_exist_ok=True)
self.assertEqual(result, target)
self.assertTrue(result.info.is_dir())
self.assertTrue(result.joinpath('dirD').info.is_dir())
self.assertTrue(result.joinpath('dirD', 'fileD').info.is_file())
self.assertEqual(result.joinpath('dirD', 'fileD').read_text(),
"this is file D\n")
self.assertTrue(result.joinpath('fileC').info.is_file())
self.assertTrue(result.joinpath('fileC').read_text(),
"this is file C\n")

def test_copy_dir_to_itself(self):
base = self.cls(self.base)
source = base / 'dirC'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Remove *dirs_exist_ok* argument from :meth:`pathlib.Path.copy` and
:meth:`~pathlib.Path.copy_into`. These methods are new in Python 3.14.
Loading