Skip to content

gh-160427: Return None from TextIOWrapper.buffer once detached - #160467

Draft
wlkr73 wants to merge 1 commit into
python:mainfrom
wlkr73:gh-160427-buffer-none
Draft

wlkr73 wants to merge 1 commit into
python:mainfrom
wlkr73:gh-160427-buffer-none

Conversation

@wlkr73

@wlkr73 wlkr73 commented Oct 23, 2026

Copy link
Copy Markdown

Fixes gh-160427.

TextIOWrapper.buffer raises ValueError: underlying buffer has been detached
once the wrapper is detached, so hasattr(stream, "buffer") raises instead of
returning a boolean. This is a regression in 3.14.9 and 3.15.1, introduced by
gh-157364.

That change replaced the buffer member descriptor with a getter that calls
buffer_access_safe(), which raises whenever self->buffer is NULL. Before
gh-157364 the attribute was exposed as
{"buffer", _Py_T_OBJECT, offsetof(textio, buffer), Py_READONLY}, and a NULL
object member reads as None. _pyio.TextIOWrapper.buffer still returns
self._buffer, which is None after detach, so the two implementations now
disagree.

The fix returns None from the getter when the wrapper is detached but
initialised. buffer_access_safe() is untouched, so every other caller keeps
the existing protection, and the uninitialised case still raises.

One existing test needed updating: test_reentrant_detach_during_read asserted
the ValueError from wrapper.buffer. Its subject is that the buffer survives
the read and the wrapper does not crash, which still holds, so it now asserts
wrapper.buffer is None.

Tests: ./python -m test test_io passes, 1,066 tests. test_detach now also
asserts the attribute is None and that hasattr works after detach.

I used an AI assistant to help trace the regression and prepare the patch, and
I have reviewed every line of it.

@python-cla-bot

python-cla-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

pythongh-157364 replaced the buffer member descriptor with a getter that goes
through buffer_access_safe(), which raises ValueError when the buffer is NULL.
The attribute therefore raised instead of returning None after detach(),
breaking hasattr() in 3.14.9 and 3.15.1 for code that probes a stream for a
buffer.

Return None from the getter when the wrapper is detached but initialised, which
is what the member descriptor did before pythongh-157364 and what _pyio returns. The
safety helper keeps raising for every other caller.
@wlkr73
wlkr73 force-pushed the gh-160427-buffer-none branch from 7551b73 to 0071853 Compare October 23, 2026 19:14
@wlkr73

wlkr73 commented Oct 23, 2026

Copy link
Copy Markdown
Author

@ashm-dev, one thing worth a look from you: I changed an assertion in test_reentrant_detach_during_read, which came from #157370. It asserted the ValueError from wrapper.buffer; the test's subject is that the buffer survives the read and the wrapper does not crash, which still passes, so it now asserts wrapper.buffer is None.

Before gh-157364 the attribute was a member descriptor, so a detached wrapper read as None, and _pyio still returns None. If you would rather keep that test untouched, I can restore the old assertion and move the None expectation somewhere else, but the two behaviours cannot both hold.

@cmaloney

Copy link
Copy Markdown
Contributor

Need to work out what the right behavior here is before getting to a PR. Ideally get an agreement in the issue first, then once have that can work on a PR. In this case that means propose the change you'd like to make there and wait for a python triager or developer to figure out if that is the right direction.

@wlkr73

wlkr73 commented Oct 23, 2026

Copy link
Copy Markdown
Author

Fair. I will take the behaviour question to gh-160427 and hold this as a draft until a triager or developer settles it.

@wlkr73

wlkr73 commented Oct 23, 2026

Copy link
Copy Markdown
Author

Noted on gh-160472 being closed as a duplicate, and the parity point: when the behaviour question on gh-160427 is settled, the _pyio half will go into this same PR so _io and _pyio land together rather than as two changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

`hasattr(stream, "buffer") fails with ValueError if buffer is detached

2 participants