]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-33721: Make some os.path functions and pathlib.Path methods be tolerant to inval...
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 18 Sep 2018 08:28:51 +0000 (11:28 +0300)
committerGitHub <noreply@github.com>
Tue, 18 Sep 2018 08:28:51 +0000 (11:28 +0300)
Such functions as os.path.exists(), os.path.lexists(), os.path.isdir(),
os.path.isfile(), os.path.islink(), and os.path.ismount() now return False
instead of raising ValueError or its subclasses UnicodeEncodeError
and UnicodeDecodeError for paths that contain characters or bytes
unrepresentative at the OS level.

15 files changed:
Doc/library/os.path.rst
Doc/library/pathlib.rst
Doc/whatsnew/3.8.rst
Lib/genericpath.py
Lib/macpath.py
Lib/ntpath.py
Lib/pathlib.py
Lib/posixpath.py
Lib/test/test_genericpath.py
Lib/test/test_pathlib.py
Lib/test/test_posixpath.py
Lib/test/test_site.py
Misc/NEWS.d/next/Library/2018-06-14-17-53-30.bpo-33721.8i9_9A.rst [new file with mode: 0644]
Modules/clinic/posixmodule.c.h
Modules/posixmodule.c

index 36bb21c222ed2801f64b396855c927e2a8e60757..5a0b178b9d04b1e03b077173fb6cf02f02862ad2 100644 (file)
@@ -55,6 +55,14 @@ the :mod:`glob` module.)
    * :mod:`macpath` for old-style MacOS paths
 
 
+.. versionchanged:: 3.8
+
+   :func:`exists`, :func:`lexists`, :func:`isdir`, :func:`isfile`,
+   :func:`islink`, and :func:`ismount` now return ``False`` instead of
+   raising an exception for paths that contain characters or bytes
+   unrepresentable at the OS level.
+
+
 .. function:: abspath(path)
 
    Return a normalized absolutized version of the pathname *path*. On most
index ec604f6815937d8184b7ed14d0cec635537d624e..fc193000ba698fe85e8ca9720735a48052e7da99 100644 (file)
@@ -638,7 +638,17 @@ Methods
 
 Concrete paths provide the following methods in addition to pure paths
 methods.  Many of these methods can raise an :exc:`OSError` if a system
-call fails (for example because the path doesn't exist):
+call fails (for example because the path doesn't exist).
+
+.. versionchanged:: 3.8
+
+   :meth:`~Path.exists()`, :meth:`~Path.is_dir()`, :meth:`~Path.is_file()`,
+   :meth:`~Path.is_mount()`, :meth:`~Path.is_symlink()`,
+   :meth:`~Path.is_block_device()`, :meth:`~Path.is_char_device()`,
+   :meth:`~Path.is_fifo()`, :meth:`~Path.is_socket()` now return ``False``
+   instead of raising an exception for paths that contain characters
+   unrepresentable at the OS level.
+
 
 .. classmethod:: Path.cwd()
 
index 38b8623dddd241f868e6443ab709e758b2f42921..1c129a704429f269c10a58df9e8553a9b595e085 100644 (file)
@@ -112,6 +112,31 @@ New Modules
 Improved Modules
 ================
 
+os.path
+-------
+
+:mod:`os.path` functions that return a boolean result like
+:func:`~os.path.exists`, :func:`~os.path.lexists`, :func:`~os.path.isdir`,
+:func:`~os.path.isfile`, :func:`~os.path.islink`, and :func:`~os.path.ismount`
+now return ``False`` instead of raising :exc:`ValueError` or its subclasses
+:exc:`UnicodeEncodeError` and :exc:`UnicodeDecodeError` for paths that contain
+characters or bytes unrepresentable at the OS level.
+(Contributed by Serhiy Storchaka in :issue:`33721`.)
+
+pathlib
+-------
+
+:mod:`pathlib.Path` methods that return a boolean result like
+:meth:`~pathlib.Path.exists()`, :meth:`~pathlib.Path.is_dir()`,
+:meth:`~pathlib.Path.is_file()`, :meth:`~pathlib.Path.is_mount()`,
+:meth:`~pathlib.Path.is_symlink()`, :meth:`~pathlib.Path.is_block_device()`,
+:meth:`~pathlib.Path.is_char_device()`, :meth:`~pathlib.Path.is_fifo()`,
+:meth:`~pathlib.Path.is_socket()` now return ``False`` instead of raising
+:exc:`ValueError` or its subclass :exc:`UnicodeEncodeError` for paths that
+contain characters unrepresentable at the OS level.
+(Contributed by Serhiy Storchaka in :issue:`33721`.)
+
+
 Optimizations
 =============
 
index 303b3b349a9f408c901bad8c569244343c125a8e..5dd703d736c50984cb69ad074b75fba6b86404d8 100644 (file)
@@ -17,7 +17,7 @@ def exists(path):
     """Test whether a path exists.  Returns False for broken symbolic links"""
     try:
         os.stat(path)
-    except OSError:
+    except (OSError, ValueError):
         return False
     return True
 
@@ -28,7 +28,7 @@ def isfile(path):
     """Test whether a path is a regular file"""
     try:
         st = os.stat(path)
-    except OSError:
+    except (OSError, ValueError):
         return False
     return stat.S_ISREG(st.st_mode)
 
@@ -40,7 +40,7 @@ def isdir(s):
     """Return true if the pathname refers to an existing directory."""
     try:
         st = os.stat(s)
-    except OSError:
+    except (OSError, ValueError):
         return False
     return stat.S_ISDIR(st.st_mode)
 
index aacf7235b011fd0e69626b69ff595de800d0b761..9a12d2feee3527cb7263d6e2d5ddaf1769b86318 100644 (file)
@@ -138,7 +138,7 @@ def lexists(path):
 
     try:
         st = os.lstat(path)
-    except OSError:
+    except (OSError, ValueError):
         return False
     return True
 
index f0e03a2f496a914ff0264e0528b0d49550ef751c..0e6de2829f3222eed596f1b1ce0f89d27d02a144 100644 (file)
@@ -229,7 +229,7 @@ def islink(path):
     """
     try:
         st = os.lstat(path)
-    except (OSError, AttributeError):
+    except (OSError, ValueError, AttributeError):
         return False
     return stat.S_ISLNK(st.st_mode)
 
@@ -239,7 +239,7 @@ def lexists(path):
     """Test whether a path exists.  Returns True for broken symbolic links"""
     try:
         st = os.lstat(path)
-    except OSError:
+    except (OSError, ValueError):
         return False
     return True
 
@@ -524,7 +524,7 @@ else:  # use native Windows method on Windows
         """Return the absolute version of a path."""
         try:
             return _getfullpathname(path)
-        except OSError:
+        except (OSError, ValueError):
             return _abspath_fallback(path)
 
 # realpath is a no-op on systems without islink support
index c2986bd022d09457fdd3ef5e9badcc6376890f62..89dffa5561a7a5c8655a7e1d0a557b38e39b4e2f 100644 (file)
@@ -1331,6 +1331,9 @@ class Path(PurePath):
             if e.errno not in _IGNORED_ERROS:
                 raise
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
         return True
 
     def is_dir(self):
@@ -1345,6 +1348,9 @@ class Path(PurePath):
             # Path doesn't exist or is a broken symlink
             # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def is_file(self):
         """
@@ -1359,6 +1365,9 @@ class Path(PurePath):
             # Path doesn't exist or is a broken symlink
             # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def is_mount(self):
         """
@@ -1392,6 +1401,9 @@ class Path(PurePath):
                 raise
             # Path doesn't exist
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def is_block_device(self):
         """
@@ -1405,6 +1417,9 @@ class Path(PurePath):
             # Path doesn't exist or is a broken symlink
             # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def is_char_device(self):
         """
@@ -1418,6 +1433,9 @@ class Path(PurePath):
             # Path doesn't exist or is a broken symlink
             # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def is_fifo(self):
         """
@@ -1431,6 +1449,9 @@ class Path(PurePath):
             # Path doesn't exist or is a broken symlink
             # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def is_socket(self):
         """
@@ -1444,6 +1465,9 @@ class Path(PurePath):
             # Path doesn't exist or is a broken symlink
             # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
             return False
+        except ValueError:
+            # Non-encodable path
+            return False
 
     def expanduser(self):
         """ Return a new path with expanded ~ and ~user constructs
index e92186c64e0ddbece53ed11a6e5f1537a162ea01..7e3f3db4b6d017827acf07f8943f94d53c36f5ae 100644 (file)
@@ -169,7 +169,7 @@ def islink(path):
     """Test whether a path is a symbolic link"""
     try:
         st = os.lstat(path)
-    except (OSError, AttributeError):
+    except (OSError, ValueError, AttributeError):
         return False
     return stat.S_ISLNK(st.st_mode)
 
@@ -179,7 +179,7 @@ def lexists(path):
     """Test whether a path exists.  Returns True for broken symbolic links"""
     try:
         os.lstat(path)
-    except OSError:
+    except (OSError, ValueError):
         return False
     return True
 
@@ -191,7 +191,7 @@ def ismount(path):
     """Test whether a path is a mount point"""
     try:
         s1 = os.lstat(path)
-    except OSError:
+    except (OSError, ValueError):
         # It doesn't exist -- so not a mount point. :-)
         return False
     else:
@@ -206,7 +206,7 @@ def ismount(path):
     parent = realpath(parent)
     try:
         s2 = os.lstat(parent)
-    except OSError:
+    except (OSError, ValueError):
         return False
 
     dev1 = s1.st_dev
index 8b291cda689ce1ec55430403348640f6536e9360..08e1c12101c7f8321a608f90fc6cc44e8a3724eb 100644 (file)
@@ -138,10 +138,20 @@ class GenericTest:
         self.assertIs(self.pathmodule.exists(filename), True)
         self.assertIs(self.pathmodule.exists(bfilename), True)
 
+        self.assertIs(self.pathmodule.exists(filename + '\udfff'), False)
+        self.assertIs(self.pathmodule.exists(bfilename + b'\xff'), False)
+        self.assertIs(self.pathmodule.exists(filename + '\x00'), False)
+        self.assertIs(self.pathmodule.exists(bfilename + b'\x00'), False)
+
         if self.pathmodule is not genericpath:
             self.assertIs(self.pathmodule.lexists(filename), True)
             self.assertIs(self.pathmodule.lexists(bfilename), True)
 
+            self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False)
+            self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False)
+            self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
+            self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)
+
     @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
     def test_exists_fd(self):
         r, w = os.pipe()
@@ -158,6 +168,11 @@ class GenericTest:
         self.assertIs(self.pathmodule.isdir(filename), False)
         self.assertIs(self.pathmodule.isdir(bfilename), False)
 
+        self.assertIs(self.pathmodule.isdir(filename + '\udfff'), False)
+        self.assertIs(self.pathmodule.isdir(bfilename + b'\xff'), False)
+        self.assertIs(self.pathmodule.isdir(filename + '\x00'), False)
+        self.assertIs(self.pathmodule.isdir(bfilename + b'\x00'), False)
+
         try:
             create_file(filename)
             self.assertIs(self.pathmodule.isdir(filename), False)
@@ -178,6 +193,11 @@ class GenericTest:
         self.assertIs(self.pathmodule.isfile(filename), False)
         self.assertIs(self.pathmodule.isfile(bfilename), False)
 
+        self.assertIs(self.pathmodule.isfile(filename + '\udfff'), False)
+        self.assertIs(self.pathmodule.isfile(bfilename + b'\xff'), False)
+        self.assertIs(self.pathmodule.isfile(filename + '\x00'), False)
+        self.assertIs(self.pathmodule.isfile(bfilename + b'\x00'), False)
+
         try:
             create_file(filename)
             self.assertIs(self.pathmodule.isfile(filename), True)
@@ -298,18 +318,20 @@ class TestGenericTest(GenericTest, unittest.TestCase):
                 continue
             func = getattr(self.pathmodule, attr)
             with self.subTest(attr=attr):
-                try:
+                if attr in ('exists', 'isdir', 'isfile'):
                     func('/tmp\udfffabcds')
-                except (OSError, UnicodeEncodeError):
-                    pass
-                try:
                     func(b'/tmp\xffabcds')
-                except (OSError, UnicodeDecodeError):
-                    pass
-                with self.assertRaisesRegex(ValueError, 'embedded null'):
                     func('/tmp\x00abcds')
-                with self.assertRaisesRegex(ValueError, 'embedded null'):
                     func(b'/tmp\x00abcds')
+                else:
+                    with self.assertRaises((OSError, UnicodeEncodeError)):
+                        func('/tmp\udfffabcds')
+                    with self.assertRaises((OSError, UnicodeDecodeError)):
+                        func(b'/tmp\xffabcds')
+                    with self.assertRaisesRegex(ValueError, 'embedded null'):
+                        func('/tmp\x00abcds')
+                    with self.assertRaisesRegex(ValueError, 'embedded null'):
+                        func(b'/tmp\x00abcds')
 
 # Following TestCase is not supposed to be run from test_genericpath.
 # It is inherited by other test modules (macpath, ntpath, posixpath).
index e436db995ce49c13fc5c37d637d3778a9291c487..876eecccfd5fe3461403dbed7a105fe49296fd3b 100644 (file)
@@ -1342,6 +1342,8 @@ class _BasePathTest(object):
             self.assertIs(False, (p / 'linkA' / 'bah').exists())
         self.assertIs(False, (p / 'foo').exists())
         self.assertIs(False, P('/xyzzy').exists())
+        self.assertIs(False, P(BASE + '\udfff').exists())
+        self.assertIs(False, P(BASE + '\x00').exists())
 
     def test_open_common(self):
         p = self.cls(BASE)
@@ -1866,7 +1868,9 @@ class _BasePathTest(object):
         if support.can_symlink():
             self.assertFalse((P / 'linkA').is_dir())
             self.assertTrue((P / 'linkB').is_dir())
-            self.assertFalse((P/ 'brokenLink').is_dir())
+            self.assertFalse((P/ 'brokenLink').is_dir(), False)
+        self.assertIs((P / 'dirA\udfff').is_dir(), False)
+        self.assertIs((P / 'dirA\x00').is_dir(), False)
 
     def test_is_file(self):
         P = self.cls(BASE)
@@ -1878,6 +1882,8 @@ class _BasePathTest(object):
             self.assertTrue((P / 'linkA').is_file())
             self.assertFalse((P / 'linkB').is_file())
             self.assertFalse((P/ 'brokenLink').is_file())
+        self.assertIs((P / 'fileA\udfff').is_file(), False)
+        self.assertIs((P / 'fileA\x00').is_file(), False)
 
     @only_posix
     def test_is_mount(self):
@@ -1890,6 +1896,8 @@ class _BasePathTest(object):
         self.assertTrue(R.is_mount())
         if support.can_symlink():
             self.assertFalse((P / 'linkA').is_mount())
+        self.assertIs(self.cls('/\udfff').is_mount(), False)
+        self.assertIs(self.cls('/\x00').is_mount(), False)
 
     def test_is_symlink(self):
         P = self.cls(BASE)
@@ -1901,6 +1909,11 @@ class _BasePathTest(object):
             self.assertTrue((P / 'linkA').is_symlink())
             self.assertTrue((P / 'linkB').is_symlink())
             self.assertTrue((P/ 'brokenLink').is_symlink())
+        self.assertIs((P / 'fileA\udfff').is_file(), False)
+        self.assertIs((P / 'fileA\x00').is_file(), False)
+        if support.can_symlink():
+            self.assertIs((P / 'linkA\udfff').is_file(), False)
+            self.assertIs((P / 'linkA\x00').is_file(), False)
 
     def test_is_fifo_false(self):
         P = self.cls(BASE)
@@ -1908,6 +1921,8 @@ class _BasePathTest(object):
         self.assertFalse((P / 'dirA').is_fifo())
         self.assertFalse((P / 'non-existing').is_fifo())
         self.assertFalse((P / 'fileA' / 'bah').is_fifo())
+        self.assertIs((P / 'fileA\udfff').is_fifo(), False)
+        self.assertIs((P / 'fileA\x00').is_fifo(), False)
 
     @unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required")
     def test_is_fifo_true(self):
@@ -1919,6 +1934,8 @@ class _BasePathTest(object):
         self.assertTrue(P.is_fifo())
         self.assertFalse(P.is_socket())
         self.assertFalse(P.is_file())
+        self.assertIs(self.cls(BASE, 'myfifo\udfff').is_fifo(), False)
+        self.assertIs(self.cls(BASE, 'myfifo\x00').is_fifo(), False)
 
     def test_is_socket_false(self):
         P = self.cls(BASE)
@@ -1926,6 +1943,8 @@ class _BasePathTest(object):
         self.assertFalse((P / 'dirA').is_socket())
         self.assertFalse((P / 'non-existing').is_socket())
         self.assertFalse((P / 'fileA' / 'bah').is_socket())
+        self.assertIs((P / 'fileA\udfff').is_socket(), False)
+        self.assertIs((P / 'fileA\x00').is_socket(), False)
 
     @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required")
     def test_is_socket_true(self):
@@ -1941,6 +1960,8 @@ class _BasePathTest(object):
         self.assertTrue(P.is_socket())
         self.assertFalse(P.is_fifo())
         self.assertFalse(P.is_file())
+        self.assertIs(self.cls(BASE, 'mysock\udfff').is_socket(), False)
+        self.assertIs(self.cls(BASE, 'mysock\x00').is_socket(), False)
 
     def test_is_block_device_false(self):
         P = self.cls(BASE)
@@ -1948,6 +1969,8 @@ class _BasePathTest(object):
         self.assertFalse((P / 'dirA').is_block_device())
         self.assertFalse((P / 'non-existing').is_block_device())
         self.assertFalse((P / 'fileA' / 'bah').is_block_device())
+        self.assertIs((P / 'fileA\udfff').is_block_device(), False)
+        self.assertIs((P / 'fileA\x00').is_block_device(), False)
 
     def test_is_char_device_false(self):
         P = self.cls(BASE)
@@ -1955,6 +1978,8 @@ class _BasePathTest(object):
         self.assertFalse((P / 'dirA').is_char_device())
         self.assertFalse((P / 'non-existing').is_char_device())
         self.assertFalse((P / 'fileA' / 'bah').is_char_device())
+        self.assertIs((P / 'fileA\udfff').is_char_device(), False)
+        self.assertIs((P / 'fileA\x00').is_char_device(), False)
 
     def test_is_char_device_true(self):
         # Under Unix, /dev/null should generally be a char device
@@ -1964,6 +1989,8 @@ class _BasePathTest(object):
         self.assertTrue(P.is_char_device())
         self.assertFalse(P.is_block_device())
         self.assertFalse(P.is_file())
+        self.assertIs(self.cls('/dev/null\udfff').is_char_device(), False)
+        self.assertIs(self.cls('/dev/null\x00').is_char_device(), False)
 
     def test_pickling_common(self):
         p = self.cls(BASE, 'fileA')
index 9476ede5319397ddce5e76ce97a795a13ad2b6ce..ae59ef5927be84f943a710cdc8a787aa5f0bb77b 100644 (file)
@@ -153,9 +153,11 @@ class PosixPathTest(unittest.TestCase):
     def test_islink(self):
         self.assertIs(posixpath.islink(support.TESTFN + "1"), False)
         self.assertIs(posixpath.lexists(support.TESTFN + "2"), False)
+
         with open(support.TESTFN + "1", "wb") as f:
             f.write(b"foo")
         self.assertIs(posixpath.islink(support.TESTFN + "1"), False)
+
         if support.can_symlink():
             os.symlink(support.TESTFN + "1", support.TESTFN + "2")
             self.assertIs(posixpath.islink(support.TESTFN + "2"), True)
@@ -164,6 +166,11 @@ class PosixPathTest(unittest.TestCase):
             self.assertIs(posixpath.exists(support.TESTFN + "2"), False)
             self.assertIs(posixpath.lexists(support.TESTFN + "2"), True)
 
+        self.assertIs(posixpath.islink(support.TESTFN + "\udfff"), False)
+        self.assertIs(posixpath.islink(os.fsencode(support.TESTFN) + b"\xff"), False)
+        self.assertIs(posixpath.islink(support.TESTFN + "\x00"), False)
+        self.assertIs(posixpath.islink(os.fsencode(support.TESTFN) + b"\x00"), False)
+
     def test_ismount(self):
         self.assertIs(posixpath.ismount("/"), True)
         self.assertIs(posixpath.ismount(b"/"), True)
@@ -177,6 +184,11 @@ class PosixPathTest(unittest.TestCase):
         finally:
             safe_rmdir(ABSTFN)
 
+        self.assertIs(posixpath.ismount('/\udfff'), False)
+        self.assertIs(posixpath.ismount(b'/\xff'), False)
+        self.assertIs(posixpath.ismount('/\x00'), False)
+        self.assertIs(posixpath.ismount(b'/\x00'), False)
+
     @unittest.skipUnless(support.can_symlink(),
                          "Test requires symlink support")
     def test_ismount_symlinks(self):
index e3c9deebf08c02ac8f492b0e9631199f4292ff9e..742be1ec03d41632343aafdb01be829bca577b44 100644 (file)
@@ -159,13 +159,11 @@ class HelperFunctionsTests(unittest.TestCase):
         # Issue 5258
         pth_dir, pth_fn = self.make_pth("abc\x00def\n")
         with captured_stderr() as err_out:
-            site.addpackage(pth_dir, pth_fn, set())
-        self.assertRegex(err_out.getvalue(), "line 1")
-        self.assertRegex(err_out.getvalue(),
-            re.escape(os.path.join(pth_dir, pth_fn)))
-        # XXX: ditto previous XXX comment.
-        self.assertRegex(err_out.getvalue(), 'Traceback')
-        self.assertRegex(err_out.getvalue(), 'ValueError')
+            self.assertFalse(site.addpackage(pth_dir, pth_fn, set()))
+        self.assertEqual(err_out.getvalue(), "")
+        for path in sys.path:
+            if isinstance(path, str):
+                self.assertNotIn("abc\x00def", path)
 
     def test_addsitedir(self):
         # Same tests for test_addpackage since addsitedir() essentially just
diff --git a/Misc/NEWS.d/next/Library/2018-06-14-17-53-30.bpo-33721.8i9_9A.rst b/Misc/NEWS.d/next/Library/2018-06-14-17-53-30.bpo-33721.8i9_9A.rst
new file mode 100644 (file)
index 0000000..987bf46
--- /dev/null
@@ -0,0 +1,12 @@
+:mod:`os.path` functions that return a boolean result like
+:func:`~os.path.exists`, :func:`~os.path.lexists`, :func:`~os.path.isdir`,
+:func:`~os.path.isfile`, :func:`~os.path.islink`, and :func:`~os.path.ismount`,
+and :mod:`pathlib.Path` methods that return a boolean result like
+:meth:`~pathlib.Path.exists()`, :meth:`~pathlib.Path.is_dir()`,
+:meth:`~pathlib.Path.is_file()`, :meth:`~pathlib.Path.is_mount()`,
+:meth:`~pathlib.Path.is_symlink()`, :meth:`~pathlib.Path.is_block_device()`,
+:meth:`~pathlib.Path.is_char_device()`, :meth:`~pathlib.Path.is_fifo()`,
+:meth:`~pathlib.Path.is_socket()` now return ``False`` instead of raising
+:exc:`ValueError` or its subclasses :exc:`UnicodeEncodeError` and
+:exc:`UnicodeDecodeError` for paths that contain characters or bytes
+unrepresentable at the OS level.
index 2c46d4bf172e08ebda0052dd2cb0c57ca1414f09..704c824e9e328212d035fa8d91833b50e0cf116a 100644 (file)
@@ -1005,27 +1005,6 @@ PyDoc_STRVAR(os__isdir__doc__,
 #define OS__ISDIR_METHODDEF    \
     {"_isdir", (PyCFunction)os__isdir, METH_O, os__isdir__doc__},
 
-static PyObject *
-os__isdir_impl(PyObject *module, path_t *path);
-
-static PyObject *
-os__isdir(PyObject *module, PyObject *arg)
-{
-    PyObject *return_value = NULL;
-    path_t path = PATH_T_INITIALIZE("_isdir", "path", 0, 0);
-
-    if (!PyArg_Parse(arg, "O&:_isdir", path_converter, &path)) {
-        goto exit;
-    }
-    return_value = os__isdir_impl(module, &path);
-
-exit:
-    /* Cleanup for path */
-    path_cleanup(&path);
-
-    return return_value;
-}
-
 #endif /* defined(MS_WINDOWS) */
 
 #if defined(MS_WINDOWS)
@@ -6778,4 +6757,4 @@ exit:
 #ifndef OS_GETRANDOM_METHODDEF
     #define OS_GETRANDOM_METHODDEF
 #endif /* !defined(OS_GETRANDOM_METHODDEF) */
-/*[clinic end generated code: output=0f23518dd4482e66 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=40cac0135f846202 input=a9049054013a1b77]*/
index 53d2ce22439e2837218e2a6e1c91113ed8633158..400ed979821d6c4e7b971dd09c60b98c24c18bb3 100644 (file)
@@ -3821,22 +3821,32 @@ cleanup:
 /*[clinic input]
 os._isdir
 
-    path: path_t
+    path as arg: object
     /
 
 Return true if the pathname refers to an existing directory.
 [clinic start generated code]*/
 
 static PyObject *
-os__isdir_impl(PyObject *module, path_t *path)
-/*[clinic end generated code: output=75f56f32720836cb input=5e0800149c0ad95f]*/
+os__isdir(PyObject *module, PyObject *arg)
+/*[clinic end generated code: output=404f334d85d4bf25 input=36cb6785874d479e]*/
 {
     DWORD attributes;
+    path_t path = PATH_T_INITIALIZE("_isdir", "path", 0, 0);
+
+    if (!path_converter(arg, &path)) {
+        if (PyErr_ExceptionMatches(PyExc_ValueError)) {
+            PyErr_Clear();
+            Py_RETURN_FALSE;
+        }
+        return NULL;
+    }
 
     Py_BEGIN_ALLOW_THREADS
-    attributes = GetFileAttributesW(path->wide);
+    attributes = GetFileAttributesW(path.wide);
     Py_END_ALLOW_THREADS
 
+    path_cleanup(&path);
     if (attributes == INVALID_FILE_ATTRIBUTES)
         Py_RETURN_FALSE;