From 410ef8e23088ab2b8bd92ac70a8176f71da2b931 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Sat, 4 Jun 2016 12:06:26 -0700 Subject: [PATCH] issue27186: add C version of os.fspath(); patch by Jelle Zijlstra --- Include/Python.h | 1 + Include/osmodule.h | 15 ++++++++++ Lib/os.py | 35 +++++++++++------------ Lib/test/test_os.py | 7 +++++ Modules/clinic/posixmodule.c.h | 34 ++++++++++++++++++++++- Modules/posixmodule.c | 51 ++++++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 18 deletions(-) create mode 100644 Include/osmodule.h diff --git a/Include/Python.h b/Include/Python.h index 858dbd1a66a..4c7c9a48c81 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -116,6 +116,7 @@ #include "pylifecycle.h" #include "ceval.h" #include "sysmodule.h" +#include "osmodule.h" #include "intrcheck.h" #include "import.h" diff --git a/Include/osmodule.h b/Include/osmodule.h new file mode 100644 index 00000000000..71467577cb6 --- /dev/null +++ b/Include/osmodule.h @@ -0,0 +1,15 @@ + +/* os module interface */ + +#ifndef Py_OSMODULE_H +#define Py_OSMODULE_H +#ifdef __cplusplus +extern "C" { +#endif + +PyAPI_FUNC(PyObject *) PyOS_FSPath(PyObject *path); + +#ifdef __cplusplus +} +#endif +#endif /* !Py_OSMODULE_H */ diff --git a/Lib/os.py b/Lib/os.py index 1318de6b5ad..0131ed8195e 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -1104,23 +1104,24 @@ def fdopen(fd, *args, **kwargs): import io return io.open(fd, *args, **kwargs) -# Supply os.fspath() -def fspath(path): - """Return the string representation of the path. +# Supply os.fspath() if not defined in C +if not _exists('fspath'): + def fspath(path): + """Return the string representation of the path. - If str or bytes is passed in, it is returned unchanged. - """ - if isinstance(path, (str, bytes)): - return path + If str or bytes is passed in, it is returned unchanged. + """ + if isinstance(path, (str, bytes)): + return path - # Work from the object's type to match method resolution of other magic - # methods. - path_type = type(path) - try: - return path_type.__fspath__(path) - except AttributeError: - if hasattr(path_type, '__fspath__'): - raise + # Work from the object's type to match method resolution of other magic + # methods. + path_type = type(path) + try: + return path_type.__fspath__(path) + except AttributeError: + if hasattr(path_type, '__fspath__'): + raise - raise TypeError("expected str, bytes or os.PathLike object, not " - + path_type.__name__) + raise TypeError("expected str, bytes or os.PathLike object, not " + + path_type.__name__) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 84ef150f827..bf06438db22 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3121,6 +3121,13 @@ class TestPEP519(unittest.TestCase): self.assertEqual(b"path/like/object", os.fsencode(pathlike)) self.assertEqual("path/like/object", os.fsdecode(pathlike)) + def test_fspathlike(self): + class PathLike(object): + def __fspath__(self): + return '#feelthegil' + + self.assertEqual('#feelthegil', os.fspath(PathLike())) + def test_garbage_in_exception_out(self): vapor = type('blah', (), {}) for o in int, type, os, vapor(): diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index a48de6ac882..2758d48cf03 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -5321,6 +5321,38 @@ exit: #endif /* defined(MS_WINDOWS) */ +PyDoc_STRVAR(os_fspath__doc__, +"fspath($module, /, path)\n" +"--\n" +"\n" +"Return the file system path representation of the object.\n" +"\n" +"If the object is str or bytes, then allow it to pass through with\n" +"an incremented refcount. If the object defines __fspath__(), then\n" +"return the result of that method. All other types raise a TypeError."); + +#define OS_FSPATH_METHODDEF \ + {"fspath", (PyCFunction)os_fspath, METH_VARARGS|METH_KEYWORDS, os_fspath__doc__}, + +static PyObject * +os_fspath_impl(PyModuleDef *module, PyObject *path); + +static PyObject * +os_fspath(PyModuleDef *module, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + static char *_keywords[] = {"path", NULL}; + PyObject *path; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:fspath", _keywords, + &path)) + goto exit; + return_value = os_fspath_impl(module, path); + +exit: + return return_value; +} + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -5792,4 +5824,4 @@ exit: #ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF #define OS_SET_HANDLE_INHERITABLE_METHODDEF #endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */ -/*[clinic end generated code: output=a5c9bef9ad11a20b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e64e246b8270abda input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ded6d716eb2..c55226576c9 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12284,6 +12284,56 @@ error: return NULL; } +/* + Return the file system path representation of the object. + + If the object is str or bytes, then allow it to pass through with + an incremented refcount. If the object defines __fspath__(), then + return the result of that method. All other types raise a TypeError. +*/ +PyObject * +PyOS_FSPath(PyObject *path) +{ + _Py_IDENTIFIER(__fspath__); + PyObject *func = NULL; + PyObject *path_repr = NULL; + + if (PyUnicode_Check(path) || PyBytes_Check(path)) { + Py_INCREF(path); + return path; + } + + func = _PyObject_LookupSpecial(path, &PyId___fspath__); + if (NULL == func) { + return PyErr_Format(PyExc_TypeError, + "expected str, bytes or os.PathLike object, " + "not %S", + path->ob_type); + } + + path_repr = PyObject_CallFunctionObjArgs(func, NULL); + Py_DECREF(func); + return path_repr; +} + +/*[clinic input] +os.fspath + + path: object + +Return the file system path representation of the object. + +If the object is str or bytes, then allow it to pass through with +an incremented refcount. If the object defines __fspath__(), then +return the result of that method. All other types raise a TypeError. +[clinic start generated code]*/ + +static PyObject * +os_fspath_impl(PyModuleDef *module, PyObject *path) +/*[clinic end generated code: output=51ef0c2772c1932a input=652c7c37e4be1c13]*/ +{ + return PyOS_FSPath(path); +} #include "clinic/posixmodule.c.h" @@ -12484,6 +12534,7 @@ static PyMethodDef posix_methods[] = { {"scandir", (PyCFunction)posix_scandir, METH_VARARGS | METH_KEYWORDS, posix_scandir__doc__}, + OS_FSPATH_METHODDEF {NULL, NULL} /* Sentinel */ };