]> granicus.if.org Git - python/commitdiff
Fix bpo-30526: Add TextIOWrapper.reconfigure() and a TextIOWrapper.write_through...
authorAntoine Pitrou <pitrou@free.fr>
Sat, 3 Jun 2017 10:32:28 +0000 (12:32 +0200)
committerGitHub <noreply@github.com>
Sat, 3 Jun 2017 10:32:28 +0000 (12:32 +0200)
* Fix bpo-30526: Add TextIOWrapper.reconfigure()

* Apply Nick's improved wording

* Update Misc/NEWS

Doc/library/io.rst
Lib/_pyio.py
Lib/test/test_io.py
Misc/NEWS
Modules/_io/clinic/textio.c.h
Modules/_io/textio.c

index c8ff5b826d8b3f10df0ea652250923d4c5288c79..8a695ad99dd9eb725f4c6db071a5569e42164fa0 100644 (file)
@@ -908,6 +908,24 @@ Text I/O
 
       Whether line buffering is enabled.
 
+   .. attribute:: write_through
+
+      Whether writes are passed immediately to the underlying binary
+      buffer.
+
+      .. versionadded:: 3.7
+
+   .. method:: reconfigure(*, line_buffering=None, write_through=None)
+
+      Reconfigure this text stream using new settings for *line_buffering*
+      and *write_through*.  Passing ``None`` as an argument will retain
+      the current setting for that parameter.
+
+      This method does an implicit stream flush before setting the
+      new parameters.
+
+      .. versionadded:: 3.7
+
 
 .. class:: StringIO(initial_value='', newline='\\n')
 
index 5dfc1f0308d97b0b9b233ccf8c0827a14ded9115..d50230dbfd8087420456e0bc33551cf63fba1306 100644 (file)
@@ -1943,7 +1943,6 @@ class TextIOWrapper(TextIOBase):
                 raise ValueError("invalid errors: %r" % errors)
 
         self._buffer = buffer
-        self._line_buffering = line_buffering
         self._encoding = encoding
         self._errors = errors
         self._readuniversal = not newline
@@ -1969,6 +1968,12 @@ class TextIOWrapper(TextIOBase):
                     # Sometimes the encoder doesn't exist
                     pass
 
+        self._configure(line_buffering, write_through)
+
+    def _configure(self, line_buffering=False, write_through=False):
+        self._line_buffering = line_buffering
+        self._write_through = write_through
+
     # self._snapshot is either None, or a tuple (dec_flags, next_input)
     # where dec_flags is the second (integer) item of the decoder state
     # and next_input is the chunk of input bytes that comes next after the
@@ -2007,10 +2012,26 @@ class TextIOWrapper(TextIOBase):
     def line_buffering(self):
         return self._line_buffering
 
+    @property
+    def write_through(self):
+        return self._write_through
+
     @property
     def buffer(self):
         return self._buffer
 
+    def reconfigure(self, *, line_buffering=None, write_through=None):
+        """Reconfigure the text stream with new parameters.
+
+        This also flushes the stream.
+        """
+        if line_buffering is None:
+            line_buffering = self.line_buffering
+        if write_through is None:
+            write_through = self.write_through
+        self.flush()
+        self._configure(line_buffering, write_through)
+
     def seekable(self):
         if self.closed:
             raise ValueError("I/O operation on closed file.")
index 69487a1b50dc3b85c49c0da6cf03a1194a9b603f..83d6c4e57ecbeed29b3873a1e28d0dfcb07cdce6 100644 (file)
@@ -2440,6 +2440,7 @@ class TextIOWrapperTest(unittest.TestCase):
         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,33 @@ class TextIOWrapperTest(unittest.TestCase):
         t.write("A\rB")
         self.assertEqual(r.getvalue(), b"XY\nZA\rB")
 
+    def test_reconfigure_line_buffering(self):
+        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.reconfigure(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.reconfigure(line_buffering=False)   # implicit flush
+        self.assertEqual(r.getvalue(), b"AB\nCDEF\nGH")
+        t.write("IJ")
+        self.assertEqual(r.getvalue(), b"AB\nCDEF\nGH")
+
+        # Keeping default value
+        t.reconfigure()
+        t.reconfigure(line_buffering=None)
+        self.assertEqual(t.line_buffering, False)
+        t.reconfigure(line_buffering=True)
+        t.reconfigure()
+        t.reconfigure(line_buffering=None)
+        self.assertEqual(t.line_buffering, True)
+
     def test_default_encoding(self):
         old_environ = dict(os.environ)
         try:
@@ -3164,6 +3192,29 @@ class TextIOWrapperTest(unittest.TestCase):
         self.assertTrue(write_called)
         self.assertEqual(rawio.getvalue(), data * 11) # all flushed
 
+    def test_reconfigure_write_through(self):
+        raw = self.MockRawIO([])
+        t = self.TextIOWrapper(raw, encoding='ascii', newline='\n')
+        t.write('1')
+        t.reconfigure(write_through=True)  # implied flush
+        self.assertEqual(t.write_through, True)
+        self.assertEqual(b''.join(raw._write_stack), b'1')
+        t.write('23')
+        self.assertEqual(b''.join(raw._write_stack), b'123')
+        t.reconfigure(write_through=False)
+        self.assertEqual(t.write_through, False)
+        t.write('45')
+        t.flush()
+        self.assertEqual(b''.join(raw._write_stack), b'12345')
+        # Keeping default value
+        t.reconfigure()
+        t.reconfigure(write_through=None)
+        self.assertEqual(t.write_through, False)
+        t.reconfigure(write_through=True)
+        t.reconfigure()
+        t.reconfigure(write_through=None)
+        self.assertEqual(t.write_through, True)
+
     def test_read_nonbytes(self):
         # Issue #17106
         # Crash when underlying read() returns non-bytes
index 902b102ac72d5ca9123a6a2dd9f6bef78e73a184..e6e8f95f4653bad045b9d04e0a9759af01b5111d 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -345,6 +345,9 @@ Extension Modules
 Library
 -------
 
+- bpo-30526: Add TextIOWrapper.reconfigure() and a TextIOWrapper.write_through
+  attribute.
+
 - bpo-30245: Fix possible overflow when organize struct.pack_into
   error message.  Patch by Yuan Liu.
   
index 4eab136f772ab296984f50d74b2d626037384cba..abb80ea3a2ab3305c2098700a314d16369279f88 100644 (file)
@@ -176,6 +176,41 @@ exit:
     return return_value;
 }
 
+PyDoc_STRVAR(_io_TextIOWrapper_reconfigure__doc__,
+"reconfigure($self, /, *, line_buffering=None, write_through=None)\n"
+"--\n"
+"\n"
+"Reconfigure the text stream with new parameters.\n"
+"\n"
+"This also does an implicit stream flush.");
+
+#define _IO_TEXTIOWRAPPER_RECONFIGURE_METHODDEF    \
+    {"reconfigure", (PyCFunction)_io_TextIOWrapper_reconfigure, METH_FASTCALL, _io_TextIOWrapper_reconfigure__doc__},
+
+static PyObject *
+_io_TextIOWrapper_reconfigure_impl(textio *self,
+                                   PyObject *line_buffering_obj,
+                                   PyObject *write_through_obj);
+
+static PyObject *
+_io_TextIOWrapper_reconfigure(textio *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    static const char * const _keywords[] = {"line_buffering", "write_through", NULL};
+    static _PyArg_Parser _parser = {"|$OO:reconfigure", _keywords, 0};
+    PyObject *line_buffering_obj = Py_None;
+    PyObject *write_through_obj = Py_None;
+
+    if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
+        &line_buffering_obj, &write_through_obj)) {
+        goto exit;
+    }
+    return_value = _io_TextIOWrapper_reconfigure_impl(self, line_buffering_obj, write_through_obj);
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(_io_TextIOWrapper_detach__doc__,
 "detach($self, /)\n"
 "--\n"
@@ -480,4 +515,4 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored))
 {
     return _io_TextIOWrapper_close_impl(self);
 }
-/*[clinic end generated code: output=8e5c21c88c7c70bc input=a9049054013a1b77]*/
+/*[clinic end generated code: output=7d0dc8eae4b725a1 input=a9049054013a1b77]*/
index 2c799e331581b85d1169ed97ddc119dd72c9999c..b5d368a175c94cc0a4bd5dcabbc696e3dfec52d3 100644 (file)
@@ -1095,6 +1095,64 @@ _io_TextIOWrapper___init___impl(textio *self, PyObject *buffer,
     return -1;
 }
 
+/* Return *default_value* if ob is None, 0 if ob is false, 1 if ob is true,
+ * -1 on error.
+ */
+static int
+convert_optional_bool(PyObject *obj, int default_value)
+{
+    long v;
+    if (obj == Py_None) {
+        v = default_value;
+    }
+    else {
+        v = PyLong_AsLong(obj);
+        if (v == -1 && PyErr_Occurred())
+            return -1;
+    }
+    return v != 0;
+}
+
+
+/*[clinic input]
+_io.TextIOWrapper.reconfigure
+    *
+    line_buffering as line_buffering_obj: object = None
+    write_through as write_through_obj: object = None
+
+Reconfigure the text stream with new parameters.
+
+This also does an implicit stream flush.
+
+[clinic start generated code]*/
+
+static PyObject *
+_io_TextIOWrapper_reconfigure_impl(textio *self,
+                                   PyObject *line_buffering_obj,
+                                   PyObject *write_through_obj)
+/*[clinic end generated code: output=7cdf79e7001e2856 input=baade27ecb9db7bc]*/
+{
+    int line_buffering;
+    int write_through;
+    PyObject *res;
+
+    line_buffering = convert_optional_bool(line_buffering_obj,
+                                           self->line_buffering);
+    write_through = convert_optional_bool(write_through_obj,
+                                          self->write_through);
+    if (line_buffering < 0 || write_through < 0) {
+        return NULL;
+    }
+    res = PyObject_CallMethodObjArgs((PyObject *) self, _PyIO_str_flush, NULL);
+    Py_XDECREF(res);
+    if (res == NULL) {
+        return NULL;
+    }
+    self->line_buffering = line_buffering;
+    self->write_through = write_through;
+    Py_RETURN_NONE;
+}
+
 static int
 textiowrapper_clear(textio *self)
 {
@@ -2839,6 +2897,7 @@ PyTypeObject PyIncrementalNewlineDecoder_Type = {
 
 static PyMethodDef textiowrapper_methods[] = {
     _IO_TEXTIOWRAPPER_DETACH_METHODDEF
+    _IO_TEXTIOWRAPPER_RECONFIGURE_METHODDEF
     _IO_TEXTIOWRAPPER_WRITE_METHODDEF
     _IO_TEXTIOWRAPPER_READ_METHODDEF
     _IO_TEXTIOWRAPPER_READLINE_METHODDEF
@@ -2862,6 +2921,7 @@ 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},
+    {"write_through", T_BOOL, offsetof(textio, write_through), READONLY},
     {"_finalizing", T_BOOL, offsetof(textio, finalizing), 0},
     {NULL}
 };