Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Unreleased

- Drop support for Python 3.8 (including PyPy-3.8). Patch by [Victorien Plot](https://github.com/Viicos).

New features:

- Add support for inline typed dictionaries ([PEP 764](https://peps.python.org/pep-0764/)).
Patch by [Victorien Plot](https://github.com/Viicos).
- Add `typing_extensions.Reader` and `typing_extensions.Writer`. Patch by
Sebastian Rittau.

# Release 4.13.2 (April 10, 2025)

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

Bugfixes:

- Fix regression in 4.13.0 on Python 3.10.2 causing a `TypeError` when using `Concatenate`.
Patch by [Daraan](https://github.com/Daraan).
- Fix `TypeError` when using `evaluate_forward_ref` on Python 3.10.1-2 and 3.9.8-10.
Expand Down
12 changes: 12 additions & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,18 @@ Protocols

.. versionadded:: 4.6.0

.. class:: Reader

See :py:class:`io.Reader`. Added to the standard library in Python 3.14.

.. versionadded:: 4.14.0

.. class:: Writer

See :py:class:`io.Writer`. Added to the standard library in Python 3.14.

.. versionadded:: 4.14.0

Decorators
~~~~~~~~~~

Expand Down
26 changes: 26 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4103,6 +4103,32 @@ def foo(self): pass
self.assertIsSubclass(Bar, Functor)


class SpecificProtocolTests(BaseTestCase):
def test_reader_runtime_checkable(self):
class MyReader:
def read(self, n: int) -> bytes:
return b""

class WrongReader:
def readx(self, n: int) -> bytes:
return b""

self.assertIsInstance(MyReader(), typing_extensions.Reader)
self.assertNotIsInstance(WrongReader(), typing_extensions.Reader)

def test_writer_runtime_checkable(self):
class MyWriter:
def write(self, b: bytes) -> int:
return 0

class WrongWriter:
def writex(self, b: bytes) -> int:
return 0

self.assertIsInstance(MyWriter(), typing_extensions.Writer)
self.assertNotIsInstance(WrongWriter(), typing_extensions.Writer)


class Point2DGeneric(Generic[T], TypedDict):
a: T
b: T
Expand Down
38 changes: 38 additions & 0 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import enum
import functools
import inspect
import io
import keyword
import operator
import sys
Expand Down Expand Up @@ -56,6 +57,8 @@
'SupportsIndex',
'SupportsInt',
'SupportsRound',
'Reader',
'Writer',

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


if hasattr(io, "Reader") and hasattr(io, "Writer"):
Reader = io.Reader
Writer = io.Writer
else:
@runtime_checkable
class Reader(Protocol[T_co]):
"""Protocol for simple I/O reader instances.

This protocol only supports blocking I/O.
"""

__slots__ = ()

@abc.abstractmethod
def read(self, size: int = ..., /) -> T_co:
"""Read data from the input stream and return it.

If size is specified, at most size items (bytes/characters) will be
read.
"""

@runtime_checkable
class Writer(Protocol[T_contra]):
"""Protocol for simple I/O writer instances.

This protocol only supports blocking I/O.
"""

__slots__ = ()

@abc.abstractmethod
def write(self, data: T_contra, /) -> int:
"""Write data to the output stream and return the number of items written."""


_NEEDS_SINGLETONMETA = (
not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
)
Expand Down
Loading