Fix bpo-30526: Add TextIOWrapper.reconfigure() and a TextIOWrapper.wr… · pythoncapi/cpython@3c2817b · GitHub
Skip to content

Commit 3c2817b

Browse files
authored
Fix bpo-30526: Add TextIOWrapper.reconfigure() and a TextIOWrapper.write_through attribute (python#1922)
* Fix bpo-30526: Add TextIOWrapper.reconfigure() * Apply Nick's improved wording * Update Misc/NEWS
1 parent ae8750b commit 3c2817b

6 files changed

Lines changed: 190 additions & 2 deletions

File tree

Doc/library/io.rst

Lines changed: 18 additions & 0 deletions

Lib/_pyio.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1943,7 +1943,6 @@ def __init__(self, buffer, encoding=None, errors=None, newline=None,
19431943
raise ValueError("invalid errors: %r" % errors)
19441944

19451945
self._buffer = buffer
1946-
self._line_buffering = line_buffering
19471946
self._encoding = encoding
19481947
self._errors = errors
19491948
self._readuniversal = not newline
@@ -1969,6 +1968,12 @@ def __init__(self, buffer, encoding=None, errors=None, newline=None,
19691968
# Sometimes the encoder doesn't exist
19701969
pass
19711970

1971+
self._configure(line_buffering, write_through)
1972+
1973+
def _configure(self, line_buffering=False, write_through=False):
1974+
self._line_buffering = line_buffering
1975+
self._write_through = write_through
1976+
19721977
# self._snapshot is either None, or a tuple (dec_flags, next_input)
19731978
# where dec_flags is the second (integer) item of the decoder state
19741979
# and next_input is the chunk of input bytes that comes next after the
@@ -2007,10 +2012,26 @@ def errors(self):
20072012
def line_buffering(self):
20082013
return self._line_buffering
20092014

2015+
@property
2016+
def write_through(self):
2017+
return self._write_through
2018+
20102019
@property
20112020
def buffer(self):
20122021
return self._buffer
20132022

2023+
def reconfigure(self, *, line_buffering=None, write_through=None):
2024+
"""Reconfigure the text stream with new parameters.
2025+
2026+
This also flushes the stream.
2027+
"""
2028+
if line_buffering is None:
2029+
line_buffering = self.line_buffering
2030+
if write_through is None:
2031+
write_through = self.write_through
2032+
self.flush()
2033+
self._configure(line_buffering, write_through)
2034+
20142035
def seekable(self):
20152036
if self.closed:
20162037
raise ValueError("I/O operation on closed file.")

Lib/test/test_io.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,6 +2440,7 @@ def test_detach(self):
24402440
self.assertEqual(t.encoding, "ascii")
24412441
self.assertEqual(t.errors, "strict")
24422442
self.assertFalse(t.line_buffering)
2443+
self.assertFalse(t.write_through)
24432444

24442445
def test_repr(self):
24452446
raw = self.BytesIO("hello".encode("utf-8"))
@@ -2482,6 +2483,33 @@ def test_line_buffering(self):
24822483
t.write("A\rB")
24832484
self.assertEqual(r.getvalue(), b"XY\nZA\rB")
24842485

2486+
def test_reconfigure_line_buffering(self):
2487+
r = self.BytesIO()
2488+
b = self.BufferedWriter(r, 1000)
2489+
t = self.TextIOWrapper(b, newline="\n", line_buffering=False)
2490+
t.write("AB\nC")
2491+
self.assertEqual(r.getvalue(), b"")
2492+
2493+
t.reconfigure(line_buffering=True) # implicit flush
2494+
self.assertEqual(r.getvalue(), b"AB\nC")
2495+
t.write("DEF\nG")
2496+
self.assertEqual(r.getvalue(), b"AB\nCDEF\nG")
2497+
t.write("H")
2498+
self.assertEqual(r.getvalue(), b"AB\nCDEF\nG")
2499+
t.reconfigure(line_buffering=False) # implicit flush
2500+
self.assertEqual(r.getvalue(), b"AB\nCDEF\nGH")
2501+
t.write("IJ")
2502+
self.assertEqual(r.getvalue(), b"AB\nCDEF\nGH")
2503+
2504+
# Keeping default value
2505+
t.reconfigure()
2506+
t.reconfigure(line_buffering=None)
2507+
self.assertEqual(t.line_buffering, False)
2508+
t.reconfigure(line_buffering=True)
2509+
t.reconfigure()
2510+
t.reconfigure(line_buffering=None)
2511+
self.assertEqual(t.line_buffering, True)
2512+
24852513
def test_default_encoding(self):
24862514
old_environ = dict(os.environ)
24872515
try:
@@ -3164,6 +3192,29 @@ def write(self, *args, **kwargs):
31643192
self.assertTrue(write_called)
31653193
self.assertEqual(rawio.getvalue(), data * 11) # all flushed
31663194

3195+
def test_reconfigure_write_through(self):
3196+
raw = self.MockRawIO([])
3197+
t = self.TextIOWrapper(raw, encoding='ascii', newline='\n')
3198+
t.write('1')
3199+
t.reconfigure(write_through=True) # implied flush
3200+
self.assertEqual(t.write_through, True)
3201+
self.assertEqual(b''.join(raw._write_stack), b'1')
3202+
t.write('23')
3203+
self.assertEqual(b''.join(raw._write_stack), b'123')
3204+
t.reconfigure(write_through=False)
3205+
self.assertEqual(t.write_through, False)
3206+
t.write('45')
3207+
t.flush()
3208+
self.assertEqual(b''.join(raw._write_stack), b'12345')
3209+
# Keeping default value
3210+
t.reconfigure()
3211+
t.reconfigure(write_through=None)
3212+
self.assertEqual(t.write_through, False)
3213+
t.reconfigure(write_through=True)
3214+
t.reconfigure()
3215+
t.reconfigure(write_through=None)
3216+
self.assertEqual(t.write_through, True)
3217+
31673218
def test_read_nonbytes(self):
31683219
# Issue #17106
31693220
# Crash when underlying read() returns non-bytes

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ Extension Modules
345345
Library
346346
-------
347347

348+
- bpo-30526: Add TextIOWrapper.reconfigure() and a TextIOWrapper.write_through
349+
attribute.
350+
348351
- bpo-30245: Fix possible overflow when organize struct.pack_into
349352
error message. Patch by Yuan Liu.
350353

Modules/_io/clinic/textio.c.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,41 @@ _io_TextIOWrapper___init__(PyObject *self, PyObject *args, PyObject *kwargs)
176176
return return_value;
177177
}
178178

179+
PyDoc_STRVAR(_io_TextIOWrapper_reconfigure__doc__,
180+
"reconfigure($self, /, *, line_buffering=None, write_through=None)\n"
181+
"--\n"
182+
"\n"
183+
"Reconfigure the text stream with new parameters.\n"
184+
"\n"
185+
"This also does an implicit stream flush.");
186+
187+
#define _IO_TEXTIOWRAPPER_RECONFIGURE_METHODDEF \
188+
{"reconfigure", (PyCFunction)_io_TextIOWrapper_reconfigure, METH_FASTCALL, _io_TextIOWrapper_reconfigure__doc__},
189+
190+
static PyObject *
191+
_io_TextIOWrapper_reconfigure_impl(textio *self,
192+
PyObject *line_buffering_obj,
193+
PyObject *write_through_obj);
194+
195+
static PyObject *
196+
_io_TextIOWrapper_reconfigure(textio *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
197+
{
198+
PyObject *return_value = NULL;
199+
static const char * const _keywords[] = {"line_buffering", "write_through", NULL};
200+
static _PyArg_Parser _parser = {"|$OO:reconfigure", _keywords, 0};
201+
PyObject *line_buffering_obj = Py_None;
202+
PyObject *write_through_obj = Py_None;
203+
204+
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
205+
&line_buffering_obj, &write_through_obj)) {
206+
goto exit;
207+
}
208+
return_value = _io_TextIOWrapper_reconfigure_impl(self, line_buffering_obj, write_through_obj);
209+
210+
exit:
211+
return return_value;
212+
}
213+
179214
PyDoc_STRVAR(_io_TextIOWrapper_detach__doc__,
180215
"detach($self, /)\n"
181216
"--\n"
@@ -480,4 +515,4 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored))
480515
{
481516
return _io_TextIOWrapper_close_impl(self);
482517
}
483-
/*[clinic end generated code: output=8e5c21c88c7c70bc input=a9049054013a1b77]*/
518+
/*[clinic end generated code: output=7d0dc8eae4b725a1 input=a9049054013a1b77]*/

Modules/_io/textio.c

Lines changed: 60 additions & 0 deletions

0 commit comments

Comments
 (0)