Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/library/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,16 @@ Text I/O

Whether line buffering is enabled.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should also say that writing to it flushes the stream


.. 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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


.. versionadded:: 3.7


.. class:: StringIO(initial_value='', newline='\\n')

Expand Down
19 changes: 19 additions & 0 deletions Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down
61 changes: 60 additions & 1 deletion Modules/_io/textio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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}
};
Expand All @@ -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}
Expand Down