diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index c909b8bad6d726..33d07623592ee9 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3027,6 +3027,10 @@ ABCs and Protocols for working with I/O :func:`open`. Please note that these classes are not protocols, and their interface is fairly broad. + .. versionchanged:: next + ``BinaryIO`` now declares a ``readinto()`` method, matching the binary + streams that :func:`open` returns. + The protocols :class:`io.Reader` and :class:`io.Writer` offer a simpler alternative for argument types, when only the ``read()`` or ``write()`` methods are accessed, respectively:: diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b0d468b34092b2..c559a863985fb1 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -9660,6 +9660,29 @@ def stuff(a: BinaryIO) -> bytes: a = stuff.__annotations__['a'] self.assertEqual(a.__parameters__, ()) + def test_write_annotations(self): + # These are evaluated lazily, so a wrong name in any of them would + # stay invisible until something introspects the stub. Compare the + # whole mapping so that a dropped or stray annotation shows up too. + self.assertEqual( + IO.writelines.__annotations__, + {'lines': collections.abc.Iterable[AnyStr], 'return': None}) + self.assertEqual( + BinaryIO.write.__annotations__, + {'s': collections.abc.Buffer, 'return': int}) + self.assertEqual( + BinaryIO.writelines.__annotations__, + {'lines': collections.abc.Iterable[collections.abc.Buffer], + 'return': None}) + + def test_binaryio_readinto(self): + # Every class typeshed marks as a BinaryIO has readinto(), so the + # runtime stub declares it as well. + self.assertTrue(BinaryIO.readinto.__isabstractmethod__) + self.assertEqual( + BinaryIO.readinto.__annotations__, + {'buffer': collections.abc.Buffer, 'return': int}) + class RETests(BaseTestCase): # Much of this is really testing _TypeAlias. diff --git a/Lib/typing.py b/Lib/typing.py index 99c467a8af07d8..d6a1f958bb8705 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3636,7 +3636,7 @@ def write(self, s: AnyStr, /) -> int: pass @abstractmethod - def writelines(self, lines: list[AnyStr], /) -> None: + def writelines(self, lines: collections.abc.Iterable[AnyStr], /) -> None: pass @abstractmethod @@ -3654,7 +3654,17 @@ class BinaryIO(IO[bytes]): __slots__ = () @abstractmethod - def write(self, s: bytes | bytearray, /) -> int: + def readinto(self, buffer: collections.abc.Buffer, /) -> int: + pass + + @abstractmethod + def write(self, s: collections.abc.Buffer, /) -> int: + pass + + @abstractmethod + def writelines( + self, lines: collections.abc.Iterable[collections.abc.Buffer], / + ) -> None: pass @abstractmethod diff --git a/Misc/NEWS.d/next/Library/2026-09-24-23-22-01.gh-issue-133492.-bKR-v.rst b/Misc/NEWS.d/next/Library/2026-09-24-23-22-01.gh-issue-133492.-bKR-v.rst new file mode 100644 index 00000000000000..8f296aef800d2c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-24-23-22-01.gh-issue-133492.-bKR-v.rst @@ -0,0 +1,5 @@ +:class:`typing.BinaryIO` now annotates ``write()`` and ``writelines()`` in +terms of :class:`collections.abc.Buffer` rather than :class:`bytes`, and +``writelines()`` on :class:`typing.IO` takes any iterable rather than a list. +Binary streams accept all of this already, and typeshed says the same through +overloads that a runtime stub has no way to spell. diff --git a/Misc/NEWS.d/next/Library/2026-09-24-23-23-48.gh-issue-133492.9Yd4xo.rst b/Misc/NEWS.d/next/Library/2026-09-24-23-23-48.gh-issue-133492.9Yd4xo.rst new file mode 100644 index 00000000000000..63b196791c1a61 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-24-23-23-48.gh-issue-133492.9Yd4xo.rst @@ -0,0 +1,4 @@ +:class:`typing.BinaryIO` now declares a ``readinto()`` method. Every class +that typeshed marks as inheriting from ``BinaryIO`` provides it, in +:class:`codecs.StreamRecoder`'s case through ``__getattr__``, so the runtime +stub was missing a method the type describes.