Skip to content

Commit 1577259

Browse files
bpo-43219: shutil.copyfile, raise a less confusing exception instead of IsADirectoryError (GH-27049)
Fixes the misleading IsADirectoryError to be FileNotFoundError. (cherry picked from commit 248173c) Co-authored-by: andrei kulakov <[email protected]>
1 parent bff66d0 commit 1577259

File tree

3 files changed

+42
-21
lines changed

3 files changed

+42
-21
lines changed

Lib/shutil.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -251,28 +251,36 @@ def copyfile(src, dst, *, follow_symlinks=True):
251251
if not follow_symlinks and _islink(src):
252252
os.symlink(os.readlink(src), dst)
253253
else:
254-
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
255-
# macOS
256-
if _HAS_FCOPYFILE:
257-
try:
258-
_fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
259-
return dst
260-
except _GiveupOnFastCopy:
261-
pass
262-
# Linux
263-
elif _USE_CP_SENDFILE:
264-
try:
265-
_fastcopy_sendfile(fsrc, fdst)
254+
try:
255+
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
256+
# macOS
257+
if _HAS_FCOPYFILE:
258+
try:
259+
_fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
260+
return dst
261+
except _GiveupOnFastCopy:
262+
pass
263+
# Linux
264+
elif _USE_CP_SENDFILE:
265+
try:
266+
_fastcopy_sendfile(fsrc, fdst)
267+
return dst
268+
except _GiveupOnFastCopy:
269+
pass
270+
# Windows, see:
271+
# https://github.com/python/cpython/pull/7160#discussion_r195405230
272+
elif _WINDOWS and file_size > 0:
273+
_copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
266274
return dst
267-
except _GiveupOnFastCopy:
268-
pass
269-
# Windows, see:
270-
# https://github.com/python/cpython/pull/7160#discussion_r195405230
271-
elif _WINDOWS and file_size > 0:
272-
_copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
273-
return dst
274-
275-
copyfileobj(fsrc, fdst)
275+
276+
copyfileobj(fsrc, fdst)
277+
278+
# Issue 43219, raise a less confusing exception
279+
except IsADirectoryError as e:
280+
if os.path.exists(dst):
281+
raise
282+
else:
283+
raise FileNotFoundError(f'Directory does not exist: {dst}') from e
276284

277285
return dst
278286

Lib/test/test_shutil.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,15 @@ def test_copyfile_same_file(self):
12491249
# Make sure file is not corrupted.
12501250
self.assertEqual(read_file(src_file), 'foo')
12511251

1252+
@unittest.skipIf(MACOS or _winapi, 'On MACOS and Windows the errors are not confusing (though different)')
1253+
def test_copyfile_nonexistent_dir(self):
1254+
# Issue 43219
1255+
src_dir = self.mkdtemp()
1256+
src_file = os.path.join(src_dir, 'foo')
1257+
dst = os.path.join(src_dir, 'does_not_exist/')
1258+
write_file(src_file, 'foo')
1259+
self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst)
1260+
12521261

12531262
class TestArchives(BaseTest, unittest.TestCase):
12541263

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Update :func:`shutil.copyfile` to raise :exc:`FileNotFoundError` instead of
2+
confusing :exc:`IsADirectoryError` when a path ending with a
3+
:const:`os.path.sep` does not exist; :func:`shutil.copy` and
4+
:func:`shutil.copy2` are also affected.

0 commit comments

Comments
 (0)