]> granicus.if.org Git - python/commitdiff
issue27186: add open/io.open; patch by Jelle Zijlstra
authorEthan Furman <ethan@stoneleaf.us>
Sat, 4 Jun 2016 21:38:43 +0000 (14:38 -0700)
committerEthan Furman <ethan@stoneleaf.us>
Sat, 4 Jun 2016 21:38:43 +0000 (14:38 -0700)
Doc/library/functions.rst
Lib/_pyio.py
Lib/test/test_io.py
Modules/_io/_iomodule.c

index c3563f369093a1305f79d35dd2cf4b27957925d3..6f7ba1fe8a3ff3e79b3a4096095508b57d9b4fcb 100644 (file)
@@ -878,11 +878,11 @@ are always available.  They are listed here in alphabetical order.
    Open *file* and return a corresponding :term:`file object`.  If the file
    cannot be opened, an :exc:`OSError` is raised.
 
-   *file* is either a string or bytes object giving the pathname (absolute or
-   relative to the current working directory) of the file to be opened or
-   an integer file descriptor of the file to be wrapped.  (If a file descriptor
-   is given, it is closed when the returned I/O object is closed, unless
-   *closefd* is set to ``False``.)
+   *file* is either a string, bytes, or :class:`os.PathLike` object giving the
+   pathname (absolute or relative to the current working directory) of the file
+   to be opened or an integer file descriptor of the file to be wrapped.  (If a
+   file descriptor is given, it is closed when the returned I/O object is
+   closed, unless *closefd* is set to ``False``.)
 
    *mode* is an optional string that specifies the mode in which the file is
    opened.  It defaults to ``'r'`` which means open for reading in text mode.
index 7b89347a4c458afe371f4fe7828002c5b261148c..40df79d3452bd0544917c4a82482165f710d6de2 100644 (file)
@@ -161,6 +161,8 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
     opened in a text mode, and for bytes a BytesIO can be used like a file
     opened in a binary mode.
     """
+    if not isinstance(file, int):
+        file = os.fspath(file)
     if not isinstance(file, (str, bytes, int)):
         raise TypeError("invalid file: %r" % file)
     if not isinstance(mode, str):
index 53e776d9106ef6bd1111e9fb4c5fe742665221cc..5584d6b13ee7e80658af9745751ddf03ba641c47 100644 (file)
@@ -844,6 +844,32 @@ class IOTest(unittest.TestCase):
                 self.assertEqual(getattr(stream, method)(buffer), 5)
                 self.assertEqual(bytes(buffer), b"12345")
 
+    def test_fspath_support(self):
+        class PathLike:
+            def __init__(self, path):
+                self.path = path
+
+            def __fspath__(self):
+                return self.path
+
+        def check_path_succeeds(path):
+            with self.open(path, "w") as f:
+                f.write("egg\n")
+
+            with self.open(path, "r") as f:
+                self.assertEqual(f.read(), "egg\n")
+
+        check_path_succeeds(PathLike(support.TESTFN))
+        check_path_succeeds(PathLike(support.TESTFN.encode('utf-8')))
+
+        bad_path = PathLike(TypeError)
+        with self.assertRaisesRegex(TypeError, 'invalid file'):
+            self.open(bad_path, 'w')
+
+        # ensure that refcounting is correct with some error conditions
+        with self.assertRaisesRegex(ValueError, 'read/write/append mode'):
+            self.open(PathLike(support.TESTFN), 'rwxa')
+
 
 class CIOTest(IOTest):
 
index d8aa1df43261792d0b3dfa0203489d5066a3f06f..85b1813c926d5f3043aa25079b01df9c0828bbe3 100644 (file)
@@ -238,21 +238,33 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
     int text = 0, binary = 0, universal = 0;
 
     char rawmode[6], *m;
-    int line_buffering;
+    int line_buffering, is_number;
     long isatty;
 
-    PyObject *raw, *modeobj = NULL, *buffer, *wrapper, *result = NULL;
+    PyObject *raw, *modeobj = NULL, *buffer, *wrapper, *result = NULL, *path_or_fd = NULL;
 
     _Py_IDENTIFIER(_blksize);
     _Py_IDENTIFIER(isatty);
     _Py_IDENTIFIER(mode);
     _Py_IDENTIFIER(close);
 
-    if (!PyUnicode_Check(file) &&
-        !PyBytes_Check(file) &&
-        !PyNumber_Check(file)) {
+    is_number = PyNumber_Check(file);
+
+    if (is_number) {
+        path_or_fd = file;
+        Py_INCREF(path_or_fd);
+    } else {
+        path_or_fd = PyOS_FSPath(file);
+        if (path_or_fd == NULL) {
+            return NULL;
+        }
+    }
+
+    if (!is_number &&
+        !PyUnicode_Check(path_or_fd) &&
+        !PyBytes_Check(path_or_fd)) {
         PyErr_Format(PyExc_TypeError, "invalid file: %R", file);
-        return NULL;
+        goto error;
     }
 
     /* Decode mode */
@@ -293,7 +305,7 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
         if (strchr(mode+i+1, c)) {
           invalid_mode:
             PyErr_Format(PyExc_ValueError, "invalid mode: '%s'", mode);
-            return NULL;
+            goto error;
         }
 
     }
@@ -311,51 +323,54 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
         if (creating || writing || appending || updating) {
             PyErr_SetString(PyExc_ValueError,
                             "mode U cannot be combined with x', 'w', 'a', or '+'");
-            return NULL;
+            goto error;
         }
         if (PyErr_WarnEx(PyExc_DeprecationWarning,
                          "'U' mode is deprecated", 1) < 0)
-            return NULL;
+            goto error;
         reading = 1;
     }
 
     if (text && binary) {
         PyErr_SetString(PyExc_ValueError,
                         "can't have text and binary mode at once");
-        return NULL;
+        goto error;
     }
 
     if (creating + reading + writing + appending > 1) {
         PyErr_SetString(PyExc_ValueError,
                         "must have exactly one of create/read/write/append mode");
-        return NULL;
+        goto error;
     }
 
     if (binary && encoding != NULL) {
         PyErr_SetString(PyExc_ValueError,
                         "binary mode doesn't take an encoding argument");
-        return NULL;
+        goto error;
     }
 
     if (binary && errors != NULL) {
         PyErr_SetString(PyExc_ValueError,
                         "binary mode doesn't take an errors argument");
-        return NULL;
+        goto error;
     }
 
     if (binary && newline != NULL) {
         PyErr_SetString(PyExc_ValueError,
                         "binary mode doesn't take a newline argument");
-        return NULL;
+        goto error;
     }
 
     /* Create the Raw file stream */
     raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type,
-                                "OsiO", file, rawmode, closefd, opener);
+                                "OsiO", path_or_fd, rawmode, closefd, opener);
     if (raw == NULL)
-        return NULL;
+        goto error;
     result = raw;
 
+    Py_DECREF(path_or_fd);
+    path_or_fd = NULL;
+
     modeobj = PyUnicode_FromString(mode);
     if (modeobj == NULL)
         goto error;
@@ -461,6 +476,7 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
         Py_XDECREF(close_result);
         Py_DECREF(result);
     }
+    Py_XDECREF(path_or_fd);
     Py_XDECREF(modeobj);
     return NULL;
 }