Skip to content

Commit cd13dd0

Browse files
committed
Require AsyncGenerator for asynccontextmanager
I'm against merging python#7430 because: - Generator is not an ergonomic type - It's very widely breaking - Safety could easily be enforced by a linter that ensures use of `yield from` instead of `return` in an @contextmanager To expand on that a little: - I care about the typing system being accessible. Generator with its three type vars is not a friendly type. I see enough confusion about Iterable, Iterator, Generator as it is. - Maintaining a high signal to noise / effort ratio is an important part of making typing accessible. Breaking changes that seem arbitrary to a casual user reinforce existing negative impressions of typing that hurt the ecosystem as a whole. - In all the years of typing, this has come up basically never (reported twice by asottile, none of the outlinks from the issue contain the bug, issue itself is not popular), so I think this is 99.99% noise. Between typeshed and mypy, I've seen a lot of typing issue reports. But I'm willing to admit that the considerations are slightly different for asynccontextmanager: - async is less widely used than sync, so maybe this is less widely breaking (let's at least see primer) - async is typically used by more experienced Python users, so there's already an accessibility floor here - There is no equivalent to `yield from` in async that a linter could enforce, so this genuinely can only be solved in type checkers - The issue that led to python#7430 was with asynccontextmanager - We've gotten away with some pedantic narrowings of async types before This is just an experiment, I'm not sure whether I'm actually in favour of this change, but I'm a lot more willing to consider it. Note that I'm not open to slippery slope consistency arguments here; if a constraint is that we do both async and sync or neither, then I'm firmly in the neither camp.
1 parent c1d307f commit cd13dd0

File tree

1 file changed

+8
-4
lines changed

1 file changed

+8
-4
lines changed

stdlib/contextlib.pyi

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import abc
22
import sys
33
from _typeshed import Self, StrOrBytesPath
44
from abc import abstractmethod
5-
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator
5+
from collections.abc import AsyncGenerator, Awaitable, Callable, Generator, Iterator
66
from types import TracebackType
77
from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable
88
from typing_extensions import ParamSpec, TypeAlias
@@ -79,7 +79,9 @@ if sys.version_info >= (3, 10):
7979
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co], AsyncContextDecorator, Generic[_T_co]):
8080
# __init__ and these attributes are actually defined in the base class _GeneratorContextManagerBase,
8181
# which is more trouble than it's worth to include in the stub (see #6676)
82-
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
82+
def __init__(
83+
self, func: Callable[..., AsyncGenerator[_T_co, Any]], args: tuple[Any, ...], kwds: dict[str, Any]
84+
) -> None: ...
8385
gen: AsyncGenerator[_T_co, Any]
8486
func: Callable[..., AsyncGenerator[_T_co, Any]]
8587
args: tuple[Any, ...]
@@ -90,7 +92,9 @@ if sys.version_info >= (3, 10):
9092

9193
else:
9294
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co], Generic[_T_co]):
93-
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
95+
def __init__(
96+
self, func: Callable[..., AsyncGenerator[_T_co, Any]], args: tuple[Any, ...], kwds: dict[str, Any]
97+
) -> None: ...
9498
gen: AsyncGenerator[_T_co, Any]
9599
func: Callable[..., AsyncGenerator[_T_co, Any]]
96100
args: tuple[Any, ...]
@@ -99,7 +103,7 @@ else:
99103
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
100104
) -> bool | None: ...
101105

102-
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ...
106+
def asynccontextmanager(func: Callable[_P, AsyncGenerator[_T_co, Any]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ...
103107

104108
class _SupportsClose(Protocol):
105109
def close(self) -> object: ...

0 commit comments

Comments
 (0)