cpython: ea7b6a7827a4 (original) (raw)
Mercurial > cpython
changeset 102160:ea7b6a7827a4
Issue #27186: Update os.fspath()/PyOS_FSPath() to check the return type of __fspath__(). As part of this change, also make sure that the pure Python implementation of os.fspath() is tested. [#27186]
Brett Cannon brett@python.org | |
---|---|
date | Fri, 24 Jun 2016 12:03:43 -0700 |
parents | 7d5bc8a3c7fc |
children | 9c57178f13dc |
files | Doc/c-api/sys.rst Doc/library/os.rst Lib/os.py Lib/test/test_io.py Lib/test/test_os.py Misc/NEWS Modules/posixmodule.c |
diffstat | 7 files changed, 103 insertions(+), 73 deletions(-)[+] [-] Doc/c-api/sys.rst 5 Doc/library/os.rst 14 Lib/os.py 71 Lib/test/test_io.py 2 Lib/test/test_os.py 68 Misc/NEWS 3 Modules/posixmodule.c 13 |
line wrap: on
line diff
--- a/Doc/c-api/sys.rst
+++ b/Doc/c-api/sys.rst
@@ -10,8 +10,9 @@ Operating System Utilities
Return the file system representation for path. If the object is a
:class:str
or :class:bytes
object, then its reference count is
incremented. If the object implements the :class:os.PathLike
interface,
- then :meth:
~os.PathLike.__fspath__
is returned as long as it is a - :class:
str
or :class:bytes
object. Otherwise :exc:TypeError
is raised - and
NULL
is returned. .. versionadded:: 3.6
--- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -179,7 +179,8 @@ process and user. .. versionadded:: 3.2 .. versionchanged:: 3.6
Support added to accept objects implementing :class:`os.PathLike`.[](#l2.7)
Support added to accept objects implementing the :class:`os.PathLike`[](#l2.8)
interface.[](#l2.9)
.. function:: fsdecode(filename) @@ -192,17 +193,18 @@ process and user. .. versionadded:: 3.2 .. versionchanged:: 3.6
Support added to accept objects implementing :class:`os.PathLike`.[](#l2.17)
Support added to accept objects implementing the :class:`os.PathLike`[](#l2.18)
interface.[](#l2.19)
.. function:: fspath(path) Return the file system representation of the path.
- If :class:
str
or :class:bytes
is passed in, it is returned unchanged; - otherwise, the result of calling
type(path).__fspath__
is returned - (which is represented by :class:
os.PathLike
). All other types raise a - :exc:
TypeError
.
- If :class:
str
or :class:bytes
is passed in, it is returned unchanged. - Otherwise :meth:
~os.PathLike.__fspath__
is called and its value is - returned as long as it is a :class:
str
or :class:bytes
object. - In all other cases, :exc:
TypeError
is raised. .. versionadded:: 3.6
--- a/Lib/os.py +++ b/Lib/os.py @@ -881,14 +881,11 @@ def _fscodec(): On Windows, use 'strict' error handler if the file system encoding is 'mbcs' (which is the default encoding). """
filename = fspath(filename)[](#l3.7)
if isinstance(filename, bytes):[](#l3.8)
return filename[](#l3.9)
elif isinstance(filename, str):[](#l3.10)
filename = fspath(filename) # Does type-checking of `filename`.[](#l3.11)
if isinstance(filename, str):[](#l3.12) return filename.encode(encoding, errors)[](#l3.13) else:[](#l3.14)
raise TypeError("expected str, bytes or os.PathLike object, not "[](#l3.15)
+ type(filename).__name__)[](#l3.16)
return filename[](#l3.17)
def fsdecode(filename): """Decode filename (an os.PathLike, bytes, or str) from the filesystem @@ -896,14 +893,11 @@ def _fscodec(): Windows, use 'strict' error handler if the file system encoding is 'mbcs' (which is the default encoding). """
filename = fspath(filename)[](#l3.25)
if isinstance(filename, str):[](#l3.26)
return filename[](#l3.27)
elif isinstance(filename, bytes):[](#l3.28)
filename = fspath(filename) # Does type-checking of `filename`.[](#l3.29)
if isinstance(filename, bytes):[](#l3.30) return filename.decode(encoding, errors)[](#l3.31) else:[](#l3.32)
raise TypeError("expected str, bytes or os.PathLike object, not "[](#l3.33)
+ type(filename).__name__)[](#l3.34)
return filename[](#l3.35)
return fsencode, fsdecode @@ -1102,27 +1096,44 @@ def fdopen(fd, *args, **kwargs): import io return io.open(fd, *args, **kwargs) -# Supply os.fspath() if not defined in C -if not _exists('fspath'):
+ +# For testing purposes, make sure the function is available when the C +# implementation exists. +def _fspath(path):
If str or bytes is passed in, it is returned unchanged.[](#l3.53)
"""[](#l3.54)
if isinstance(path, (str, bytes)):[](#l3.55)
return path[](#l3.56)
- If str or bytes is passed in, it is returned unchanged. Otherwise the
- os.PathLike interface is used to get the path representation. If the
- path representation is not str or bytes, TypeError is raised. If the
- provided path is not str, bytes, or os.PathLike, TypeError is raised.
- """
- if isinstance(path, (str, bytes)):
return path[](#l3.63)
# Work from the object's type to match method resolution of other magic[](#l3.65)
# methods.[](#l3.66)
path_type = type(path)[](#l3.67)
try:[](#l3.68)
return path_type.__fspath__(path)[](#l3.69)
except AttributeError:[](#l3.70)
if hasattr(path_type, '__fspath__'):[](#l3.71)
raise[](#l3.72)
Work from the object's type to match method resolution of other magic
methods.
- path_type = type(path)
- try:
path_repr = path_type.__fspath__(path)[](#l3.77)
- except AttributeError:
if hasattr(path_type, '__fspath__'):[](#l3.79)
raise[](#l3.80)
else:[](#l3.81)
raise TypeError("expected str, bytes or os.PathLike object, "[](#l3.82)
"not " + path_type.__name__)[](#l3.83)
- if isinstance(path_repr, (str, bytes)):
return path_repr[](#l3.85)
- else:
raise TypeError("expected {}.__fspath__() to return str or bytes, "[](#l3.87)
"not {}".format(path_type.__name__,[](#l3.88)
type(path_repr).__name__))[](#l3.89)
raise TypeError("expected str, bytes or os.PathLike object, not "[](#l3.91)
+ path_type.__name__)[](#l3.92)
+# If there is no C implementation, make the pure Python version the +# implementation as transparently as possible. +if not _exists('fspath'):
--- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -879,7 +879,7 @@ class IOTest(unittest.TestCase): check_path_succeeds(PathLike(support.TESTFN.encode('utf-8'))) bad_path = PathLike(TypeError)
with self.assertRaisesRegex(TypeError, 'invalid file'):[](#l4.7)
with self.assertRaises(TypeError):[](#l4.8) self.open(bad_path, 'w')[](#l4.9)
# ensure that refcounting is correct with some error conditions
--- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3112,55 +3112,59 @@ class TestScandir(unittest.TestCase): class TestPEP519(unittest.TestCase):
Abstracted so it can be overridden to test pure Python implementation
if a C version is provided.
- fspath = staticmethod(os.fspath)
- class PathLike:
def __init__(self, path=''):[](#l5.14)
self.path = path[](#l5.15)
def __fspath__(self):[](#l5.16)
return self.path[](#l5.17)
def test_return_bytes(self): for b in b'hello', b'goodbye', b'some/path/and/file':
self.assertEqual(b, os.fspath(b))[](#l5.21)
self.assertEqual(b, self.fspath(b))[](#l5.22)
def test_return_string(self): for s in 'hello', 'goodbye', 'some/path/and/file':
self.assertEqual(s, os.fspath(s))[](#l5.26)
- def test_fsencode_fsdecode_return_pathlike(self):
class PathLike:[](#l5.29)
def __init__(self, path):[](#l5.30)
self.path = path[](#l5.31)
def __fspath__(self):[](#l5.32)
return self.path[](#l5.33)
self.assertEqual(s, self.fspath(s))[](#l5.35)
pathlike = PathLike(p)[](#l5.39)
self.assertEqual(p, os.fspath(pathlike))[](#l5.41)
pathlike = self.PathLike(p)[](#l5.42)
self.assertEqual(p, self.fspath(pathlike))[](#l5.44) self.assertEqual(b"path/like/object", os.fsencode(pathlike))[](#l5.45) self.assertEqual("path/like/object", os.fsdecode(pathlike))[](#l5.46)
- def test_fspathlike(self):
class PathLike:[](#l5.49)
def __init__(self, path=''):[](#l5.50)
self.path = path[](#l5.51)
def __fspath__(self):[](#l5.52)
return self.path[](#l5.53)
self.assertEqual('#feelthegil', os.fspath(PathLike('#feelthegil')))[](#l5.55)
self.assertTrue(issubclass(PathLike, os.PathLike))[](#l5.56)
self.assertTrue(isinstance(PathLike(), os.PathLike))[](#l5.57)
message = 'expected str, bytes or os.PathLike object, not'[](#l5.59)
for fn in (os.fsencode, os.fsdecode):[](#l5.60)
for obj in PathLike(None), None:[](#l5.61)
with self.assertRaisesRegex(TypeError, message):[](#l5.62)
fn(obj)[](#l5.63)
- def test_pathlike(self):
self.assertEqual('#feelthegil', self.fspath(self.PathLike('#feelthegil')))[](#l5.65)
self.assertTrue(issubclass(self.PathLike, os.PathLike))[](#l5.66)
self.assertTrue(isinstance(self.PathLike(), os.PathLike))[](#l5.67)
with self.assertRaises(TypeError):[](#l5.69)
self.fspath(self.PathLike(42))[](#l5.70)
def test_garbage_in_exception_out(self): vapor = type('blah', (), {}) for o in int, type, os, vapor():
self.assertRaises(TypeError, os.fspath, o)[](#l5.75)
self.assertRaises(TypeError, self.fspath, o)[](#l5.76)
def test_argument_required(self): with self.assertRaises(TypeError):
os.fspath()[](#l5.80)
self.fspath()[](#l5.81)
+ + +# Only test if the C version is provided, otherwise TestPEP519 already tested +# the pure Python implementation. +if hasattr(os, "_fspath"):
"""Explicitly test the pure Python implementation of os.fspath()."""[](#l5.89)
fspath = staticmethod(os._fspath)[](#l5.91)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.6.0 alpha 3 Library ------- +- Issue #27186: Update os.fspath()/PyOS_FSPath() to check the return value of
- Issue #18726: All optional parameters of the dump(), dumps(), load() and loads() functions and JSONEncoder and JSONDecoder class constructors in the json module are now keyword-only.
--- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12317,12 +12317,21 @@ PyOS_FSPath(PyObject *path) if (NULL == func) { return PyErr_Format(PyExc_TypeError, "expected str, bytes or os.PathLike object, "
"not %S",[](#l7.7)
path->ob_type);[](#l7.8)
"not %.200s",[](#l7.9)
} path_repr = PyObject_CallFunctionObjArgs(func, NULL); Py_DECREF(func);Py_TYPE(path)->tp_name);[](#l7.10)
- if (!(PyUnicode_Check(path_repr) || PyBytes_Check(path_repr))) {
PyErr_Format(PyExc_TypeError,[](#l7.16)
"expected %.200s.__fspath__() to return str or bytes, "[](#l7.17)
"not %.200s", Py_TYPE(path)->tp_name,[](#l7.18)
Py_TYPE(path_repr)->tp_name);[](#l7.19)
Py_DECREF(path_repr);[](#l7.20)
return NULL;[](#l7.21)
- }