]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.9] bpo-43219: shutil.copyfile, raise a less confusing exception instead of IsADire...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sat, 10 Jul 2021 04:13:59 +0000 (21:13 -0700)
committerGitHub <noreply@github.com>
Sat, 10 Jul 2021 04:13:59 +0000 (21:13 -0700)
Fixes the misleading IsADirectoryError to be FileNotFoundError.
(cherry picked from commit 248173cc0483a9ad9261353302f1234cf9eb2ebe)

Co-authored-by: andrei kulakov <andrei.avk@gmail.com>
Automerge-Triggered-By: GH:gpshead
Lib/shutil.py
Lib/test/test_shutil.py
Misc/NEWS.d/next/Library/2021-07-09-07-14-37.bpo-41928.Q1jMrr.rst [new file with mode: 0644]

index 5cb796e800f62b65d627af5df30f96984695f03e..08384cf92bb5db5a83cb6b4d67805a989fb72e9c 100644 (file)
@@ -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
 
index e378033acd35ac3b09a1fef98fffea0d79e74a8e..7cf263972ba5fe47a77d02b293f8abf08859fc1b 100644 (file)
@@ -1243,6 +1243,15 @@ class TestCopy(BaseTest, unittest.TestCase):
         # 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):
 
diff --git a/Misc/NEWS.d/next/Library/2021-07-09-07-14-37.bpo-41928.Q1jMrr.rst b/Misc/NEWS.d/next/Library/2021-07-09-07-14-37.bpo-41928.Q1jMrr.rst
new file mode 100644 (file)
index 0000000..e6bd758
--- /dev/null
@@ -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.