issue27186: add open/io.open; patch by Jelle Zijlstra

This commit is contained in:
Ethan Furman 2016-06-04 14:38:43 -07:00
parent 228c636908
commit d62548afed
4 changed files with 65 additions and 21 deletions

View 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 Open *file* and return a corresponding :term:`file object`. If the file
cannot be opened, an :exc:`OSError` is raised. cannot be opened, an :exc:`OSError` is raised.
*file* is either a string or bytes object giving the pathname (absolute or *file* is either a string, bytes, or :class:`os.PathLike` object giving the
relative to the current working directory) of the file to be opened or pathname (absolute or relative to the current working directory) of the file
an integer file descriptor of the file to be wrapped. (If a file descriptor to be opened or an integer file descriptor of the file to be wrapped. (If a
is given, it is closed when the returned I/O object is closed, unless file descriptor is given, it is closed when the returned I/O object is
*closefd* is set to ``False``.) closed, unless *closefd* is set to ``False``.)
*mode* is an optional string that specifies the mode in which the file is *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. opened. It defaults to ``'r'`` which means open for reading in text mode.

View 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 text mode, and for bytes a BytesIO can be used like a file
opened in a binary mode. opened in a binary mode.
""" """
if not isinstance(file, int):
file = os.fspath(file)
if not isinstance(file, (str, bytes, int)): if not isinstance(file, (str, bytes, int)):
raise TypeError("invalid file: %r" % file) raise TypeError("invalid file: %r" % file)
if not isinstance(mode, str): if not isinstance(mode, str):

View File

@ -844,6 +844,32 @@ class IOTest(unittest.TestCase):
self.assertEqual(getattr(stream, method)(buffer), 5) self.assertEqual(getattr(stream, method)(buffer), 5)
self.assertEqual(bytes(buffer), b"12345") 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): class CIOTest(IOTest):

View File

@ -238,21 +238,33 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
int text = 0, binary = 0, universal = 0; int text = 0, binary = 0, universal = 0;
char rawmode[6], *m; char rawmode[6], *m;
int line_buffering; int line_buffering, is_number;
long isatty; 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(_blksize);
_Py_IDENTIFIER(isatty); _Py_IDENTIFIER(isatty);
_Py_IDENTIFIER(mode); _Py_IDENTIFIER(mode);
_Py_IDENTIFIER(close); _Py_IDENTIFIER(close);
if (!PyUnicode_Check(file) && is_number = PyNumber_Check(file);
!PyBytes_Check(file) &&
!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); PyErr_Format(PyExc_TypeError, "invalid file: %R", file);
return NULL; goto error;
} }
/* Decode mode */ /* Decode mode */
@ -293,7 +305,7 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
if (strchr(mode+i+1, c)) { if (strchr(mode+i+1, c)) {
invalid_mode: invalid_mode:
PyErr_Format(PyExc_ValueError, "invalid mode: '%s'", 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) { if (creating || writing || appending || updating) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"mode U cannot be combined with x', 'w', 'a', or '+'"); "mode U cannot be combined with x', 'w', 'a', or '+'");
return NULL; goto error;
} }
if (PyErr_WarnEx(PyExc_DeprecationWarning, if (PyErr_WarnEx(PyExc_DeprecationWarning,
"'U' mode is deprecated", 1) < 0) "'U' mode is deprecated", 1) < 0)
return NULL; goto error;
reading = 1; reading = 1;
} }
if (text && binary) { if (text && binary) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"can't have text and binary mode at once"); "can't have text and binary mode at once");
return NULL; goto error;
} }
if (creating + reading + writing + appending > 1) { if (creating + reading + writing + appending > 1) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"must have exactly one of create/read/write/append mode"); "must have exactly one of create/read/write/append mode");
return NULL; goto error;
} }
if (binary && encoding != NULL) { if (binary && encoding != NULL) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"binary mode doesn't take an encoding argument"); "binary mode doesn't take an encoding argument");
return NULL; goto error;
} }
if (binary && errors != NULL) { if (binary && errors != NULL) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"binary mode doesn't take an errors argument"); "binary mode doesn't take an errors argument");
return NULL; goto error;
} }
if (binary && newline != NULL) { if (binary && newline != NULL) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"binary mode doesn't take a newline argument"); "binary mode doesn't take a newline argument");
return NULL; goto error;
} }
/* Create the Raw file stream */ /* Create the Raw file stream */
raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type, raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type,
"OsiO", file, rawmode, closefd, opener); "OsiO", path_or_fd, rawmode, closefd, opener);
if (raw == NULL) if (raw == NULL)
return NULL; goto error;
result = raw; result = raw;
Py_DECREF(path_or_fd);
path_or_fd = NULL;
modeobj = PyUnicode_FromString(mode); modeobj = PyUnicode_FromString(mode);
if (modeobj == NULL) if (modeobj == NULL)
goto error; goto error;
@ -461,6 +476,7 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode,
Py_XDECREF(close_result); Py_XDECREF(close_result);
Py_DECREF(result); Py_DECREF(result);
} }
Py_XDECREF(path_or_fd);
Py_XDECREF(modeobj); Py_XDECREF(modeobj);
return NULL; return NULL;
} }