https://github.com/python/cpython/commit/3422197f5481ac1b3134e2633332415a02959ba4
commit: 3422197f5481ac1b3134e2633332415a02959ba4
branch: main
author: Cody Maloney <[email protected]>
committer: cmaloney <[email protected]>
date: 2026-11-05T13:40:33-08:00
summary:

gh-160427: Return None from TextIOWrapper.buffer when detached (#160802)

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.

files:
A Misc/NEWS.d/next/Library/2026-10-26-16-15-29.gh-issue-160427.9G4EBb.rst
M Lib/test/test_io/test_textio.py
M Modules/_io/textio.c

diff --git a/Lib/test/test_io/test_textio.py b/Lib/test/test_io/test_textio.py
index a18386d68569ae9..6e649240f08d5bf 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 000000000000000..d57dd02a6d576f5
--- /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 4ef5251d451a4cd..b6f3f4f17119289 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[] = {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to