summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/pathlib/_abc.py21
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py62
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py119
3 files changed, 201 insertions, 1 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index 9943ea4..93758b1 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -14,7 +14,7 @@ resemble pathlib's PurePath and Path respectively.
import functools
import operator
import posixpath
-from errno import EINVAL
+from errno import EINVAL, EXDEV
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 pathlib._os import copyfileobj
@@ -928,6 +928,25 @@ class PathBase(PurePathBase):
"""
raise UnsupportedOperation(self._unsupported_msg('replace()'))
+ def move(self, target):
+ """
+ Recursively move this file or directory tree to the given destination.
+ """
+ self._ensure_different_file(target)
+ try:
+ return self.replace(target)
+ except UnsupportedOperation:
+ pass
+ except TypeError:
+ if not isinstance(target, PathBase):
+ raise
+ except OSError as err:
+ if err.errno != EXDEV:
+ raise
+ target = self.copy(target, follow_symlinks=False, preserve_metadata=True)
+ self.delete()
+ return 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 ad1720c..4d38246 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -45,6 +45,19 @@ delete_use_fd_functions = (
{os.open, os.stat, os.unlink, os.rmdir} <= os.supports_dir_fd and
os.listdir in os.supports_fd and os.stat in os.supports_follow_symlinks)
+def patch_replace(old_test):
+ def new_replace(self, target):
+ raise OSError(errno.EXDEV, "Cross-device link", self, target)
+
+ def new_test(self):
+ old_replace = self.cls.replace
+ self.cls.replace = new_replace
+ try:
+ old_test(self)
+ finally:
+ self.cls.replace = old_replace
+ return new_test
+
#
# Tests for the pure classes.
#
@@ -799,6 +812,55 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
target_file = target.joinpath('dirD', 'fileD')
self.assertEqual(os.getxattr(target_file, b'user.foo'), b'42')
+ @patch_replace
+ def test_move_file_other_fs(self):
+ self.test_move_file()
+
+ @patch_replace
+ def test_move_file_to_file_other_fs(self):
+ self.test_move_file_to_file()
+
+ @patch_replace
+ def test_move_file_to_dir_other_fs(self):
+ self.test_move_file_to_dir()
+
+ @patch_replace
+ def test_move_dir_other_fs(self):
+ self.test_move_dir()
+
+ @patch_replace
+ def test_move_dir_to_dir_other_fs(self):
+ self.test_move_dir_to_dir()
+
+ @patch_replace
+ def test_move_dir_into_itself_other_fs(self):
+ self.test_move_dir_into_itself()
+
+ @patch_replace
+ @needs_symlinks
+ def test_move_file_symlink_other_fs(self):
+ self.test_move_file_symlink()
+
+ @patch_replace
+ @needs_symlinks
+ def test_move_file_symlink_to_itself_other_fs(self):
+ self.test_move_file_symlink_to_itself()
+
+ @patch_replace
+ @needs_symlinks
+ def test_move_dir_symlink_other_fs(self):
+ self.test_move_dir_symlink()
+
+ @patch_replace
+ @needs_symlinks
+ def test_move_dir_symlink_to_itself_other_fs(self):
+ self.test_move_dir_symlink_to_itself()
+
+ @patch_replace
+ @needs_symlinks
+ def test_move_dangling_symlink_other_fs(self):
+ self.test_move_dangling_symlink()
+
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 5b71475..7f8f614 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -2072,6 +2072,125 @@ class DummyPathTest(DummyPurePathTest):
self.assertTrue(target2.joinpath('link').is_symlink())
self.assertEqual(target2.joinpath('link').readlink(), self.cls('nonexistent'))
+ def test_move_file(self):
+ base = self.cls(self.base)
+ source = base / 'fileA'
+ source_text = source.read_text()
+ target = base / 'fileA_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.exists())
+ self.assertEqual(source_text, target.read_text())
+
+ def test_move_file_to_file(self):
+ base = self.cls(self.base)
+ source = base / 'fileA'
+ source_text = source.read_text()
+ target = base / 'dirB' / 'fileB'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.exists())
+ self.assertEqual(source_text, target.read_text())
+
+ def test_move_file_to_dir(self):
+ base = self.cls(self.base)
+ source = base / 'fileA'
+ target = base / 'dirB'
+ self.assertRaises(OSError, source.move, target)
+
+ def test_move_file_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'fileA'
+ self.assertRaises(OSError, source.move, source)
+
+ def test_move_dir(self):
+ base = self.cls(self.base)
+ source = base / 'dirC'
+ target = base / 'dirC_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_dir())
+ self.assertTrue(target.joinpath('dirD').is_dir())
+ self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
+ self.assertEqual(target.joinpath('dirD', 'fileD').read_text(),
+ "this is file D\n")
+ self.assertTrue(target.joinpath('fileC').is_file())
+ self.assertTrue(target.joinpath('fileC').read_text(),
+ "this is file C\n")
+
+ def test_move_dir_to_dir(self):
+ base = self.cls(self.base)
+ source = base / 'dirC'
+ target = base / 'dirB'
+ self.assertRaises(OSError, source.move, target)
+ self.assertTrue(source.exists())
+ self.assertTrue(target.exists())
+
+ def test_move_dir_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'dirC'
+ self.assertRaises(OSError, source.move, source)
+ self.assertTrue(source.exists())
+
+ def test_move_dir_into_itself(self):
+ base = self.cls(self.base)
+ source = base / 'dirC'
+ target = base / 'dirC' / 'bar'
+ self.assertRaises(OSError, source.move, target)
+ self.assertTrue(source.exists())
+ self.assertFalse(target.exists())
+
+ @needs_symlinks
+ def test_move_file_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ source_readlink = source.readlink()
+ target = base / 'linkA_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source_readlink, target.readlink())
+
+ @needs_symlinks
+ def test_move_file_symlink_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ self.assertRaises(OSError, source.move, source)
+
+ @needs_symlinks
+ def test_move_dir_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ source_readlink = source.readlink()
+ target = base / 'linkB_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source_readlink, target.readlink())
+
+ @needs_symlinks
+ def test_move_dir_symlink_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ self.assertRaises(OSError, source.move, source)
+
+ @needs_symlinks
+ def test_move_dangling_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'brokenLink'
+ source_readlink = source.readlink()
+ target = base / 'brokenLink_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source_readlink, target.readlink())
+
def test_iterdir(self):
P = self.cls
p = P(self.base)