GH-73991: Add pathlib.Path.move that can handle rename across FS by oz123 · Pull Request #30650 · python/cpython · GitHub
Skip to content
Closed
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
31 changes: 30 additions & 1 deletion Doc/library/pathlib.rst
11 changes: 11 additions & 0 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import posixpath
import re
import shutil
import sys
import warnings
from _collections_abc import Sequence
Expand Down Expand Up @@ -1177,6 +1178,16 @@ def replace(self, target):
os.replace(self, target)
return self.__class__(target)

def move(self, target, copy_function=shutil.copy2):
"""
Recursively move a file or directory to another location (target),
using ``shutil.move``.
If *target* is on the current filesystem, then ``os.rename()`` is used.
Otherwise, *target* will be copied using *copy_function* and then removed.
Returns the new Path instance pointing to the target path.
"""
return self.__class__(shutil.move(self, target, copy_function))

def symlink_to(self, target, target_is_directory=False):
"""
Make this path a symlink pointing to the target path.
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2046,6 +2046,48 @@ def test_replace(self):
self.assertEqual(os.stat(r).st_size, size)
self.assertFileNotFound(q.stat)

def test_move(self):
P = self.cls(BASE)
p = P / 'fileA'
size = p.stat().st_size
# Replacing a non-existing path.
q = P / 'dirA' / 'fileAA'
replaced_p = p.move(q)
self.assertEqual(replaced_p, q)
self.assertEqual(q.stat().st_size, size)
self.assertFileNotFound(p.stat)
# Replacing another (existing) path.
r = rel_join('dirB', 'fileB')
replaced_q = q.move(r)
self.assertEqual(replaced_q, self.cls(r))
self.assertEqual(os.stat(r).st_size, size)
self.assertFileNotFound(q.stat)

Comment thread
oz123 marked this conversation as resolved.
Outdated
# test moving to existing directory
newdir = P / 'newDir/'
newdir.mkdir()

replaced_q.move(newdir)
self.assertTrue(newdir.joinpath(replaced_q.stem).exists())

def test_move_is_calling_os_rename(self):
P = self.cls(BASE)
src = P / 'fileA'
dst = src / 'dirA'
with mock.patch("os.rename") as rename:
src.move(dst)
self.assertTrue(rename.called)
rename.assert_called()
rename.assert_called_with(src.joinpath(), dst.joinpath())

@os_helper.skip_unless_symlink
def test_move_symlink(self):
P = self.cls(BASE)
link = P / 'linkA'
link.move( P / 'newLink')
newlink = P / 'newLink'
self.assertTrue(newlink.is_symlink())

@os_helper.skip_unless_symlink
def test_readlink(self):
P = self.cls(BASE)
Expand Down