summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/pathlib.rst21
-rw-r--r--Doc/whatsnew/3.14.rst8
-rw-r--r--Lib/pathlib/_abc.py31
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py8
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py30
-rw-r--r--Misc/NEWS.d/next/Library/2024-08-25-16-59-20.gh-issue-73991.1w8u3K.rst2
6 files changed, 96 insertions, 4 deletions
diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst
index 9f5f10a..0b6a6a3 100644
--- a/Doc/library/pathlib.rst
+++ b/Doc/library/pathlib.rst
@@ -1575,6 +1575,18 @@ Copying, moving and deleting
.. versionadded:: 3.14
+.. method:: Path.copy_into(target_dir, *, follow_symlinks=True, \
+ dirs_exist_ok=False, preserve_metadata=False, \
+ ignore=None, on_error=None)
+
+ Copy this file or directory tree into the given *target_dir*, which should
+ be an existing directory. Other arguments are handled identically to
+ :meth:`Path.copy`. Returns a new :class:`!Path` instance pointing to the
+ copy.
+
+ .. versionadded:: 3.14
+
+
.. method:: Path.rename(target)
Rename this file or directory to the given *target*, and return a new
@@ -1633,6 +1645,15 @@ Copying, moving and deleting
.. versionadded:: 3.14
+.. method:: Path.move_into(target_dir)
+
+ Move this file or directory tree into the given *target_dir*, which should
+ be an existing directory. Returns a new :class:`!Path` instance pointing to
+ the moved path.
+
+ .. versionadded:: 3.14
+
+
.. method:: Path.unlink(missing_ok=False)
Remove this file or symbolic link. If the path points to a directory,
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index e5c0fda..34434e4 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -188,10 +188,10 @@ pathlib
* Add methods to :class:`pathlib.Path` to recursively copy, move, or remove
files and directories:
- * :meth:`~pathlib.Path.copy` copies a file or directory tree to a given
- destination.
- * :meth:`~pathlib.Path.move` moves a file or directory tree to a given
- destination.
+ * :meth:`~pathlib.Path.copy` copies a file or directory tree to a destination.
+ * :meth:`~pathlib.Path.copy_into` copies *into* a destination directory.
+ * :meth:`~pathlib.Path.move` moves a file or directory tree to a destination.
+ * :meth:`~pathlib.Path.move_into` moves *into* a destination directory.
* :meth:`~pathlib.Path.delete` removes a file or directory tree.
(Contributed by Barney Gale in :gh:`73991`.)
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index 93758b1..0c76480 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -904,6 +904,24 @@ class PathBase(PurePathBase):
on_error(err)
return target
+ def copy_into(self, target_dir, *, follow_symlinks=True,
+ dirs_exist_ok=False, preserve_metadata=False, ignore=None,
+ on_error=None):
+ """
+ Copy this file or directory tree into the given existing directory.
+ """
+ name = self.name
+ if not name:
+ raise ValueError(f"{self!r} has an empty name")
+ elif isinstance(target_dir, PathBase):
+ target = target_dir / name
+ 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, ignore=ignore,
+ on_error=on_error)
+
def rename(self, target):
"""
Rename this path to the target path.
@@ -947,6 +965,19 @@ class PathBase(PurePathBase):
self.delete()
return target
+ def move_into(self, target_dir):
+ """
+ Move this file or directory tree into the given existing directory.
+ """
+ name = self.name
+ if not name:
+ raise ValueError(f"{self!r} has an empty name")
+ elif isinstance(target_dir, PathBase):
+ target = target_dir / name
+ else:
+ target = self.with_segments(target_dir, name)
+ return self.move(target)
+
def chmod(self, mode, *, follow_symlinks=True):
"""
Change the permissions of the path, like os.chmod().
diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py
index 4d38246..080b8df 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -861,6 +861,14 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
def test_move_dangling_symlink_other_fs(self):
self.test_move_dangling_symlink()
+ @patch_replace
+ def test_move_into_other_os(self):
+ self.test_move_into()
+
+ @patch_replace
+ def test_move_into_empty_name_other_os(self):
+ self.test_move_into_empty_name()
+
def test_resolve_nonexist_relative_issue38671(self):
p = self.cls('non', 'exist')
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
index 7f8f614..4a32cb9 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -2072,6 +2072,20 @@ class DummyPathTest(DummyPurePathTest):
self.assertTrue(target2.joinpath('link').is_symlink())
self.assertEqual(target2.joinpath('link').readlink(), self.cls('nonexistent'))
+ def test_copy_into(self):
+ base = self.cls(self.base)
+ source = base / 'fileA'
+ target_dir = base / 'dirA'
+ result = source.copy_into(target_dir)
+ self.assertEqual(result, target_dir / 'fileA')
+ self.assertTrue(result.exists())
+ self.assertEqual(source.read_text(), result.read_text())
+
+ def test_copy_into_empty_name(self):
+ source = self.cls('')
+ target_dir = self.base
+ self.assertRaises(ValueError, source.copy_into, target_dir)
+
def test_move_file(self):
base = self.cls(self.base)
source = base / 'fileA'
@@ -2191,6 +2205,22 @@ class DummyPathTest(DummyPurePathTest):
self.assertTrue(target.is_symlink())
self.assertEqual(source_readlink, target.readlink())
+ def test_move_into(self):
+ base = self.cls(self.base)
+ source = base / 'fileA'
+ source_text = source.read_text()
+ target_dir = base / 'dirA'
+ result = source.move_into(target_dir)
+ self.assertEqual(result, target_dir / 'fileA')
+ self.assertFalse(source.exists())
+ self.assertTrue(result.exists())
+ self.assertEqual(source_text, result.read_text())
+
+ def test_move_into_empty_name(self):
+ source = self.cls('')
+ target_dir = self.base
+ self.assertRaises(ValueError, source.move_into, target_dir)
+
def test_iterdir(self):
P = self.cls
p = P(self.base)
diff --git a/Misc/NEWS.d/next/Library/2024-08-25-16-59-20.gh-issue-73991.1w8u3K.rst b/Misc/NEWS.d/next/Library/2024-08-25-16-59-20.gh-issue-73991.1w8u3K.rst
new file mode 100644
index 0000000..4ad5a06
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-08-25-16-59-20.gh-issue-73991.1w8u3K.rst
@@ -0,0 +1,2 @@
+Add :meth:`pathlib.Path.copy_into` and :meth:`~pathlib.Path.move_into`,
+which copy and move files and directories into *existing* directories.