[Python-checkins] cpython: Issue #26027: Support path-like objects in PyUnicode-FSConverter().

brett.cannon python-checkins at python.org
Tue Sep 6 18:50:45 EDT 2016


https://hg.python.org/cpython/rev/d0d9d7f55cb5
changeset:   103157:d0d9d7f55cb5
user:        Brett Cannon <brett at python.org>
date:        Tue Sep 06 15:50:29 2016 -0700
summary:
  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.

files:
  Doc/c-api/unicode.rst   |   5 +-
  Lib/test/test_os.py     |  64 ++++++++++++----------------
  Misc/NEWS               |   5 ++
  Objects/unicodeobject.c |  27 ++++++-----
  4 files changed, 51 insertions(+), 50 deletions(-)


diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst
--- a/Doc/c-api/unicode.rst
+++ b/Doc/c-api/unicode.rst
@@ -810,13 +810,16 @@
 
 .. 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.
    *result* must be a :c:type:`PyBytesObject*` which must be released when it is
    no longer used.
 
    .. versionadded:: 3.1
 
+   .. versionchanged:: 3.6
+      Accepts a :term:`path-like object`.
 
 To decode file names during argument parsing, the ``"O&"`` converter should be
 used, passing :c:func:`PyUnicode_FSDecoder` as the conversion function:
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -100,6 +100,21 @@
         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'):
     with open(filename, "xb", 0) as fp:
         fp.write(content)
@@ -894,15 +909,7 @@
         self.assertEqual(all[1], self.sub2_tree)
 
     def test_file_like_path(self):
-        class FileLike:
-            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))
+        self.test_walk_prune(_PathLike(self.walk_path))
 
     def test_walk_bottom_up(self):
         # Walk bottom-up.
@@ -2124,7 +2131,8 @@
 
     def test_waitpid(self):
         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)
         self.assertEqual(status, (pid, 0))
 
@@ -2833,25 +2841,18 @@
     ]
 
     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
         if os.name == 'nt':
             bytes_fspath = bytes_filename = None
         else:
             bytes_filename = support.TESTFN.encode('ascii')
-            bytes_fspath = PathLike(bytes_filename)
-        fd = os.open(PathLike(str_filename), os.O_WRONLY|os.O_CREAT)
+            bytes_fspath = _PathLike(bytes_filename)
+        fd = os.open(_PathLike(str_filename), os.O_WRONLY|os.O_CREAT)
         self.addCleanup(support.unlink, support.TESTFN)
         self.addCleanup(os.close, fd)
 
-        int_fspath = PathLike(fd)
-        str_fspath = PathLike(str_filename)
+        int_fspath = _PathLike(fd)
+        str_fspath = _PathLike(str_filename)
 
         for name, allow_fd, extra_args, cleanup_fn in self.functions:
             with self.subTest(name=name):
@@ -3205,15 +3206,6 @@
     # if a C version is provided.
     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):
         for b in b'hello', b'goodbye', b'some/path/and/file':
             self.assertEqual(b, self.fspath(b))
@@ -3224,16 +3216,16 @@
 
     def test_fsencode_fsdecode(self):
         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(b"path/like/object", os.fsencode(pathlike))
             self.assertEqual("path/like/object", os.fsdecode(pathlike))
 
     def test_pathlike(self):
-        self.assertEqual('#feelthegil', self.fspath(self.PathLike('#feelthegil')))
-        self.assertTrue(issubclass(self.PathLike, os.PathLike))
-        self.assertTrue(isinstance(self.PathLike(), os.PathLike))
+        self.assertEqual('#feelthegil', self.fspath(_PathLike('#feelthegil')))
+        self.assertTrue(issubclass(_PathLike, os.PathLike))
+        self.assertTrue(isinstance(_PathLike(), os.PathLike))
 
     def test_garbage_in_exception_out(self):
         vapor = type('blah', (), {})
@@ -3245,14 +3237,14 @@
 
     def test_bad_pathlike(self):
         # __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.
         c = type('foo', (), {})
         c.__fspath__ = 1
         self.assertRaises(TypeError, self.fspath, c())
         # __fspath__ raises an exception.
         self.assertRaises(ZeroDivisionError, self.fspath,
-                          self.PathLike(ZeroDivisionError()))
+                          _PathLike(ZeroDivisionError()))
 
 # Only test if the C version is provided, otherwise TestPEP519 already tested
 # the pure Python implementation.
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -188,6 +188,11 @@
 
 - Issue #27573: exit message for code.interact is now configurable.
 
+C API
+-----
+
+- Issue #26027: Add support for path-like objects in PyUnicode_FSConverter().
+
 Tests
 -----
 
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -3842,6 +3842,7 @@
 int
 PyUnicode_FSConverter(PyObject* arg, void* addr)
 {
+    PyObject *path = NULL;
     PyObject *output = NULL;
     Py_ssize_t size;
     void *data;
@@ -3850,22 +3851,22 @@
         *(PyObject**)addr = NULL;
         return 1;
     }
-    if (PyBytes_Check(arg)) {
-        output = arg;
-        Py_INCREF(output);
-    }
-    else if (PyUnicode_Check(arg)) {
-        output = PyUnicode_EncodeFSDefault(arg);
-        if (!output)
+    path = PyOS_FSPath(arg);
+    if (path == NULL) {
+        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));
     }
-    else {
-        PyErr_Format(PyExc_TypeError,
-                     "must be str or bytes, not %.100s",
-                     Py_TYPE(arg)->tp_name);
-        return 0;
-    }
+
     size = PyBytes_GET_SIZE(output);
     data = PyBytes_AS_STRING(output);
     if ((size_t)size != strlen(data)) {

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list