[Python-checkins] gh-106046: Improve error message from `os.fspath` if `__fspath__` is set to `None` (#106082)
AlexWaygood
webhook-mailer at python.org
Sun Jun 25 19:06:15 EDT 2023
https://github.com/python/cpython/commit/93a970ffbce58657cc99305be69e460a11371730
commit: 93a970ffbce58657cc99305be69e460a11371730
branch: main
author: Alex Waygood <Alex.Waygood at Gmail.com>
committer: AlexWaygood <Alex.Waygood at Gmail.com>
date: 2023-06-26T00:06:12+01:00
summary:
gh-106046: Improve error message from `os.fspath` if `__fspath__` is set to `None` (#106082)
files:
A Misc/NEWS.d/next/Library/2023-06-23-22-52-24.gh-issue-106046.OdLiLJ.rst
M Doc/reference/datamodel.rst
M Lib/os.py
M Lib/test/test_os.py
M Modules/posixmodule.c
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index e8f9775dd33ce..7f5edbbcadca6 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -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.
diff --git a/Lib/os.py b/Lib/os.py
index 31b957f13215d..d8c9ba4b15400 100644
--- 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:
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index 8de4ef7270b75..99e9ed213e561 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -4647,6 +4647,45 @@ def __fspath__(self):
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
index 0000000000000..ce10a9d81dc64
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-06-23-22-52-24.gh-issue-106046.OdLiLJ.rst
@@ -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.
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 694cff19d2286..d73886f14cb9e 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -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",
More information about the Python-checkins
mailing list