From 2fbc916ea1c0e9932bebbe525afdae507905eaec Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Wed, 31 May 2017 18:46:01 +0200 Subject: [PATCH] Fix #30526: make TextIOWrapper.{line_buffering, write_through} writable This simplifies the reconfiguration of standard streams as it can now be done in-place. --- Doc/library/io.rst | 10 ++++++++ Lib/_pyio.py | 19 ++++++++++++++ Lib/test/test_io.py | 30 ++++++++++++++++++++++ Modules/_io/textio.c | 61 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/Doc/library/io.rst b/Doc/library/io.rst index c8ff5b826d8b3f..f688e5c85d60bb 100644 --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -908,6 +908,16 @@ Text I/O Whether line buffering is enabled. + .. versionchanged:: 3.7 + This argument is now writable. + + .. attribute:: write_through + + Whether writes are passed immediately to the underlying binary + buffer. This argument is writable. + + .. versionadded:: 3.7 + .. class:: StringIO(initial_value='', newline='\\n') diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 5dfc1f0308d97b..29b94a418e224a 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1944,6 +1944,7 @@ def __init__(self, buffer, encoding=None, errors=None, newline=None, self._buffer = buffer self._line_buffering = line_buffering + self._write_through = write_through self._encoding = encoding self._errors = errors self._readuniversal = not newline @@ -2007,6 +2008,24 @@ def errors(self): def line_buffering(self): return self._line_buffering + @line_buffering.setter + def line_buffering(self, value): + if not isinstance(value, bool): + raise TypeError("attribute value type must be bool") + self.flush() + self._line_buffering = value + + @property + def write_through(self): + return self._write_through + + @write_through.setter + def write_through(self, value): + if not isinstance(value, bool): + raise TypeError("attribute value type must be bool") + self.flush() + self._write_through = value + @property def buffer(self): return self._buffer diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 69487a1b50dc3b..b7b7644b5ca60e 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2440,6 +2440,7 @@ def test_detach(self): self.assertEqual(t.encoding, "ascii") self.assertEqual(t.errors, "strict") self.assertFalse(t.line_buffering) + self.assertFalse(t.write_through) def test_repr(self): raw = self.BytesIO("hello".encode("utf-8")) @@ -2482,6 +2483,23 @@ def test_line_buffering(self): t.write("A\rB") self.assertEqual(r.getvalue(), b"XY\nZA\rB") + # The attribute is settable + r = self.BytesIO() + b = self.BufferedWriter(r, 1000) + t = self.TextIOWrapper(b, newline="\n", line_buffering=False) + t.write("AB\nC") + self.assertEqual(r.getvalue(), b"") + t.line_buffering = True # implicit flush + self.assertEqual(r.getvalue(), b"AB\nC") + t.write("DEF\nG") + self.assertEqual(r.getvalue(), b"AB\nCDEF\nG") + t.write("H") + self.assertEqual(r.getvalue(), b"AB\nCDEF\nG") + t.line_buffering = False # implicit flush + self.assertEqual(r.getvalue(), b"AB\nCDEF\nGH") + t.write("IJ") + self.assertEqual(r.getvalue(), b"AB\nCDEF\nGH") + def test_default_encoding(self): old_environ = dict(os.environ) try: @@ -3131,6 +3149,18 @@ def test_rawio_write_through(self): txt.write('23\n4') txt.write('5') self.assertEqual(b''.join(raw._write_stack), b'123\n45') + # The attribute is settable + raw = self.MockRawIO([]) + txt = self.TextIOWrapper(raw, encoding='ascii', newline='\n') + txt.write('1') + txt.write_through = True # implied flush + self.assertEqual(b''.join(raw._write_stack), b'1') + txt.write('23') + self.assertEqual(b''.join(raw._write_stack), b'123') + txt.write_through = False + txt.write('45') + txt.flush() + self.assertEqual(b''.join(raw._write_stack), b'12345') def test_bufio_write_through(self): # Issue #21396: write_through=True doesn't force a flush() diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 2c799e331581b8..90635116e296e1 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -2756,6 +2756,62 @@ textiowrapper_errors_get(textio *self, void *context) return PyUnicode_FromString(PyBytes_AS_STRING(self->errors)); } +static PyObject * +textiowrapper_line_buffering_get(textio *self, void *context) +{ + CHECK_INITIALIZED(self); + return PyBool_FromLong(self->line_buffering); +} + +static int +textiowrapper_line_buffering_set(textio *self, PyObject *arg, void *context) +{ + PyObject *ret; + CHECK_ATTACHED_INT(self); + if (!PyBool_Check(arg)) { + PyErr_SetString(PyExc_TypeError, + "attribute value type must be bool"); + return -1; + } + self->line_buffering = (arg == Py_True); + ret = PyObject_CallMethodObjArgs((PyObject *) self, _PyIO_str_flush, NULL); + if (ret == NULL) { + return -1; + } + else { + Py_DECREF(ret); + return 0; + } +} + +static PyObject * +textiowrapper_write_through_get(textio *self, void *context) +{ + CHECK_INITIALIZED(self); + return PyBool_FromLong(self->write_through); +} + +static int +textiowrapper_write_through_set(textio *self, PyObject *arg, void *context) +{ + PyObject *ret; + CHECK_ATTACHED_INT(self); + if (!PyBool_Check(arg)) { + PyErr_SetString(PyExc_TypeError, + "attribute value type must be bool"); + return -1; + } + self->write_through = (arg == Py_True); + ret = PyObject_CallMethodObjArgs((PyObject *) self, _PyIO_str_flush, NULL); + if (ret == NULL) { + return -1; + } + else { + Py_DECREF(ret); + return 0; + } +} + static PyObject * textiowrapper_chunk_size_get(textio *self, void *context) { @@ -2861,7 +2917,6 @@ static PyMethodDef textiowrapper_methods[] = { static PyMemberDef textiowrapper_members[] = { {"encoding", T_OBJECT, offsetof(textio, encoding), READONLY}, {"buffer", T_OBJECT, offsetof(textio, buffer), READONLY}, - {"line_buffering", T_BOOL, offsetof(textio, line_buffering), READONLY}, {"_finalizing", T_BOOL, offsetof(textio, finalizing), 0}, {NULL} }; @@ -2873,6 +2928,10 @@ static PyGetSetDef textiowrapper_getset[] = { */ {"newlines", (getter)textiowrapper_newlines_get, NULL, NULL}, {"errors", (getter)textiowrapper_errors_get, NULL, NULL}, + {"line_buffering", (getter)textiowrapper_line_buffering_get, + (setter)textiowrapper_line_buffering_set, NULL}, + {"write_through", (getter)textiowrapper_write_through_get, + (setter)textiowrapper_write_through_set, NULL}, {"_CHUNK_SIZE", (getter)textiowrapper_chunk_size_get, (setter)textiowrapper_chunk_size_set, NULL}, {NULL}