Commit 7103ff94 authored by Benjamin Peterson's avatar Benjamin Peterson

merge heads

parents 0cf22387 581139cb
...@@ -810,13 +810,16 @@ used, passing :c:func:`PyUnicode_FSConverter` as the conversion function: ...@@ -810,13 +810,16 @@ used, passing :c:func:`PyUnicode_FSConverter` as the conversion function:
.. c:function:: int PyUnicode_FSConverter(PyObject* obj, void* result) .. c:function:: int PyUnicode_FSConverter(PyObject* obj, void* result)
ParseTuple converter: encode :class:`str` objects to :class:`bytes` using ParseTuple converter: encode :class:`str` objects -- obtained directly or
through the :class:`os.PathLike` interface -- to :class:`bytes` using
:c:func:`PyUnicode_EncodeFSDefault`; :class:`bytes` objects are output as-is. :c:func:`PyUnicode_EncodeFSDefault`; :class:`bytes` objects are output as-is.
*result* must be a :c:type:`PyBytesObject*` which must be released when it is *result* must be a :c:type:`PyBytesObject*` which must be released when it is
no longer used. no longer used.
.. versionadded:: 3.1 .. versionadded:: 3.1
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.
To decode file names during argument parsing, the ``"O&"`` converter should be To decode file names during argument parsing, the ``"O&"`` converter should be
used, passing :c:func:`PyUnicode_FSDecoder` as the conversion function: used, passing :c:func:`PyUnicode_FSDecoder` as the conversion function:
......
...@@ -100,6 +100,21 @@ def bytes_filename_warn(expected): ...@@ -100,6 +100,21 @@ def bytes_filename_warn(expected):
yield yield
class _PathLike(os.PathLike):
def __init__(self, path=""):
self.path = path
def __str__(self):
return str(self.path)
def __fspath__(self):
if isinstance(self.path, BaseException):
raise self.path
else:
return self.path
def create_file(filename, content=b'content'): def create_file(filename, content=b'content'):
with open(filename, "xb", 0) as fp: with open(filename, "xb", 0) as fp:
fp.write(content) fp.write(content)
...@@ -894,15 +909,7 @@ class WalkTests(unittest.TestCase): ...@@ -894,15 +909,7 @@ class WalkTests(unittest.TestCase):
self.assertEqual(all[1], self.sub2_tree) self.assertEqual(all[1], self.sub2_tree)
def test_file_like_path(self): def test_file_like_path(self):
class FileLike: self.test_walk_prune(_PathLike(self.walk_path))
def __init__(self, path):
self._path = path
def __str__(self):
return str(self._path)
def __fspath__(self):
return self._path
self.test_walk_prune(FileLike(self.walk_path))
def test_walk_bottom_up(self): def test_walk_bottom_up(self):
# Walk bottom-up. # Walk bottom-up.
...@@ -2124,7 +2131,8 @@ class PidTests(unittest.TestCase): ...@@ -2124,7 +2131,8 @@ class PidTests(unittest.TestCase):
def test_waitpid(self): def test_waitpid(self):
args = [sys.executable, '-c', 'pass'] args = [sys.executable, '-c', 'pass']
pid = os.spawnv(os.P_NOWAIT, args[0], args) # Add an implicit test for PyUnicode_FSConverter().
pid = os.spawnv(os.P_NOWAIT, _PathLike(args[0]), args)
status = os.waitpid(pid, 0) status = os.waitpid(pid, 0)
self.assertEqual(status, (pid, 0)) self.assertEqual(status, (pid, 0))
...@@ -2833,25 +2841,18 @@ class PathTConverterTests(unittest.TestCase): ...@@ -2833,25 +2841,18 @@ class PathTConverterTests(unittest.TestCase):
] ]
def test_path_t_converter(self): def test_path_t_converter(self):
class PathLike:
def __init__(self, path):
self.path = path
def __fspath__(self):
return self.path
str_filename = support.TESTFN str_filename = support.TESTFN
if os.name == 'nt': if os.name == 'nt':
bytes_fspath = bytes_filename = None bytes_fspath = bytes_filename = None
else: else:
bytes_filename = support.TESTFN.encode('ascii') bytes_filename = support.TESTFN.encode('ascii')
bytes_fspath = PathLike(bytes_filename) bytes_fspath = _PathLike(bytes_filename)
fd = os.open(PathLike(str_filename), os.O_WRONLY|os.O_CREAT) fd = os.open(_PathLike(str_filename), os.O_WRONLY|os.O_CREAT)
self.addCleanup(support.unlink, support.TESTFN) self.addCleanup(support.unlink, support.TESTFN)
self.addCleanup(os.close, fd) self.addCleanup(os.close, fd)
int_fspath = PathLike(fd) int_fspath = _PathLike(fd)
str_fspath = PathLike(str_filename) str_fspath = _PathLike(str_filename)
for name, allow_fd, extra_args, cleanup_fn in self.functions: for name, allow_fd, extra_args, cleanup_fn in self.functions:
with self.subTest(name=name): with self.subTest(name=name):
...@@ -3205,15 +3206,6 @@ class TestPEP519(unittest.TestCase): ...@@ -3205,15 +3206,6 @@ class TestPEP519(unittest.TestCase):
# if a C version is provided. # if a C version is provided.
fspath = staticmethod(os.fspath) fspath = staticmethod(os.fspath)
class PathLike:
def __init__(self, path=''):
self.path = path
def __fspath__(self):
if isinstance(self.path, BaseException):
raise self.path
else:
return self.path
def test_return_bytes(self): def test_return_bytes(self):
for b in b'hello', b'goodbye', b'some/path/and/file': for b in b'hello', b'goodbye', b'some/path/and/file':
self.assertEqual(b, self.fspath(b)) self.assertEqual(b, self.fspath(b))
...@@ -3224,16 +3216,16 @@ class TestPEP519(unittest.TestCase): ...@@ -3224,16 +3216,16 @@ class TestPEP519(unittest.TestCase):
def test_fsencode_fsdecode(self): def test_fsencode_fsdecode(self):
for p in "path/like/object", b"path/like/object": for p in "path/like/object", b"path/like/object":
pathlike = self.PathLike(p) pathlike = _PathLike(p)
self.assertEqual(p, self.fspath(pathlike)) self.assertEqual(p, self.fspath(pathlike))
self.assertEqual(b"path/like/object", os.fsencode(pathlike)) self.assertEqual(b"path/like/object", os.fsencode(pathlike))
self.assertEqual("path/like/object", os.fsdecode(pathlike)) self.assertEqual("path/like/object", os.fsdecode(pathlike))
def test_pathlike(self): def test_pathlike(self):
self.assertEqual('#feelthegil', self.fspath(self.PathLike('#feelthegil'))) self.assertEqual('#feelthegil', self.fspath(_PathLike('#feelthegil')))
self.assertTrue(issubclass(self.PathLike, os.PathLike)) self.assertTrue(issubclass(_PathLike, os.PathLike))
self.assertTrue(isinstance(self.PathLike(), os.PathLike)) self.assertTrue(isinstance(_PathLike(), os.PathLike))
def test_garbage_in_exception_out(self): def test_garbage_in_exception_out(self):
vapor = type('blah', (), {}) vapor = type('blah', (), {})
...@@ -3245,14 +3237,14 @@ class TestPEP519(unittest.TestCase): ...@@ -3245,14 +3237,14 @@ class TestPEP519(unittest.TestCase):
def test_bad_pathlike(self): def test_bad_pathlike(self):
# __fspath__ returns a value other than str or bytes. # __fspath__ returns a value other than str or bytes.
self.assertRaises(TypeError, self.fspath, self.PathLike(42)) self.assertRaises(TypeError, self.fspath, _PathLike(42))
# __fspath__ attribute that is not callable. # __fspath__ attribute that is not callable.
c = type('foo', (), {}) c = type('foo', (), {})
c.__fspath__ = 1 c.__fspath__ = 1
self.assertRaises(TypeError, self.fspath, c()) self.assertRaises(TypeError, self.fspath, c())
# __fspath__ raises an exception. # __fspath__ raises an exception.
self.assertRaises(ZeroDivisionError, self.fspath, self.assertRaises(ZeroDivisionError, self.fspath,
self.PathLike(ZeroDivisionError())) _PathLike(ZeroDivisionError()))
# Only test if the C version is provided, otherwise TestPEP519 already tested # Only test if the C version is provided, otherwise TestPEP519 already tested
# the pure Python implementation. # the pure Python implementation.
......
...@@ -188,6 +188,11 @@ Library ...@@ -188,6 +188,11 @@ Library
- Issue #27573: exit message for code.interact is now configurable. - Issue #27573: exit message for code.interact is now configurable.
C API
-----
- Issue #26027: Add support for path-like objects in PyUnicode_FSConverter().
Tests Tests
----- -----
......
...@@ -3011,13 +3011,13 @@ PyDoc_STRVAR(os_waitpid__doc__, ...@@ -3011,13 +3011,13 @@ PyDoc_STRVAR(os_waitpid__doc__,
{"waitpid", (PyCFunction)os_waitpid, METH_VARARGS, os_waitpid__doc__}, {"waitpid", (PyCFunction)os_waitpid, METH_VARARGS, os_waitpid__doc__},
static PyObject * static PyObject *
os_waitpid_impl(PyObject *module, Py_intptr_t pid, int options); os_waitpid_impl(PyObject *module, intptr_t pid, int options);
static PyObject * static PyObject *
os_waitpid(PyObject *module, PyObject *args) os_waitpid(PyObject *module, PyObject *args)
{ {
PyObject *return_value = NULL; PyObject *return_value = NULL;
Py_intptr_t pid; intptr_t pid;
int options; int options;
if (!PyArg_ParseTuple(args, "" _Py_PARSE_INTPTR "i:waitpid", if (!PyArg_ParseTuple(args, "" _Py_PARSE_INTPTR "i:waitpid",
...@@ -5479,13 +5479,13 @@ PyDoc_STRVAR(os_get_handle_inheritable__doc__, ...@@ -5479,13 +5479,13 @@ PyDoc_STRVAR(os_get_handle_inheritable__doc__,
{"get_handle_inheritable", (PyCFunction)os_get_handle_inheritable, METH_O, os_get_handle_inheritable__doc__}, {"get_handle_inheritable", (PyCFunction)os_get_handle_inheritable, METH_O, os_get_handle_inheritable__doc__},
static int static int
os_get_handle_inheritable_impl(PyObject *module, Py_intptr_t handle); os_get_handle_inheritable_impl(PyObject *module, intptr_t handle);
static PyObject * static PyObject *
os_get_handle_inheritable(PyObject *module, PyObject *arg) os_get_handle_inheritable(PyObject *module, PyObject *arg)
{ {
PyObject *return_value = NULL; PyObject *return_value = NULL;
Py_intptr_t handle; intptr_t handle;
int _return_value; int _return_value;
if (!PyArg_Parse(arg, "" _Py_PARSE_INTPTR ":get_handle_inheritable", &handle)) { if (!PyArg_Parse(arg, "" _Py_PARSE_INTPTR ":get_handle_inheritable", &handle)) {
...@@ -5515,14 +5515,14 @@ PyDoc_STRVAR(os_set_handle_inheritable__doc__, ...@@ -5515,14 +5515,14 @@ PyDoc_STRVAR(os_set_handle_inheritable__doc__,
{"set_handle_inheritable", (PyCFunction)os_set_handle_inheritable, METH_VARARGS, os_set_handle_inheritable__doc__}, {"set_handle_inheritable", (PyCFunction)os_set_handle_inheritable, METH_VARARGS, os_set_handle_inheritable__doc__},
static PyObject * static PyObject *
os_set_handle_inheritable_impl(PyObject *module, Py_intptr_t handle, os_set_handle_inheritable_impl(PyObject *module, intptr_t handle,
int inheritable); int inheritable);
static PyObject * static PyObject *
os_set_handle_inheritable(PyObject *module, PyObject *args) os_set_handle_inheritable(PyObject *module, PyObject *args)
{ {
PyObject *return_value = NULL; PyObject *return_value = NULL;
Py_intptr_t handle; intptr_t handle;
int inheritable; int inheritable;
if (!PyArg_ParseTuple(args, "" _Py_PARSE_INTPTR "p:set_handle_inheritable", if (!PyArg_ParseTuple(args, "" _Py_PARSE_INTPTR "p:set_handle_inheritable",
...@@ -6042,4 +6042,4 @@ exit: ...@@ -6042,4 +6042,4 @@ exit:
#ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF #ifndef OS_SET_HANDLE_INHERITABLE_METHODDEF
#define OS_SET_HANDLE_INHERITABLE_METHODDEF #define OS_SET_HANDLE_INHERITABLE_METHODDEF
#endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */ #endif /* !defined(OS_SET_HANDLE_INHERITABLE_METHODDEF) */
/*[clinic end generated code: output=2b85bb3703a6488a input=a9049054013a1b77]*/ /*[clinic end generated code: output=677ce794fb126161 input=a9049054013a1b77]*/
...@@ -2520,7 +2520,7 @@ class sched_param_converter(CConverter): ...@@ -2520,7 +2520,7 @@ class sched_param_converter(CConverter):
impl_by_reference = True; impl_by_reference = True;
[python start generated code]*/ [python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=affe68316f160401]*/ /*[python end generated code: output=da39a3ee5e6b4b0d input=418fce0e01144461]*/
/*[clinic input] /*[clinic input]
...@@ -7092,7 +7092,7 @@ The options argument is ignored on Windows. ...@@ -7092,7 +7092,7 @@ The options argument is ignored on Windows.
static PyObject * static PyObject *
os_waitpid_impl(PyObject *module, intptr_t pid, int options) os_waitpid_impl(PyObject *module, intptr_t pid, int options)
/*[clinic end generated code: output=15f1ce005a346b09 input=444c8f51cca5b862]*/ /*[clinic end generated code: output=be836b221271d538 input=40f2440c515410f8]*/
{ {
int status; int status;
intptr_t res; intptr_t res;
...@@ -11383,7 +11383,7 @@ Get the close-on-exe flag of the specified file descriptor. ...@@ -11383,7 +11383,7 @@ Get the close-on-exe flag of the specified file descriptor.
static int static int
os_get_handle_inheritable_impl(PyObject *module, intptr_t handle) os_get_handle_inheritable_impl(PyObject *module, intptr_t handle)
/*[clinic end generated code: output=9e5389b0aa0916ce input=5f7759443aae3dc5]*/ /*[clinic end generated code: output=36be5afca6ea84d8 input=cfe99f9c05c70ad1]*/
{ {
DWORD flags; DWORD flags;
...@@ -11408,7 +11408,7 @@ Set the inheritable flag of the specified handle. ...@@ -11408,7 +11408,7 @@ Set the inheritable flag of the specified handle.
static PyObject * static PyObject *
os_set_handle_inheritable_impl(PyObject *module, intptr_t handle, os_set_handle_inheritable_impl(PyObject *module, intptr_t handle,
int inheritable) int inheritable)
/*[clinic end generated code: output=b1e67bfa3213d745 input=e64b2b2730469def]*/ /*[clinic end generated code: output=021d74fe6c96baa3 input=7a7641390d8364fc]*/
{ {
DWORD flags = inheritable ? HANDLE_FLAG_INHERIT : 0; DWORD flags = inheritable ? HANDLE_FLAG_INHERIT : 0;
if (!SetHandleInformation((HANDLE)handle, HANDLE_FLAG_INHERIT, flags)) { if (!SetHandleInformation((HANDLE)handle, HANDLE_FLAG_INHERIT, flags)) {
......
...@@ -3842,6 +3842,7 @@ PyUnicode_DecodeFSDefaultAndSize(const char *s, Py_ssize_t size) ...@@ -3842,6 +3842,7 @@ PyUnicode_DecodeFSDefaultAndSize(const char *s, Py_ssize_t size)
int int
PyUnicode_FSConverter(PyObject* arg, void* addr) PyUnicode_FSConverter(PyObject* arg, void* addr)
{ {
PyObject *path = NULL;
PyObject *output = NULL; PyObject *output = NULL;
Py_ssize_t size; Py_ssize_t size;
void *data; void *data;
...@@ -3850,22 +3851,22 @@ PyUnicode_FSConverter(PyObject* arg, void* addr) ...@@ -3850,22 +3851,22 @@ PyUnicode_FSConverter(PyObject* arg, void* addr)
*(PyObject**)addr = NULL; *(PyObject**)addr = NULL;
return 1; return 1;
} }
if (PyBytes_Check(arg)) { path = PyOS_FSPath(arg);
output = arg; if (path == NULL) {
Py_INCREF(output); return 0;
} }
else if (PyUnicode_Check(arg)) { if (PyBytes_Check(path)) {
output = PyUnicode_EncodeFSDefault(arg); output = path;
if (!output) }
else { // PyOS_FSPath() guarantees its returned value is bytes or str.
output = PyUnicode_EncodeFSDefault(path);
Py_DECREF(path);
if (!output) {
return 0; return 0;
}
assert(PyBytes_Check(output)); assert(PyBytes_Check(output));
} }
else {
PyErr_Format(PyExc_TypeError,
"must be str or bytes, not %.100s",
Py_TYPE(arg)->tp_name);
return 0;
}
size = PyBytes_GET_SIZE(output); size = PyBytes_GET_SIZE(output);
data = PyBytes_AS_STRING(output); data = PyBytes_AS_STRING(output);
if ((size_t)size != strlen(data)) { if ((size_t)size != strlen(data)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment