From fff6ca57fc49cf2887297eba19b15729f307da96 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 17:10:13 +0200 Subject: [PATCH 1/7] Implement `_SuppressedExcReturnT` --- stdlib/contextlib.pyi | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 4663b448c79c..aa5fa329b919 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator from types import TracebackType from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable -from typing_extensions import ParamSpec, Self, TypeAlias +from typing_extensions import Never, ParamSpec, Self, TypeAlias __all__ = [ "contextmanager", @@ -32,9 +32,11 @@ _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) _T_io = TypeVar("_T_io", bound=IO[str] | None) _ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None) +_SuppressedExcReturnT = TypeVar("_SuppressedExcReturnT", Never, None, default=Never) _F = TypeVar("_F", bound=Callable[..., Any]) _G_co = TypeVar("_G_co", bound=Generator[Any, Any, Any] | AsyncGenerator[Any, Any], covariant=True) _P = ParamSpec("_P") +_R = TypeVar("_R") _SendT_contra = TypeVar("_SendT_contra", contravariant=True, default=None) _ReturnT_co = TypeVar("_ReturnT_co", covariant=True, default=None) @@ -64,9 +66,14 @@ class AbstractAsyncContextManager(ABC, Protocol[_T_co, _ExitT_co]): # type: ign self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / ) -> _ExitT_co: ... -class ContextDecorator: +# __exit__ can suppress exceptions by returning a true value. +# _SuppressedReturnT extends the decorated function's return type with +# - Never, if the decorating context manager never suppresses exceptions; +# - None, if the decorating context manager may suppress exceptions. +# See #13512. +class ContextDecorator(Generic[_SuppressedExcReturnT]): def _recreate_cm(self) -> Self: ... - def __call__(self, func: _F) -> _F: ... + def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R | _SuppressedExcReturnT]: ... class _GeneratorContextManagerBase(Generic[_G_co]): # Ideally this would use ParamSpec, but that requires (*args, **kwargs), which this isn't. see #6676 @@ -79,7 +86,7 @@ class _GeneratorContextManagerBase(Generic[_G_co]): class _GeneratorContextManager( _GeneratorContextManagerBase[Generator[_T_co, _SendT_contra, _ReturnT_co]], AbstractContextManager[_T_co, bool | None], - ContextDecorator, + ContextDecorator[None], ): def __exit__( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None @@ -90,14 +97,15 @@ def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _Generat if sys.version_info >= (3, 10): _AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) - class AsyncContextDecorator: + # _SuppressedReturnT: see ContextDecorator. + class AsyncContextDecorator(Generic[_SuppressedExcReturnT]): def _recreate_cm(self) -> Self: ... - def __call__(self, func: _AF) -> _AF: ... + def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R | _SuppressedExcReturnT]: ... class _AsyncGeneratorContextManager( _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], AbstractAsyncContextManager[_T_co, bool | None], - AsyncContextDecorator, + AsyncContextDecorator[None], ): async def __aexit__( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None From 6db7d8dd9ba6f7c7edf0080120d276f980a231df Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 17:28:38 +0200 Subject: [PATCH 2/7] Increase LoB and improve comments --- stdlib/contextlib.pyi | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index aa5fa329b919..b37697de65ca 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -32,7 +32,6 @@ _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) _T_io = TypeVar("_T_io", bound=IO[str] | None) _ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None) -_SuppressedExcReturnT = TypeVar("_SuppressedExcReturnT", Never, None, default=Never) _F = TypeVar("_F", bound=Callable[..., Any]) _G_co = TypeVar("_G_co", bound=Generator[Any, Any, Any] | AsyncGenerator[Any, Any], covariant=True) _P = ParamSpec("_P") @@ -66,9 +65,11 @@ class AbstractAsyncContextManager(ABC, Protocol[_T_co, _ExitT_co]): # type: ign self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / ) -> _ExitT_co: ... +_SuppressedExcReturnT = TypeVar("_SuppressedExcReturnT", Never, None, default=Never) # __exit__ can suppress exceptions by returning a true value. # _SuppressedReturnT extends the decorated function's return type with -# - Never, if the decorating context manager never suppresses exceptions; +# - Never (default, has no effect on the return type), +# if the decorating context manager never suppresses exceptions; # - None, if the decorating context manager may suppress exceptions. # See #13512. class ContextDecorator(Generic[_SuppressedExcReturnT]): From 976351f89e4c96dae38aad0fc466da5eeeae4c68 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 17:29:15 +0200 Subject: [PATCH 3/7] Remove `_F` and `_AF` type var --- stdlib/contextlib.pyi | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index b37697de65ca..cfdcf8943966 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -32,7 +32,6 @@ _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) _T_io = TypeVar("_T_io", bound=IO[str] | None) _ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None) -_F = TypeVar("_F", bound=Callable[..., Any]) _G_co = TypeVar("_G_co", bound=Generator[Any, Any, Any] | AsyncGenerator[Any, Any], covariant=True) _P = ParamSpec("_P") _R = TypeVar("_R") @@ -66,6 +65,7 @@ class AbstractAsyncContextManager(ABC, Protocol[_T_co, _ExitT_co]): # type: ign ) -> _ExitT_co: ... _SuppressedExcReturnT = TypeVar("_SuppressedExcReturnT", Never, None, default=Never) + # __exit__ can suppress exceptions by returning a true value. # _SuppressedReturnT extends the decorated function's return type with # - Never (default, has no effect on the return type), @@ -96,8 +96,6 @@ class _GeneratorContextManager( def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ... if sys.version_info >= (3, 10): - _AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) - # _SuppressedReturnT: see ContextDecorator. class AsyncContextDecorator(Generic[_SuppressedExcReturnT]): def _recreate_cm(self) -> Self: ... From 9dd131f891828a43e8ecd11b81aecfc44a56021f Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 17:32:05 +0200 Subject: [PATCH 4/7] Nit: remove commas where incorrect --- stdlib/contextlib.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index cfdcf8943966..b248cabd352b 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -68,9 +68,9 @@ _SuppressedExcReturnT = TypeVar("_SuppressedExcReturnT", Never, None, default=Ne # __exit__ can suppress exceptions by returning a true value. # _SuppressedReturnT extends the decorated function's return type with -# - Never (default, has no effect on the return type), +# - Never (default, has no effect on the return type) # if the decorating context manager never suppresses exceptions; -# - None, if the decorating context manager may suppress exceptions. +# - None if the decorating context manager may suppress exceptions. # See #13512. class ContextDecorator(Generic[_SuppressedExcReturnT]): def _recreate_cm(self) -> Self: ... From ed5debcd689485f99dc3eddab61d748535e799e3 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 17:43:17 +0200 Subject: [PATCH 5/7] Improve the comments --- stdlib/contextlib.pyi | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index b248cabd352b..1ea022b822fb 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -64,17 +64,16 @@ class AbstractAsyncContextManager(ABC, Protocol[_T_co, _ExitT_co]): # type: ign self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, / ) -> _ExitT_co: ... -_SuppressedExcReturnT = TypeVar("_SuppressedExcReturnT", Never, None, default=Never) +_ExcReturnT = TypeVar("_ExcReturnT", Never, None, default=Never) # __exit__ can suppress exceptions by returning a true value. -# _SuppressedReturnT extends the decorated function's return type with -# - Never (default, has no effect on the return type) -# if the decorating context manager never suppresses exceptions; -# - None if the decorating context manager may suppress exceptions. +# _ExcReturnT describes function's return type after an exception occurs: +# - Never (default, the context manager never suppresses exceptions) +# - None (the context manager may suppress exceptions) # See #13512. -class ContextDecorator(Generic[_SuppressedExcReturnT]): +class ContextDecorator(Generic[_ExcReturnT]): def _recreate_cm(self) -> Self: ... - def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R | _SuppressedExcReturnT]: ... + def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R | _ExcReturnT]: ... class _GeneratorContextManagerBase(Generic[_G_co]): # Ideally this would use ParamSpec, but that requires (*args, **kwargs), which this isn't. see #6676 @@ -96,10 +95,10 @@ class _GeneratorContextManager( def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ... if sys.version_info >= (3, 10): - # _SuppressedReturnT: see ContextDecorator. - class AsyncContextDecorator(Generic[_SuppressedExcReturnT]): + # _ExcReturnT: see ContextDecorator. + class AsyncContextDecorator(Generic[_ExcReturnT]): def _recreate_cm(self) -> Self: ... - def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R | _SuppressedExcReturnT]: ... + def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R | _ExcReturnT]: ... class _AsyncGeneratorContextManager( _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], From 8eefca3aa45fa71ea856e6a59ca9a6fe13efee01 Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 22:14:06 +0200 Subject: [PATCH 6/7] Delegate `_ExcReturnT` to `_GeneratorContextManager` optional type arg --- stdlib/contextlib.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 1ea022b822fb..fdb7bdd98107 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -86,7 +86,7 @@ class _GeneratorContextManagerBase(Generic[_G_co]): class _GeneratorContextManager( _GeneratorContextManagerBase[Generator[_T_co, _SendT_contra, _ReturnT_co]], AbstractContextManager[_T_co, bool | None], - ContextDecorator[None], + ContextDecorator[_ExcReturnT], ): def __exit__( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None @@ -103,7 +103,7 @@ if sys.version_info >= (3, 10): class _AsyncGeneratorContextManager( _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], AbstractAsyncContextManager[_T_co, bool | None], - AsyncContextDecorator[None], + AsyncContextDecorator[_ExcReturnT], ): async def __aexit__( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None From d604bb27dfff07e570ea956c4196051876eac7aa Mon Sep 17 00:00:00 2001 From: bswck Date: Tue, 22 Jul 2025 22:18:23 +0200 Subject: [PATCH 7/7] Add note on type argument inference --- stdlib/contextlib.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index fdb7bdd98107..9e2499b77a77 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -86,7 +86,7 @@ class _GeneratorContextManagerBase(Generic[_G_co]): class _GeneratorContextManager( _GeneratorContextManagerBase[Generator[_T_co, _SendT_contra, _ReturnT_co]], AbstractContextManager[_T_co, bool | None], - ContextDecorator[_ExcReturnT], + ContextDecorator[_ExcReturnT], # _ExcReturnT is inferred by the type checker ): def __exit__( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None @@ -103,7 +103,7 @@ if sys.version_info >= (3, 10): class _AsyncGeneratorContextManager( _GeneratorContextManagerBase[AsyncGenerator[_T_co, _SendT_contra]], AbstractAsyncContextManager[_T_co, bool | None], - AsyncContextDecorator[_ExcReturnT], + AsyncContextDecorator[_ExcReturnT], # _ExcReturnT is inferred by the type checker ): async def __aexit__( self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None