]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-106046: Improve error message from `os.fspath` if `__fspath__` is set to `None...
authorAlex Waygood <Alex.Waygood@Gmail.com>
Sun, 25 Jun 2023 23:06:12 +0000 (00:06 +0100)
committerGitHub <noreply@github.com>
Sun, 25 Jun 2023 23:06:12 +0000 (00:06 +0100)
Doc/reference/datamodel.rst
Lib/os.py
Lib/test/test_os.py
Misc/NEWS.d/next/Library/2023-06-23-22-52-24.gh-issue-106046.OdLiLJ.rst [new file with mode: 0644]
Modules/posixmodule.c

index e8f9775dd33ce1b834dfeb01fc5683098787060e..7f5edbbcadca6cd6ed790daabea83516cf9aa386 100644 (file)
@@ -3179,8 +3179,9 @@ An example of an asynchronous context manager class::
    lead to some very strange behaviour if it is handled incorrectly.
 
 .. [#] The :meth:`~object.__hash__`, :meth:`~object.__iter__`,
-   :meth:`~object.__reversed__`, and :meth:`~object.__contains__` methods have
-   special handling for this; others
+   :meth:`~object.__reversed__`, :meth:`~object.__contains__`,
+   :meth:`~object.__class_getitem__` and :meth:`~os.PathLike.__fspath__`
+   methods have special handling for this. Others
    will still raise a :exc:`TypeError`, but may do so by relying on
    the behavior that ``None`` is not callable.
 
index 31b957f13215d538a68f0e55e816b64a53b4a2b9..d8c9ba4b15400a160dd21096eb019cb37cc40f43 100644 (file)
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -1061,6 +1061,12 @@ def _fspath(path):
         else:
             raise TypeError("expected str, bytes or os.PathLike object, "
                             "not " + path_type.__name__)
+    except TypeError:
+        if path_type.__fspath__ is None:
+            raise TypeError("expected str, bytes or os.PathLike object, "
+                            "not " + path_type.__name__) from None
+        else:
+            raise
     if isinstance(path_repr, (str, bytes)):
         return path_repr
     else:
index 8de4ef7270b754b38c909a7f9782caaac67d5372..99e9ed213e5615c21776d055c616a83edd2a4667 100644 (file)
@@ -4647,6 +4647,45 @@ class TestPEP519(unittest.TestCase):
                 return ''
         self.assertFalse(hasattr(A(), '__dict__'))
 
+    def test_fspath_set_to_None(self):
+        class Foo:
+            __fspath__ = None
+
+        class Bar:
+            def __fspath__(self):
+                return 'bar'
+
+        class Baz(Bar):
+            __fspath__ = None
+
+        good_error_msg = (
+            r"expected str, bytes or os.PathLike object, not {}".format
+        )
+
+        with self.assertRaisesRegex(TypeError, good_error_msg("Foo")):
+            self.fspath(Foo())
+
+        self.assertEqual(self.fspath(Bar()), 'bar')
+
+        with self.assertRaisesRegex(TypeError, good_error_msg("Baz")):
+            self.fspath(Baz())
+
+        with self.assertRaisesRegex(TypeError, good_error_msg("Foo")):
+            open(Foo())
+
+        with self.assertRaisesRegex(TypeError, good_error_msg("Baz")):
+            open(Baz())
+
+        other_good_error_msg = (
+            r"should be string, bytes or os.PathLike, not {}".format
+        )
+
+        with self.assertRaisesRegex(TypeError, other_good_error_msg("Foo")):
+            os.rename(Foo(), "foooo")
+
+        with self.assertRaisesRegex(TypeError, other_good_error_msg("Baz")):
+            os.rename(Baz(), "bazzz")
+
 class TimesTests(unittest.TestCase):
     def test_times(self):
         times = os.times()
diff --git a/Misc/NEWS.d/next/Library/2023-06-23-22-52-24.gh-issue-106046.OdLiLJ.rst b/Misc/NEWS.d/next/Library/2023-06-23-22-52-24.gh-issue-106046.OdLiLJ.rst
new file mode 100644 (file)
index 0000000..ce10a9d
--- /dev/null
@@ -0,0 +1,2 @@
+Improve the error message from :func:`os.fspath` if called on an object
+where ``__fspath__`` is set to ``None``. Patch by Alex Waygood.
index 694cff19d2286c09ebffb84d31c32456815c1621..d73886f14cb9ec4a9fef1882ea9ededfdd87315b 100644 (file)
@@ -1197,7 +1197,7 @@ path_converter(PyObject *o, void *p)
         PyObject *func, *res;
 
         func = _PyObject_LookupSpecial(o, &_Py_ID(__fspath__));
-        if (NULL == func) {
+        if ((NULL == func) || (func == Py_None)) {
             goto error_format;
         }
         res = _PyObject_CallNoArgs(func);
@@ -15430,7 +15430,7 @@ PyOS_FSPath(PyObject *path)
     }
 
     func = _PyObject_LookupSpecial(path, &_Py_ID(__fspath__));
-    if (NULL == func) {
+    if ((NULL == func) || (func == Py_None)) {
         return PyErr_Format(PyExc_TypeError,
                             "expected str, bytes or os.PathLike object, "
                             "not %.200s",