Skip to content

Commit c89f0b2

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

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
@@ -261,28 +261,36 @@ def copyfile(src, dst, *, follow_symlinks=True):
261261
if not follow_symlinks and _islink(src):
262262
os.symlink(os.readlink(src), dst)
263263
else:
264-
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
265-
# macOS
266-
if _HAS_FCOPYFILE:
267-
try:
268-
_fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
269-
return dst
270-
except _GiveupOnFastCopy:
271-
pass
272-
# Linux
273-
elif _USE_CP_SENDFILE:
274-
try:
275-
_fastcopy_sendfile(fsrc, fdst)
264+
try:
265+
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
266+
# macOS
267+
if _HAS_FCOPYFILE:
268+
try:
269+
_fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
270+
return dst
271+
except _GiveupOnFastCopy:
272+
pass
273+
# Linux
274+
elif _USE_CP_SENDFILE:
275+
try:
276+
_fastcopy_sendfile(fsrc, fdst)
277+
return dst
278+
except _GiveupOnFastCopy:
279+
pass
280+
# Windows, see:
281+
# https://github.com/python/cpython/pull/7160#discussion_r195405230
282+
elif _WINDOWS and file_size > 0:
283+
_copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
276284
return dst
277-
except _GiveupOnFastCopy:
278-
pass
279-
# Windows, see:
280-
# https://github.com/python/cpython/pull/7160#discussion_r195405230
281-
elif _WINDOWS and file_size > 0:
282-
_copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
283-
return dst
284-
285-
copyfileobj(fsrc, fdst)
285+
286+
copyfileobj(fsrc, fdst)
287+
288+
# Issue 43219, raise a less confusing exception
289+
except IsADirectoryError as e:
290+
if os.path.exists(dst):
291+
raise
292+
else:
293+
raise FileNotFoundError(f'Directory does not exist: {dst}') from e
286294

287295
return dst
288296

Lib/test/test_shutil.py

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

1246+
@unittest.skipIf(MACOS or _winapi, 'On MACOS and Windows the errors are not confusing (though different)')
1247+
def test_copyfile_nonexistent_dir(self):
1248+
# Issue 43219
1249+
src_dir = self.mkdtemp()
1250+
src_file = os.path.join(src_dir, 'foo')
1251+
dst = os.path.join(src_dir, 'does_not_exist/')
1252+
write_file(src_file, 'foo')
1253+
self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst)
1254+
12461255

12471256
class TestArchives(BaseTest, unittest.TestCase):
12481257

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)