mirror of https://github.com/python/cpython
gh-106046: Improve error message from `os.fspath` if `__fspath__` is set to `None` (#106082)
This commit is contained in:
parent
8c24a83737
commit
93a970ffbc
|
@ -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.
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue