mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue #26027: Support path-like objects in PyUnicode-FSConverter().
This is to add support for os.exec*() and os.spawn*() functions. Part of PEP 519.
This commit is contained in:
parent
dc5a3fe4ef
commit
ec6ce879c7
4 changed files with 52 additions and 51 deletions
|
@ -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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
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):
|
||||||
]
|
]
|
||||||
|
|
||||||
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):
|
||||||
# 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):
|
||||||
|
|
||||||
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):
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
- 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
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
*(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);
|
|
||||||
}
|
|
||||||
else if (PyUnicode_Check(arg)) {
|
|
||||||
output = PyUnicode_EncodeFSDefault(arg);
|
|
||||||
if (!output)
|
|
||||||
return 0;
|
|
||||||
assert(PyBytes_Check(output));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PyErr_Format(PyExc_TypeError,
|
|
||||||
"must be str or bytes, not %.100s",
|
|
||||||
Py_TYPE(arg)->tp_name);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (PyBytes_Check(path)) {
|
||||||
|
output = path;
|
||||||
|
}
|
||||||
|
else { // PyOS_FSPath() guarantees its returned value is bytes or str.
|
||||||
|
output = PyUnicode_EncodeFSDefault(path);
|
||||||
|
Py_DECREF(path);
|
||||||
|
if (!output) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
assert(PyBytes_Check(output));
|
||||||
|
}
|
||||||
|
|
||||||
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)) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue