Skip to content

Commit f02b99d

Browse files
authored
Add Reader and Writer protocols (#582)
1 parent 28f08ac commit f02b99d

File tree

5 files changed

+85
-1
lines changed

5 files changed

+85
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Unreleased
22

33
- Drop support for Python 3.8 (including PyPy-3.8). Patch by [Victorien Plot](https://github.com/Viicos).
4+
5+
New features:
6+
47
- Add support for inline typed dictionaries ([PEP 764](https://peps.python.org/pep-0764/)).
58
Patch by [Victorien Plot](https://github.com/Viicos).
9+
- Add `typing_extensions.Reader` and `typing_extensions.Writer`. Patch by
10+
Sebastian Rittau.
611

712
# Release 4.13.2 (April 10, 2025)
813

@@ -17,6 +22,7 @@
1722
# Release 4.13.1 (April 3, 2025)
1823

1924
Bugfixes:
25+
2026
- Fix regression in 4.13.0 on Python 3.10.2 causing a `TypeError` when using `Concatenate`.
2127
Patch by [Daraan](https://github.com/Daraan).
2228
- Fix `TypeError` when using `evaluate_forward_ref` on Python 3.10.1-2 and 3.9.8-10.

doc/conf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
templates_path = ['_templates']
2828
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
2929

30-
intersphinx_mapping = {'py': ('https://docs.python.org/3', None)}
30+
# This should usually point to /3, unless there is a necessity to link to
31+
# features in future versions of Python.
32+
intersphinx_mapping = {'py': ('https://docs.python.org/3.14', None)}
3133

3234
add_module_names = False
3335

doc/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,18 @@ Protocols
659659

660660
.. versionadded:: 4.6.0
661661

662+
.. class:: Reader
663+
664+
See :py:class:`io.Reader`. Added to the standard library in Python 3.14.
665+
666+
.. versionadded:: 4.14.0
667+
668+
.. class:: Writer
669+
670+
See :py:class:`io.Writer`. Added to the standard library in Python 3.14.
671+
672+
.. versionadded:: 4.14.0
673+
662674
Decorators
663675
~~~~~~~~~~
664676

src/test_typing_extensions.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4103,6 +4103,32 @@ def foo(self): pass
41034103
self.assertIsSubclass(Bar, Functor)
41044104

41054105

4106+
class SpecificProtocolTests(BaseTestCase):
4107+
def test_reader_runtime_checkable(self):
4108+
class MyReader:
4109+
def read(self, n: int) -> bytes:
4110+
return b""
4111+
4112+
class WrongReader:
4113+
def readx(self, n: int) -> bytes:
4114+
return b""
4115+
4116+
self.assertIsInstance(MyReader(), typing_extensions.Reader)
4117+
self.assertNotIsInstance(WrongReader(), typing_extensions.Reader)
4118+
4119+
def test_writer_runtime_checkable(self):
4120+
class MyWriter:
4121+
def write(self, b: bytes) -> int:
4122+
return 0
4123+
4124+
class WrongWriter:
4125+
def writex(self, b: bytes) -> int:
4126+
return 0
4127+
4128+
self.assertIsInstance(MyWriter(), typing_extensions.Writer)
4129+
self.assertNotIsInstance(WrongWriter(), typing_extensions.Writer)
4130+
4131+
41064132
class Point2DGeneric(Generic[T], TypedDict):
41074133
a: T
41084134
b: T

src/typing_extensions.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import enum
77
import functools
88
import inspect
9+
import io
910
import keyword
1011
import operator
1112
import sys
@@ -56,6 +57,8 @@
5657
'SupportsIndex',
5758
'SupportsInt',
5859
'SupportsRound',
60+
'Reader',
61+
'Writer',
5962

6063
# One-off things.
6164
'Annotated',
@@ -846,6 +849,41 @@ def __round__(self, ndigits: int = 0) -> T_co:
846849
pass
847850

848851

852+
if hasattr(io, "Reader") and hasattr(io, "Writer"):
853+
Reader = io.Reader
854+
Writer = io.Writer
855+
else:
856+
@runtime_checkable
857+
class Reader(Protocol[T_co]):
858+
"""Protocol for simple I/O reader instances.
859+
860+
This protocol only supports blocking I/O.
861+
"""
862+
863+
__slots__ = ()
864+
865+
@abc.abstractmethod
866+
def read(self, size: int = ..., /) -> T_co:
867+
"""Read data from the input stream and return it.
868+
869+
If *size* is specified, at most *size* items (bytes/characters) will be
870+
read.
871+
"""
872+
873+
@runtime_checkable
874+
class Writer(Protocol[T_contra]):
875+
"""Protocol for simple I/O writer instances.
876+
877+
This protocol only supports blocking I/O.
878+
"""
879+
880+
__slots__ = ()
881+
882+
@abc.abstractmethod
883+
def write(self, data: T_contra, /) -> int:
884+
"""Write *data* to the output stream and return the number of items written.""" # noqa: E501
885+
886+
849887
_NEEDS_SINGLETONMETA = (
850888
not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
851889
)

0 commit comments

Comments
 (0)