Issue #12797: Added custom opener parameter to builtin open() and FileIO.open().

This commit is contained in:
Ross Lagerwall 2011-10-31 20:34:46 +02:00
parent ab06e3f285
commit 59142db6d3
7 changed files with 89 additions and 22 deletions

View File

@ -776,7 +776,7 @@ are always available. They are listed here in alphabetical order.
:meth:`__index__` method that returns an integer. :meth:`__index__` method that returns an integer.
.. function:: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True) .. function:: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
Open *file* and return a corresponding stream. If the file cannot be opened, Open *file* and return a corresponding stream. If the file cannot be opened,
an :exc:`OSError` is raised. an :exc:`OSError` is raised.
@ -883,6 +883,15 @@ are always available. They are listed here in alphabetical order.
closed. If a filename is given *closefd* has no effect and must be ``True`` closed. If a filename is given *closefd* has no effect and must be ``True``
(the default). (the default).
A custom opener can be used by passing a callable as *opener*. The underlying
file descriptor for the file object is then obtained by calling *opener* with
(*file*, *flags*). *opener* must return an open file descriptor (passing
:mod:`os.open` as *opener* results in functionality similar to passing
``None``).
.. versionchanged:: 3.3
The *opener* parameter was added.
The type of file object returned by the :func:`open` function depends on the The type of file object returned by the :func:`open` function depends on the
mode. When :func:`open` is used to open a file in a text mode (``'w'``, mode. When :func:`open` is used to open a file in a text mode (``'w'``,
``'r'``, ``'wt'``, ``'rt'``, etc.), it returns a subclass of ``'r'``, ``'wt'``, ``'rt'``, etc.), it returns a subclass of

View File

@ -458,7 +458,7 @@ I/O Base Classes
Raw File I/O Raw File I/O
^^^^^^^^^^^^ ^^^^^^^^^^^^
.. class:: FileIO(name, mode='r', closefd=True) .. class:: FileIO(name, mode='r', closefd=True, opener=None)
:class:`FileIO` represents an OS-level file containing bytes data. :class:`FileIO` represents an OS-level file containing bytes data.
It implements the :class:`RawIOBase` interface (and therefore the It implements the :class:`RawIOBase` interface (and therefore the
@ -479,6 +479,15 @@ Raw File I/O
The :meth:`read` (when called with a positive argument), :meth:`readinto` The :meth:`read` (when called with a positive argument), :meth:`readinto`
and :meth:`write` methods on this class will only make one system call. and :meth:`write` methods on this class will only make one system call.
A custom opener can be used by passing a callable as *opener*. The underlying
file descriptor for the file object is then obtained by calling *opener* with
(*name*, *flags*). *opener* must return an open file descriptor (passing
:mod:`os.open` as *opener* results in functionality similar to passing
``None``).
.. versionchanged:: 3.3
The *opener* parameter was added.
In addition to the attributes and methods from :class:`IOBase` and In addition to the attributes and methods from :class:`IOBase` and
:class:`RawIOBase`, :class:`FileIO` provides the following data :class:`RawIOBase`, :class:`FileIO` provides the following data
attributes and methods: attributes and methods:

View File

@ -27,7 +27,7 @@ BlockingIOError = BlockingIOError
def open(file, mode="r", buffering=-1, encoding=None, errors=None, def open(file, mode="r", buffering=-1, encoding=None, errors=None,
newline=None, closefd=True): newline=None, closefd=True, opener=None):
r"""Open file and return a stream. Raise IOError upon failure. r"""Open file and return a stream. Raise IOError upon failure.
@ -122,6 +122,12 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
be kept open when the file is closed. This does not work when a file name is be kept open when the file is closed. This does not work when a file name is
given and must be True in that case. given and must be True in that case.
A custom opener can be used by passing a callable as *opener*. The
underlying file descriptor for the file object is then obtained by calling
*opener* with (*file*, *flags*). *opener* must return an open file
descriptor (passing os.open as *opener* results in functionality similar to
passing None).
open() returns a file object whose type depends on the mode, and open() returns a file object whose type depends on the mode, and
through which the standard file operations such as reading and writing through which the standard file operations such as reading and writing
are performed. When open() is used to open a file in a text mode ('w', are performed. When open() is used to open a file in a text mode ('w',
@ -176,7 +182,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
(writing and "w" or "") + (writing and "w" or "") +
(appending and "a" or "") + (appending and "a" or "") +
(updating and "+" or ""), (updating and "+" or ""),
closefd) closefd, opener=opener)
line_buffering = False line_buffering = False
if buffering == 1 or buffering < 0 and raw.isatty(): if buffering == 1 or buffering < 0 and raw.isatty():
buffering = -1 buffering = -1

View File

@ -621,6 +621,15 @@ class IOTest(unittest.TestCase):
for obj in test: for obj in test:
self.assertTrue(hasattr(obj, "__dict__")) self.assertTrue(hasattr(obj, "__dict__"))
def test_opener(self):
with self.open(support.TESTFN, "w") as f:
f.write("egg\n")
fd = os.open(support.TESTFN, os.O_RDONLY)
def opener(path, flags):
return fd
with self.open("non-existent", "r", opener=opener) as f:
self.assertEqual(f.read(), "egg\n")
class CIOTest(IOTest): class CIOTest(IOTest):
def test_IOBase_finalize(self): def test_IOBase_finalize(self):

View File

@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #12797: Added custom opener parameter to builtin open() and
FileIO.open().
- Issue #10519: Avoid unnecessary recursive function calls in - Issue #10519: Avoid unnecessary recursive function calls in
setobject.c. setobject.c.

View File

@ -95,7 +95,7 @@ PyDoc_STRVAR(module_doc,
*/ */
PyDoc_STRVAR(open_doc, PyDoc_STRVAR(open_doc,
"open(file, mode='r', buffering=-1, encoding=None,\n" "open(file, mode='r', buffering=-1, encoding=None,\n"
" errors=None, newline=None, closefd=True) -> file object\n" " errors=None, newline=None, closefd=True, opener=None) -> file object\n"
"\n" "\n"
"Open file and return a stream. Raise IOError upon failure.\n" "Open file and return a stream. Raise IOError upon failure.\n"
"\n" "\n"
@ -190,6 +190,12 @@ PyDoc_STRVAR(open_doc,
"when the file is closed. This does not work when a file name is given\n" "when the file is closed. This does not work when a file name is given\n"
"and must be True in that case.\n" "and must be True in that case.\n"
"\n" "\n"
"A custom opener can be used by passing a callable as *opener*. The\n"
"underlying file descriptor for the file object is then obtained by\n"
"calling *opener* with (*file*, *flags*). *opener* must return an open\n"
"file descriptor (passing os.open as *opener* results in functionality\n"
"similar to passing None).\n"
"\n"
"open() returns a file object whose type depends on the mode, and\n" "open() returns a file object whose type depends on the mode, and\n"
"through which the standard file operations such as reading and writing\n" "through which the standard file operations such as reading and writing\n"
"are performed. When open() is used to open a file in a text mode ('w',\n" "are performed. When open() is used to open a file in a text mode ('w',\n"
@ -210,8 +216,8 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
{ {
char *kwlist[] = {"file", "mode", "buffering", char *kwlist[] = {"file", "mode", "buffering",
"encoding", "errors", "newline", "encoding", "errors", "newline",
"closefd", NULL}; "closefd", "opener", NULL};
PyObject *file; PyObject *file, *opener = Py_None;
char *mode = "r"; char *mode = "r";
int buffering = -1, closefd = 1; int buffering = -1, closefd = 1;
char *encoding = NULL, *errors = NULL, *newline = NULL; char *encoding = NULL, *errors = NULL, *newline = NULL;
@ -228,10 +234,10 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
_Py_IDENTIFIER(isatty); _Py_IDENTIFIER(isatty);
_Py_IDENTIFIER(fileno); _Py_IDENTIFIER(fileno);
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzzi:open", kwlist, if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzziO:open", kwlist,
&file, &mode, &buffering, &file, &mode, &buffering,
&encoding, &errors, &newline, &encoding, &errors, &newline,
&closefd)) { &closefd, &opener)) {
return NULL; return NULL;
} }
@ -331,7 +337,7 @@ io_open(PyObject *self, PyObject *args, PyObject *kwds)
/* Create the Raw file stream */ /* Create the Raw file stream */
raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type, raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type,
"Osi", file, rawmode, closefd); "OsiO", file, rawmode, closefd, opener);
if (raw == NULL) if (raw == NULL)
return NULL; return NULL;

View File

@ -212,9 +212,9 @@ static int
fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
{ {
fileio *self = (fileio *) oself; fileio *self = (fileio *) oself;
static char *kwlist[] = {"file", "mode", "closefd", NULL}; static char *kwlist[] = {"file", "mode", "closefd", "opener", NULL};
const char *name = NULL; const char *name = NULL;
PyObject *nameobj, *stringobj = NULL; PyObject *nameobj, *stringobj = NULL, *opener = Py_None;
char *mode = "r"; char *mode = "r";
char *s; char *s;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
@ -233,8 +233,9 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
return -1; return -1;
} }
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|si:fileio", if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|siO:fileio",
kwlist, &nameobj, &mode, &closefd)) kwlist, &nameobj, &mode, &closefd,
&opener))
return -1; return -1;
if (PyFloat_Check(nameobj)) { if (PyFloat_Check(nameobj)) {
@ -363,15 +364,35 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
goto error; goto error;
} }
Py_BEGIN_ALLOW_THREADS
errno = 0; errno = 0;
if (opener == Py_None) {
Py_BEGIN_ALLOW_THREADS
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (widename != NULL) if (widename != NULL)
self->fd = _wopen(widename, flags, 0666); self->fd = _wopen(widename, flags, 0666);
else else
#endif #endif
self->fd = open(name, flags, 0666); self->fd = open(name, flags, 0666);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} else {
PyObject *fdobj = PyObject_CallFunction(
opener, "Oi", nameobj, flags);
if (fdobj == NULL)
goto error;
if (!PyLong_Check(fdobj)) {
Py_DECREF(fdobj);
PyErr_SetString(PyExc_TypeError,
"expected integer from opener");
goto error;
}
self->fd = PyLong_AsLong(fdobj);
Py_DECREF(fdobj);
if (self->fd == -1) {
goto error;
}
}
if (self->fd < 0) { if (self->fd < 0) {
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (widename != NULL) if (widename != NULL)
@ -1017,13 +1038,17 @@ fileio_getstate(fileio *self)
PyDoc_STRVAR(fileio_doc, PyDoc_STRVAR(fileio_doc,
"file(name: str[, mode: str]) -> file IO object\n" "file(name: str[, mode: str][, opener: None]) -> file IO object\n"
"\n" "\n"
"Open a file. The mode can be 'r', 'w' or 'a' for reading (default),\n" "Open a file. The mode can be 'r', 'w' or 'a' for reading (default),\n"
"writing or appending. The file will be created if it doesn't exist\n" "writing or appending. The file will be created if it doesn't exist\n"
"when opened for writing or appending; it will be truncated when\n" "when opened for writing or appending; it will be truncated when\n"
"opened for writing. Add a '+' to the mode to allow simultaneous\n" "opened for writing. Add a '+' to the mode to allow simultaneous\n"
"reading and writing."); "reading and writing. A custom opener can be used by passing a\n"
"callable as *opener*. The underlying file descriptor for the file\n"
"object is then obtained by calling opener with (*name*, *flags*).\n"
"*opener* must return an open file descriptor (passing os.open as\n"
"*opener* results in functionality similar to passing None).");
PyDoc_STRVAR(read_doc, PyDoc_STRVAR(read_doc,
"read(size: int) -> bytes. read at most size bytes, returned as bytes.\n" "read(size: int) -> bytes. read at most size bytes, returned as bytes.\n"