Skip to content

gh-133492: Add readinto to typing.BinaryIO and widen its write signatures - #158136

Open
v0ropaev wants to merge 2 commits into
python:mainfrom
v0ropaev:gh-133492-binaryio-readinto
Open

v0ropaev wants to merge 2 commits into
python:mainfrom
v0ropaev:gh-133492-binaryio-readinto

Conversation

@v0ropaev

@v0ropaev v0ropaev commented Sep 24, 2026 •

Copy link
Copy Markdown

Closes #133492.

typing.BinaryIO has no readinto, though every binary stream open() returns supports it, and its write/writelines are narrower than what those streams accept.

@JelleZijlstra set a condition for the readinto half:

I'd be fine with doing this it can be shown that the method is present on all classes in typeshed that are currently marked as inheriting from BinaryIO.

Seven classes in typeshed are declared that way. FileIO, BytesIO, BufferedReader, BufferedWriter and BufferedRandom get readinto from _RawIOBase or _BufferedIOBase; http.client.HTTPResponse declares it itself. The seventh, codecs.StreamRecoder, does not define it — its __getattr__ forwards every unknown attribute to the wrapped stream:

def __getattr__(self, name, getattr=getattr):
    """ Inherit all other methods from the underlying stream.
    """
    return getattr(self.stream, name)

so a recoder wrapping a binary stream has it and it works:

>>> sr = codecs.StreamRecoder(io.BytesIO(b"abc"), enc, dec, Reader, Writer)
>>> buf = bytearray(3); sr.readinto(buf), bytes(buf)
(3, b'abc')

Worth noting that a class-level hasattr(codecs.StreamRecoder, "readinto") says False and is the wrong probe — it misses the forwarding, which only shows on an instance.

The other half you were fine with outright:

I wouldn't mind a PR updating the typing.py definitions to match those in typeshed better though.

So BinaryIO.write and BinaryIO.writelines now take collections.abc.Buffer instead of bytes | bytearray, and IO.writelines takes an iterable rather than a list. Binary streams accept all of that today; typeshed says the same through overloads a runtime stub cannot spell.

Nothing here affects type checkers, which read typeshed rather than these definitions — this is about the runtime stubs matching what they claim to describe.

./python.exe -m test test_typing is 744 tests passing; reverting Lib/typing.py while keeping the new test fails one. Two news entries, since the two halves stand on their own. Docs get a versionchanged:: next on BinaryIO. main only.

BinaryIO.write was annotated as taking only bytes or bytearray, but the
binary streams open() returns take any object with a buffer: BytesIO.write
accepts a memoryview or an array.array just fine. typeshed spells this as
ReadableBuffer on an IO[bytes] self-type overload, which a runtime stub
cannot express, so the closest it can get is collections.abc.Buffer.

writelines was off along two axes. IO.writelines said list[AnyStr] where the
real method takes any iterable, and where typeshed says Iterable[AnyStr]. On
the binary side it then stayed at bytes, although BytesIO.writelines swallows
a list of memoryviews as readily as write swallows one memoryview; typeshed
has a second self-type overload for exactly that, so BinaryIO now overrides
writelines the way it already overrides write.

Type checkers read typeshed rather than these annotations, so nothing about
type checking changes; the runtime stubs just stop contradicting the stubs
people check against.
The method is present on, or reachable from, every class typeshed marks as
inheriting from BinaryIO. There are seven of them: _io.FileIO, BytesIO,
BufferedReader, BufferedWriter and BufferedRandom pick it up from the raw
and buffered base classes, http.client.HTTPResponse from io.BufferedIOBase,
and codecs.StreamRecoder reaches it through __getattr__.

StreamRecoder is not much of an exception there. typeshed already declares
nine methods on it that the class itself does not have (close, fileno,
flush, isatty, readable, truncate, seekable, tell and writable), with a
comment saying they are delegated through __getattr__. Wrap a stream that
has only read and write, and all nine raise AttributeError, exactly as
readinto does.

The return type is int rather than int | None for the same reason IO.read
is AnyStr: a raw stream in non-blocking mode can return None from either,
and these stubs already pass over that case.
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34747862 | 📁 Comparing c209802 against main (a5b03fa)

  🔍 Preview build  

2 files changed
± library/typing.html
± whatsnew/changelog.html

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"typing.BinaryIO" missing "readinto" method

1 participant