Skip to content

Commit

Permalink
bpo-45234: Fix FileNotFound exception raised instead of IsADirectoryE…
Browse files Browse the repository at this point in the history
…rror in shutil.copyfile() (GH-28421)

This was a regression from fixing BPO-43219.
  • Loading branch information
akulakov committed Sep 21, 2021
1 parent 86f2837 commit b7eac52
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 29 deletions.
59 changes: 30 additions & 29 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,36 +253,37 @@ def copyfile(src, dst, *, follow_symlinks=True):
if not follow_symlinks and _islink(src):
os.symlink(os.readlink(src), dst)
else:
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)
with open(src, 'rb') as fsrc:
try:
with 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)

# 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

copyfileobj(fsrc, fdst)

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

return dst

Expand Down
40 changes: 40 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,28 @@ def test_copy_return_value(self):
rv = fn(src, os.path.join(dst_dir, 'bar'))
self.assertEqual(rv, os.path.join(dst_dir, 'bar'))

def test_copy_dir(self):
self._test_copy_dir(shutil.copy)

def test_copy2_dir(self):
self._test_copy_dir(shutil.copy2)

def _test_copy_dir(self, copy_func):
src_dir = self.mkdtemp()
src_file = os.path.join(src_dir, 'foo')
dir2 = self.mkdtemp()
dst = os.path.join(src_dir, 'does_not_exist/')
write_file(src_file, 'foo')
if sys.platform == "win32":
err = PermissionError
else:
err = IsADirectoryError
self.assertRaises(err, copy_func, dir2, src_dir)

# raise *err* because of src rather than FileNotFoundError because of dst
self.assertRaises(err, copy_func, dir2, dst)
copy_func(src_file, dir2) # should not raise exceptions

### shutil.copyfile

@os_helper.skip_unless_symlink
Expand Down Expand Up @@ -1259,6 +1281,24 @@ def test_copyfile_nonexistent_dir(self):
write_file(src_file, 'foo')
self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst)

def test_copyfile_copy_dir(self):
# Issue 45234
# test copy() and copyfile() raising proper exceptions when src and/or
# dst are directories
src_dir = self.mkdtemp()
src_file = os.path.join(src_dir, 'foo')
dir2 = self.mkdtemp()
dst = os.path.join(src_dir, 'does_not_exist/')
write_file(src_file, 'foo')
if sys.platform == "win32":
err = PermissionError
else:
err = IsADirectoryError

self.assertRaises(err, shutil.copyfile, src_dir, dst)
self.assertRaises(err, shutil.copyfile, src_file, src_dir)
self.assertRaises(err, shutil.copyfile, dir2, src_dir)


class TestArchives(BaseTest, unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed a regression in :func:`~shutil.copyfile`, :func:`~shutil.copy`,
:func:`~shutil.copy2` raising :exc:`FileNotFoundError` when source is a
directory, which should raise :exc:`IsADirectoryError`

0 comments on commit b7eac52

Please sign in to comment.