Skip to content

Commit 31ee46e

Browse files
barneygalesrinivasreddy
authored andcommitted
pythonGH-127381: pathlib ABCs: remove PathBase.move() and move_into() (python#128337)
These methods combine `_delete()` and `copy()`, but `_delete()` isn't part of the public interface, and it's unlikely to be added until the pathlib ABCs are made official, or perhaps even later.
1 parent 86e1638 commit 31ee46e

File tree

4 files changed

+148
-153
lines changed

4 files changed

+148
-153
lines changed

Lib/pathlib/_abc.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -573,30 +573,3 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
573573
return self.copy(target, follow_symlinks=follow_symlinks,
574574
dirs_exist_ok=dirs_exist_ok,
575575
preserve_metadata=preserve_metadata)
576-
577-
def _delete(self):
578-
"""
579-
Delete this file or directory (including all sub-directories).
580-
"""
581-
raise NotImplementedError
582-
583-
def move(self, target):
584-
"""
585-
Recursively move this file or directory tree to the given destination.
586-
"""
587-
target = self.copy(target, follow_symlinks=False, preserve_metadata=True)
588-
self._delete()
589-
return target
590-
591-
def move_into(self, target_dir):
592-
"""
593-
Move this file or directory tree into the given existing directory.
594-
"""
595-
name = self.name
596-
if not name:
597-
raise ValueError(f"{self!r} has an empty name")
598-
elif isinstance(target_dir, PathBase):
599-
target = target_dir / name
600-
else:
601-
target = self.with_segments(target_dir, name)
602-
return self.move(target)

Lib/pathlib/_local.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,16 +1128,38 @@ def move(self, target):
11281128
"""
11291129
Recursively move this file or directory tree to the given destination.
11301130
"""
1131-
if not isinstance(target, PathBase):
1132-
target = self.with_segments(target)
1133-
target.copy._ensure_different_file(self)
1131+
# Use os.replace() if the target is os.PathLike and on the same FS.
11341132
try:
1135-
return self.replace(target)
1136-
except OSError as err:
1137-
if err.errno != EXDEV:
1138-
raise
1133+
target_str = os.fspath(target)
1134+
except TypeError:
1135+
pass
1136+
else:
1137+
if not isinstance(target, PathBase):
1138+
target = self.with_segments(target_str)
1139+
target.copy._ensure_different_file(self)
1140+
try:
1141+
os.replace(self, target_str)
1142+
return target
1143+
except OSError as err:
1144+
if err.errno != EXDEV:
1145+
raise
11391146
# Fall back to copy+delete.
1140-
return PathBase.move(self, target)
1147+
target = self.copy(target, follow_symlinks=False, preserve_metadata=True)
1148+
self._delete()
1149+
return target
1150+
1151+
def move_into(self, target_dir):
1152+
"""
1153+
Move this file or directory tree into the given existing directory.
1154+
"""
1155+
name = self.name
1156+
if not name:
1157+
raise ValueError(f"{self!r} has an empty name")
1158+
elif isinstance(target_dir, PathBase):
1159+
target = target_dir / name
1160+
else:
1161+
target = self.with_segments(target_dir, name)
1162+
return self.move(target)
11411163

11421164
if hasattr(os, "symlink"):
11431165
def symlink_to(self, target, target_is_directory=False):

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,26 +1423,97 @@ def test_move_dangling_symlink(self):
14231423
self.assertTrue(target.is_symlink())
14241424
self.assertEqual(source_readlink, target.readlink())
14251425

1426+
def test_move_file(self):
1427+
base = self.cls(self.base)
1428+
source = base / 'fileA'
1429+
source_text = source.read_text()
1430+
target = base / 'fileA_moved'
1431+
result = source.move(target)
1432+
self.assertEqual(result, target)
1433+
self.assertFalse(source.exists())
1434+
self.assertTrue(target.exists())
1435+
self.assertEqual(source_text, target.read_text())
1436+
14261437
@patch_replace
14271438
def test_move_file_other_fs(self):
14281439
self.test_move_file()
14291440

1441+
def test_move_file_to_file(self):
1442+
base = self.cls(self.base)
1443+
source = base / 'fileA'
1444+
source_text = source.read_text()
1445+
target = base / 'dirB' / 'fileB'
1446+
result = source.move(target)
1447+
self.assertEqual(result, target)
1448+
self.assertFalse(source.exists())
1449+
self.assertTrue(target.exists())
1450+
self.assertEqual(source_text, target.read_text())
1451+
14301452
@patch_replace
14311453
def test_move_file_to_file_other_fs(self):
14321454
self.test_move_file_to_file()
14331455

1456+
def test_move_file_to_dir(self):
1457+
base = self.cls(self.base)
1458+
source = base / 'fileA'
1459+
target = base / 'dirB'
1460+
self.assertRaises(OSError, source.move, target)
1461+
14341462
@patch_replace
14351463
def test_move_file_to_dir_other_fs(self):
14361464
self.test_move_file_to_dir()
14371465

1466+
def test_move_file_to_itself(self):
1467+
base = self.cls(self.base)
1468+
source = base / 'fileA'
1469+
self.assertRaises(OSError, source.move, source)
1470+
1471+
def test_move_dir(self):
1472+
base = self.cls(self.base)
1473+
source = base / 'dirC'
1474+
target = base / 'dirC_moved'
1475+
result = source.move(target)
1476+
self.assertEqual(result, target)
1477+
self.assertFalse(source.exists())
1478+
self.assertTrue(target.is_dir())
1479+
self.assertTrue(target.joinpath('dirD').is_dir())
1480+
self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
1481+
self.assertEqual(target.joinpath('dirD', 'fileD').read_text(),
1482+
"this is file D\n")
1483+
self.assertTrue(target.joinpath('fileC').is_file())
1484+
self.assertTrue(target.joinpath('fileC').read_text(),
1485+
"this is file C\n")
1486+
14381487
@patch_replace
14391488
def test_move_dir_other_fs(self):
14401489
self.test_move_dir()
14411490

1491+
def test_move_dir_to_dir(self):
1492+
base = self.cls(self.base)
1493+
source = base / 'dirC'
1494+
target = base / 'dirB'
1495+
self.assertRaises(OSError, source.move, target)
1496+
self.assertTrue(source.exists())
1497+
self.assertTrue(target.exists())
1498+
14421499
@patch_replace
14431500
def test_move_dir_to_dir_other_fs(self):
14441501
self.test_move_dir_to_dir()
14451502

1503+
def test_move_dir_to_itself(self):
1504+
base = self.cls(self.base)
1505+
source = base / 'dirC'
1506+
self.assertRaises(OSError, source.move, source)
1507+
self.assertTrue(source.exists())
1508+
1509+
def test_move_dir_into_itself(self):
1510+
base = self.cls(self.base)
1511+
source = base / 'dirC'
1512+
target = base / 'dirC' / 'bar'
1513+
self.assertRaises(OSError, source.move, target)
1514+
self.assertTrue(source.exists())
1515+
self.assertFalse(target.exists())
1516+
14461517
@patch_replace
14471518
def test_move_dir_into_itself_other_fs(self):
14481519
self.test_move_dir_into_itself()
@@ -1472,10 +1543,26 @@ def test_move_dir_symlink_to_itself_other_fs(self):
14721543
def test_move_dangling_symlink_other_fs(self):
14731544
self.test_move_dangling_symlink()
14741545

1546+
def test_move_into(self):
1547+
base = self.cls(self.base)
1548+
source = base / 'fileA'
1549+
source_text = source.read_text()
1550+
target_dir = base / 'dirA'
1551+
result = source.move_into(target_dir)
1552+
self.assertEqual(result, target_dir / 'fileA')
1553+
self.assertFalse(source.exists())
1554+
self.assertTrue(result.exists())
1555+
self.assertEqual(source_text, result.read_text())
1556+
14751557
@patch_replace
14761558
def test_move_into_other_os(self):
14771559
self.test_move_into()
14781560

1561+
def test_move_into_empty_name(self):
1562+
source = self.cls('')
1563+
target_dir = self.base
1564+
self.assertRaises(ValueError, source.move_into, target_dir)
1565+
14791566
@patch_replace
14801567
def test_move_into_empty_name_other_os(self):
14811568
self.test_move_into_empty_name()
@@ -1794,6 +1881,37 @@ def test_rmdir(self):
17941881
self.assertFileNotFound(p.stat)
17951882
self.assertFileNotFound(p.unlink)
17961883

1884+
def test_delete_file(self):
1885+
p = self.cls(self.base) / 'fileA'
1886+
p._delete()
1887+
self.assertFalse(p.exists())
1888+
self.assertFileNotFound(p._delete)
1889+
1890+
def test_delete_dir(self):
1891+
base = self.cls(self.base)
1892+
base.joinpath('dirA')._delete()
1893+
self.assertFalse(base.joinpath('dirA').exists())
1894+
self.assertFalse(base.joinpath('dirA', 'linkC').exists(
1895+
follow_symlinks=False))
1896+
base.joinpath('dirB')._delete()
1897+
self.assertFalse(base.joinpath('dirB').exists())
1898+
self.assertFalse(base.joinpath('dirB', 'fileB').exists())
1899+
self.assertFalse(base.joinpath('dirB', 'linkD').exists(
1900+
follow_symlinks=False))
1901+
base.joinpath('dirC')._delete()
1902+
self.assertFalse(base.joinpath('dirC').exists())
1903+
self.assertFalse(base.joinpath('dirC', 'dirD').exists())
1904+
self.assertFalse(base.joinpath('dirC', 'dirD', 'fileD').exists())
1905+
self.assertFalse(base.joinpath('dirC', 'fileC').exists())
1906+
self.assertFalse(base.joinpath('dirC', 'novel.txt').exists())
1907+
1908+
def test_delete_missing(self):
1909+
tmp = self.cls(self.base, 'delete')
1910+
tmp.mkdir()
1911+
# filename is guaranteed not to exist
1912+
filename = tmp / 'foo'
1913+
self.assertRaises(FileNotFoundError, filename._delete)
1914+
17971915
@needs_symlinks
17981916
def test_delete_symlink(self):
17991917
tmp = self.cls(self.base, 'delete')

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 0 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,93 +1345,6 @@ def test_copy_into_empty_name(self):
13451345
target_dir = self.base
13461346
self.assertRaises(ValueError, source.copy_into, target_dir)
13471347

1348-
def test_move_file(self):
1349-
base = self.cls(self.base)
1350-
source = base / 'fileA'
1351-
source_text = source.read_text()
1352-
target = base / 'fileA_moved'
1353-
result = source.move(target)
1354-
self.assertEqual(result, target)
1355-
self.assertFalse(source.exists())
1356-
self.assertTrue(target.exists())
1357-
self.assertEqual(source_text, target.read_text())
1358-
1359-
def test_move_file_to_file(self):
1360-
base = self.cls(self.base)
1361-
source = base / 'fileA'
1362-
source_text = source.read_text()
1363-
target = base / 'dirB' / 'fileB'
1364-
result = source.move(target)
1365-
self.assertEqual(result, target)
1366-
self.assertFalse(source.exists())
1367-
self.assertTrue(target.exists())
1368-
self.assertEqual(source_text, target.read_text())
1369-
1370-
def test_move_file_to_dir(self):
1371-
base = self.cls(self.base)
1372-
source = base / 'fileA'
1373-
target = base / 'dirB'
1374-
self.assertRaises(OSError, source.move, target)
1375-
1376-
def test_move_file_to_itself(self):
1377-
base = self.cls(self.base)
1378-
source = base / 'fileA'
1379-
self.assertRaises(OSError, source.move, source)
1380-
1381-
def test_move_dir(self):
1382-
base = self.cls(self.base)
1383-
source = base / 'dirC'
1384-
target = base / 'dirC_moved'
1385-
result = source.move(target)
1386-
self.assertEqual(result, target)
1387-
self.assertFalse(source.exists())
1388-
self.assertTrue(target.is_dir())
1389-
self.assertTrue(target.joinpath('dirD').is_dir())
1390-
self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
1391-
self.assertEqual(target.joinpath('dirD', 'fileD').read_text(),
1392-
"this is file D\n")
1393-
self.assertTrue(target.joinpath('fileC').is_file())
1394-
self.assertTrue(target.joinpath('fileC').read_text(),
1395-
"this is file C\n")
1396-
1397-
def test_move_dir_to_dir(self):
1398-
base = self.cls(self.base)
1399-
source = base / 'dirC'
1400-
target = base / 'dirB'
1401-
self.assertRaises(OSError, source.move, target)
1402-
self.assertTrue(source.exists())
1403-
self.assertTrue(target.exists())
1404-
1405-
def test_move_dir_to_itself(self):
1406-
base = self.cls(self.base)
1407-
source = base / 'dirC'
1408-
self.assertRaises(OSError, source.move, source)
1409-
self.assertTrue(source.exists())
1410-
1411-
def test_move_dir_into_itself(self):
1412-
base = self.cls(self.base)
1413-
source = base / 'dirC'
1414-
target = base / 'dirC' / 'bar'
1415-
self.assertRaises(OSError, source.move, target)
1416-
self.assertTrue(source.exists())
1417-
self.assertFalse(target.exists())
1418-
1419-
def test_move_into(self):
1420-
base = self.cls(self.base)
1421-
source = base / 'fileA'
1422-
source_text = source.read_text()
1423-
target_dir = base / 'dirA'
1424-
result = source.move_into(target_dir)
1425-
self.assertEqual(result, target_dir / 'fileA')
1426-
self.assertFalse(source.exists())
1427-
self.assertTrue(result.exists())
1428-
self.assertEqual(source_text, result.read_text())
1429-
1430-
def test_move_into_empty_name(self):
1431-
source = self.cls('')
1432-
target_dir = self.base
1433-
self.assertRaises(ValueError, source.move_into, target_dir)
1434-
14351348
def test_iterdir(self):
14361349
P = self.cls
14371350
p = P(self.base)
@@ -1660,37 +1573,6 @@ def test_is_symlink(self):
16601573
self.assertIs((P / 'linkA\udfff').is_file(), False)
16611574
self.assertIs((P / 'linkA\x00').is_file(), False)
16621575

1663-
def test_delete_file(self):
1664-
p = self.cls(self.base) / 'fileA'
1665-
p._delete()
1666-
self.assertFalse(p.exists())
1667-
self.assertFileNotFound(p._delete)
1668-
1669-
def test_delete_dir(self):
1670-
base = self.cls(self.base)
1671-
base.joinpath('dirA')._delete()
1672-
self.assertFalse(base.joinpath('dirA').exists())
1673-
self.assertFalse(base.joinpath('dirA', 'linkC').exists(
1674-
follow_symlinks=False))
1675-
base.joinpath('dirB')._delete()
1676-
self.assertFalse(base.joinpath('dirB').exists())
1677-
self.assertFalse(base.joinpath('dirB', 'fileB').exists())
1678-
self.assertFalse(base.joinpath('dirB', 'linkD').exists(
1679-
follow_symlinks=False))
1680-
base.joinpath('dirC')._delete()
1681-
self.assertFalse(base.joinpath('dirC').exists())
1682-
self.assertFalse(base.joinpath('dirC', 'dirD').exists())
1683-
self.assertFalse(base.joinpath('dirC', 'dirD', 'fileD').exists())
1684-
self.assertFalse(base.joinpath('dirC', 'fileC').exists())
1685-
self.assertFalse(base.joinpath('dirC', 'novel.txt').exists())
1686-
1687-
def test_delete_missing(self):
1688-
tmp = self.cls(self.base, 'delete')
1689-
tmp.mkdir()
1690-
# filename is guaranteed not to exist
1691-
filename = tmp / 'foo'
1692-
self.assertRaises(FileNotFoundError, filename._delete)
1693-
16941576

16951577
class DummyPathWalkTest(unittest.TestCase):
16961578
cls = DummyPath

0 commit comments

Comments
 (0)