Skip to content

[3.9] bpo-43219: shutil.copyfile, raise a less confusing exception instead of IsADirectoryError (GH-27049) #27082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 10, 2021
Merged
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
50 changes: 29 additions & 21 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,28 +261,36 @@ def copyfile(src, dst, *, follow_symlinks=True):
if not follow_symlinks and _islink(src):
os.symlink(os.readlink(src), dst)
else:
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
# macOS
if _HAS_FCOPYFILE:
try:
_fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
return dst
except _GiveupOnFastCopy:
pass
# Linux
elif _USE_CP_SENDFILE:
try:
_fastcopy_sendfile(fsrc, fdst)
try:
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
# macOS
if _HAS_FCOPYFILE:
try:
_fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA)
return dst
except _GiveupOnFastCopy:
pass
# Linux
elif _USE_CP_SENDFILE:
try:
_fastcopy_sendfile(fsrc, fdst)
return dst
except _GiveupOnFastCopy:
pass
# Windows, see:
# https://github.com/python/cpython/pull/7160#discussion_r195405230
elif _WINDOWS and file_size > 0:
_copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
return dst
except _GiveupOnFastCopy:
pass
# Windows, see:
# https://github.com/python/cpython/pull/7160#discussion_r195405230
elif _WINDOWS and file_size > 0:
_copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE))
return dst

copyfileobj(fsrc, fdst)

copyfileobj(fsrc, fdst)

# Issue 43219, raise a less confusing exception
except IsADirectoryError as e:
if os.path.exists(dst):
raise
else:
raise FileNotFoundError(f'Directory does not exist: {dst}') from e

return dst

Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,15 @@ def test_copyfile_same_file(self):
# Make sure file is not corrupted.
self.assertEqual(read_file(src_file), 'foo')

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


class TestArchives(BaseTest, unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Update :func:`shutil.copyfile` to raise :exc:`FileNotFoundError` instead of
confusing :exc:`IsADirectoryError` when a path ending with a
:const:`os.path.sep` does not exist; :func:`shutil.copy` and
:func:`shutil.copy2` are also affected.