summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEthan Furman <ethan@stoneleaf.us>2016-06-04 21:38:43 (GMT)
committerEthan Furman <ethan@stoneleaf.us>2016-06-04 21:38:43 (GMT)
commitd62548afede899e71e59a2a0b31f19fdf031c560 (patch)
tree11dc76fe4b5c89d93c63e8bdf4b9ec535fde6451
parent228c636908bda8a6b20b0f6930655fbaedc4ebad (diff)
downloadcpython-d62548afede899e71e59a2a0b31f19fdf031c560.zip
cpython-d62548afede899e71e59a2a0b31f19fdf031c560.tar.gz
cpython-d62548afede899e71e59a2a0b31f19fdf031c560.tar.bz2
issue27186: add open/io.open; patch by Jelle Zijlstra
-rw-r--r--Doc/library/functions.rst10
-rw-r--r--Lib/_pyio.py2
-rw-r--r--Lib/test/test_io.py26
-rw-r--r--Modules/_io/_iomodule.c48
4 files changed, 65 insertions, 21 deletions
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index c3563f3..6f7ba1f 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -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.
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 7b89347..40df79d 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -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):
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 53e776d..5584d6b 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -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):
diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c
index d8aa1df..85b1813 100644
--- a/Modules/_io/_iomodule.c
+++ b/Modules/_io/_iomodule.c
@@ -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;
}