Skip to content
Open
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
16 changes: 10 additions & 6 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,10 +1276,12 @@ def rename(self, target):

Returns the new Path instance pointing to the target path.
"""
if hasattr(target, 'with_segments'):
result = target
else:
result = self.with_segments(target)
os.rename(self, target)
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
return target
return result

def replace(self, target):
"""
Expand All @@ -1291,10 +1293,12 @@ def replace(self, target):

Returns the new Path instance pointing to the target path.
"""
if hasattr(target, 'with_segments'):
result = target
else:
result = self.with_segments(target)
os.replace(self, target)
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
return target
return result

def copy(self, target, **kwargs):
"""
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,27 @@ def test_rename(self):
self.assertEqual(os.stat(r).st_size, size)
self.assertFileNotFound(q.stat)

def test_rename_bytes_target(self):
P = self.cls(self.base)
source = P / 'fileA'
target = P / 'dirA' / 'fileAA'
target_bytes = os.fsencode(target)
for target_arg in (target_bytes, FakePath(target_bytes)):
with self.subTest(target=target_arg):
with self.assertRaises(TypeError):
source.rename(target_arg)
self.assertTrue(source.exists())
self.assertFalse(target.exists())

def test_rename_preserves_target(self):
P = self.cls(self.base)
source = P / 'fileA'
target = str(P / 'dirA' / 'fileAA') + self.parser.sep
with mock.patch.object(os, 'rename') as rename:
renamed = source.rename(target)
rename.assert_called_once_with(source, target)
self.assertEqual(renamed, self.cls(target))

def test_replace(self):
P = self.cls(self.base)
p = P / 'fileA'
Expand All @@ -2326,6 +2347,27 @@ def test_replace(self):
self.assertEqual(os.stat(r).st_size, size)
self.assertFileNotFound(q.stat)

def test_replace_bytes_target(self):
P = self.cls(self.base)
source = P / 'fileA'
target = P / 'dirA' / 'fileAA'
target_bytes = os.fsencode(target)
for target_arg in (target_bytes, FakePath(target_bytes)):
with self.subTest(target=target_arg):
with self.assertRaises(TypeError):
source.replace(target_arg)
self.assertTrue(source.exists())
self.assertFalse(target.exists())

def test_replace_preserves_target(self):
P = self.cls(self.base)
source = P / 'fileA'
target = str(P / 'dirA' / 'fileAA') + self.parser.sep
with mock.patch.object(os, 'replace') as replace:
replaced = source.replace(target)
replace.assert_called_once_with(source, target)
self.assertEqual(replaced, self.cls(target))

def test_touch_common(self):
P = self.cls(self.base)
p = P / 'newfileA'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :meth:`pathlib.Path.rename` and :meth:`pathlib.Path.replace` to validate
the target before modifying the filesystem. Passing a bytes path, including
a path-like object returning bytes, now raises :exc:`TypeError` without
moving the source.
Loading