gh-106046: Improve error message from `os.fspath` if `__fspath__` is set to `None` (#106082)

This commit is contained in:
Alex Waygood 2023-06-26 00:06:12 +01:00 committed by GitHub
parent 8c24a83737
commit 93a970ffbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 4 deletions

View 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.

View File

@ -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:

View 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()

View File

@ -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.

View 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",