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
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.

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 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):

View 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):

View 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;
}