diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea7c833..8f9523f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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. diff --git a/doc/conf.py b/doc/conf.py index cbb15a70..db9b5185 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -27,7 +27,9 @@ templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -intersphinx_mapping = {'py': ('https://docs.python.org/3', None)} +# This should usually point to /3, unless there is a necessity to link to +# features in future versions of Python. +intersphinx_mapping = {'py': ('https://docs.python.org/3.14', None)} add_module_names = False diff --git a/doc/index.rst b/doc/index.rst index e652c9e4..325182eb 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -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 ~~~~~~~~~~ diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index a542aa75..01e2b270 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -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 diff --git a/src/typing_extensions.py b/src/typing_extensions.py index b541bac5..f2bee507 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -6,6 +6,7 @@ import enum import functools import inspect +import io import keyword import operator import sys @@ -56,6 +57,8 @@ 'SupportsIndex', 'SupportsInt', 'SupportsRound', + 'Reader', + 'Writer', # One-off things. 'Annotated', @@ -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.""" # noqa: E501 + + _NEEDS_SINGLETONMETA = ( not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems") )