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.
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.
......
......@@ -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):
......
......@@ -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):
......
......@@ -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;
}
......
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