Kaydet (Commit) d62548af authored tarafından Ethan Furman's avatar Ethan Furman

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

üst 228c6369
...@@ -878,11 +878,11 @@ are always available. They are listed here in alphabetical order. ...@@ -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.
......
...@@ -161,6 +161,8 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None, ...@@ -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):
......
...@@ -844,6 +844,32 @@ class IOTest(unittest.TestCase): ...@@ -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):
......
...@@ -238,21 +238,33 @@ _io_open_impl(PyModuleDef *module, PyObject *file, const char *mode, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment