From 58f2c106200b87a31b48f2a517d72309b26a4bf7 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Tue, 27 Oct 2026 21:26:06 -0700 Subject: [PATCH] gh-160427: Return None from TextIOWrapper.buffer when detached gh-154523 turned the buffer member descriptor into a getter that raises ValueError when the buffer is NULL, and the backport shipped in 3.14.9 and 3.15.1. That made hasattr(stream, "buffer") raise after detach(). Restore the old member descriptor result (None) while keeping the critical section. --- Lib/test/test_io/test_textio.py | 6 +++--- .../Library/2026-10-26-16-15-29.gh-issue-160427.9G4EBb.rst | 2 ++ Modules/_io/textio.c | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-26-16-15-29.gh-issue-160427.9G4EBb.rst diff --git a/Lib/test/test_io/test_textio.py b/Lib/test/test_io/test_textio.py index a18386d68569ae..6e649240f08d5b 100644 --- a/Lib/test/test_io/test_textio.py +++ b/Lib/test/test_io/test_textio.py @@ -244,6 +244,7 @@ def test_detach(self): r = self.BytesIO() b = self.BufferedWriter(r) t = self.TextIOWrapper(b, encoding="ascii") + self.assertIs(t.buffer, b) self.assertIs(t.detach(), b) t = self.TextIOWrapper(b, encoding="ascii") @@ -259,6 +260,7 @@ def test_detach(self): self.assertEqual(t.errors, "strict") self.assertFalse(t.line_buffering) self.assertFalse(t.write_through) + self.assertIsNone(t.buffer) def test_repr(self): raw = self.BytesIO("hello".encode("utf-8")) @@ -1721,9 +1723,7 @@ def readinto(self, b): self.BufferedReader(raw), encoding="utf-8") method = getattr(wrapper, method_name) self.assertEqual(method(), "ab\n") - with self.assertRaisesRegex(ValueError, - "underlying buffer has been detached"): - getattr(wrapper, 'buffer') + self.assertIsNone(wrapper.buffer) def test_reentrant_seek_during_tell(self): # gh-153539: reading short of _CHUNK_SIZE leaves residual bytes in the diff --git a/Misc/NEWS.d/next/Library/2026-10-26-16-15-29.gh-issue-160427.9G4EBb.rst b/Misc/NEWS.d/next/Library/2026-10-26-16-15-29.gh-issue-160427.9G4EBb.rst new file mode 100644 index 00000000000000..d57dd02a6d576f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-26-16-15-29.gh-issue-160427.9G4EBb.rst @@ -0,0 +1,2 @@ +Fix :attr:`~io.TextIOBase.buffer` of a detached :class:`io.TextIOWrapper` +raising :exc:`ValueError` instead of returning ``None``. diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 4ef5251d451a4c..b6f3f4f1711928 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3474,7 +3474,11 @@ static PyObject * _io_TextIOWrapper_buffer_get_impl(textio *self) /*[clinic end generated code: output=d265a34555aa5d4b input=5951cfa148f7350a]*/ { - return buffer_access_safe(self); + /* gh-160427: None once detached, matching the former member descriptor. */ + if (self->buffer == NULL) { + Py_RETURN_NONE; + } + return Py_NewRef(self->buffer); } static PyMethodDef incrementalnewlinedecoder_methods[] = {