From 26ecc260c90b9992591656d62340b9b7d03de496 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Wed, 9 Oct 2024 17:44:52 -0700 Subject: [PATCH 1/4] add _asyncio improves naming and inheritance for asyncio.Future and asyncio.Task related to https://github.com/python/typeshed/issues/3968 --- stdlib/@tests/stubtest_allowlists/common.txt | 1 + stdlib/VERSIONS | 1 + stdlib/_asyncio.pyi | 111 +++++++++++++++++++ stdlib/asyncio/events.pyi | 3 +- stdlib/asyncio/futures.pyi | 45 +------- stdlib/asyncio/tasks.pyi | 58 +--------- 6 files changed, 121 insertions(+), 98 deletions(-) create mode 100644 stdlib/_asyncio.pyi diff --git a/stdlib/@tests/stubtest_allowlists/common.txt b/stdlib/@tests/stubtest_allowlists/common.txt index d3b998c61590..36b7bf39b6c9 100644 --- a/stdlib/@tests/stubtest_allowlists/common.txt +++ b/stdlib/@tests/stubtest_allowlists/common.txt @@ -4,6 +4,7 @@ # Please keep sorted alphabetically +_asyncio.Future.__init__ # Usually initialized from c object _collections_abc.AsyncGenerator.ag_await _collections_abc.AsyncGenerator.ag_code _collections_abc.AsyncGenerator.ag_frame diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 28a27f5d9a0b..f33ca81acf8f 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -19,6 +19,7 @@ __future__: 3.0- __main__: 3.0- +_asyncio: 3.0- _ast: 3.0- _bisect: 3.0- _bootlocale: 3.4-3.9 diff --git a/stdlib/_asyncio.pyi b/stdlib/_asyncio.pyi new file mode 100644 index 000000000000..109fe4f8e482 --- /dev/null +++ b/stdlib/_asyncio.pyi @@ -0,0 +1,111 @@ +import sys +from asyncio.events import AbstractEventLoop +from collections.abc import Awaitable, Callable, Coroutine, Generator, Iterable +from contextvars import Context +from types import FrameType +from typing import Any, Literal, TextIO, TypeVar +from typing_extensions import Self, TypeAlias + +if sys.version_info >= (3, 9): + from types import GenericAlias + +_T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) +_TaskYieldType: TypeAlias = Future[object] | None + +class Future(Awaitable[_T], Iterable[_T]): + _state: str + @property + def _exception(self) -> BaseException | None: ... + _blocking: bool + @property + def _log_traceback(self) -> bool: ... + @_log_traceback.setter + def _log_traceback(self, val: Literal[False]) -> None: ... + _asyncio_future_blocking: bool # is a part of duck-typing contract for `Future` + def __init__(self, *, loop: AbstractEventLoop | None = ...) -> None: ... + def __del__(self) -> None: ... + def get_loop(self) -> AbstractEventLoop: ... + @property + def _callbacks(self) -> list[tuple[Callable[[Self], Any], Context]]: ... + def add_done_callback(self, fn: Callable[[Self], object], /, *, context: Context | None = None) -> None: ... + if sys.version_info >= (3, 9): + def cancel(self, msg: Any | None = None) -> bool: ... + else: + def cancel(self) -> bool: ... + + def cancelled(self) -> bool: ... + def done(self) -> bool: ... + def result(self) -> _T: ... + def exception(self) -> BaseException | None: ... + def remove_done_callback(self, fn: Callable[[Self], object], /) -> int: ... + def set_result(self, result: _T, /) -> None: ... + def set_exception(self, exception: type | BaseException, /) -> None: ... + def __iter__(self) -> Generator[Any, None, _T]: ... + def __await__(self) -> Generator[Any, None, _T]: ... + @property + def _loop(self) -> AbstractEventLoop: ... + if sys.version_info >= (3, 9): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + +if sys.version_info >= (3, 12): + _TaskCompatibleCoro: TypeAlias = Coroutine[Any, Any, _T_co] +elif sys.version_info >= (3, 9): + _TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Coroutine[Any, Any, _T_co] +else: + _TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co] + +# mypy and pyright complain that a subclass of an invariant class shouldn't be covariant. +# While this is true in general, here it's sort-of okay to have a covariant subclass, +# since the only reason why `asyncio.Future` is invariant is the `set_result()` method, +# and `asyncio.Task.set_result()` always raises. +class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportInvalidTypeArguments] + if sys.version_info >= (3, 12): + def __init__( + self, + coro: _TaskCompatibleCoro[_T_co], + *, + loop: AbstractEventLoop = ..., + name: str | None = ..., + context: Context | None = None, + eager_start: bool = False, + ) -> None: ... + elif sys.version_info >= (3, 11): + def __init__( + self, + coro: _TaskCompatibleCoro[_T_co], + *, + loop: AbstractEventLoop = ..., + name: str | None = ..., + context: Context | None = None, + ) -> None: ... + else: + def __init__( + self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ... + ) -> None: ... + + if sys.version_info >= (3, 12): + def get_coro(self) -> _TaskCompatibleCoro[_T_co] | None: ... + else: + def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ... + + def get_name(self) -> str: ... + def set_name(self, value: object, /) -> None: ... + if sys.version_info >= (3, 12): + def get_context(self) -> Context: ... + + def get_stack(self, *, limit: int | None = None) -> list[FrameType]: ... + def print_stack(self, *, limit: int | None = None, file: TextIO | None = None) -> None: ... + if sys.version_info >= (3, 11): + def cancelling(self) -> int: ... + def uncancel(self) -> int: ... + if sys.version_info < (3, 9): + @classmethod + def current_task(cls, loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... + @classmethod + def all_tasks(cls, loop: AbstractEventLoop | None = None) -> set[Task[Any]]: ... + if sys.version_info >= (3, 9): + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + +def get_event_loop() -> AbstractEventLoop: ... +def get_running_loop() -> AbstractEventLoop: ... diff --git a/stdlib/asyncio/events.pyi b/stdlib/asyncio/events.pyi index eed688fc792a..443a5e67031f 100644 --- a/stdlib/asyncio/events.pyi +++ b/stdlib/asyncio/events.pyi @@ -1,5 +1,6 @@ import ssl import sys +from _asyncio import get_event_loop as get_event_loop, get_running_loop as get_running_loop from _typeshed import FileDescriptorLike, ReadableBuffer, StrPath, Unused, WriteableBuffer from abc import ABCMeta, abstractmethod from collections.abc import Callable, Sequence @@ -632,7 +633,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy, metaclass=ABCMeta): def get_event_loop_policy() -> AbstractEventLoopPolicy: ... def set_event_loop_policy(policy: AbstractEventLoopPolicy | None) -> None: ... -def get_event_loop() -> AbstractEventLoop: ... def set_event_loop(loop: AbstractEventLoop | None) -> None: ... def new_event_loop() -> AbstractEventLoop: ... @@ -649,4 +649,3 @@ if sys.version_info < (3, 14): def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ... def _get_running_loop() -> AbstractEventLoop: ... -def get_running_loop() -> AbstractEventLoop: ... diff --git a/stdlib/asyncio/futures.pyi b/stdlib/asyncio/futures.pyi index e19fd53f3311..d9d7d1cb0139 100644 --- a/stdlib/asyncio/futures.pyi +++ b/stdlib/asyncio/futures.pyi @@ -1,14 +1,13 @@ import sys -from collections.abc import Awaitable, Callable, Generator, Iterable +from _asyncio import Future as Future from concurrent.futures._base import Future as _ConcurrentFuture -from contextvars import Context -from typing import Any, Literal, TypeVar -from typing_extensions import Self, TypeIs +from typing import Any, TypeVar +from typing_extensions import TypeIs from .events import AbstractEventLoop if sys.version_info >= (3, 9): - from types import GenericAlias + pass __all__ = ("Future", "wrap_future", "isfuture") @@ -18,40 +17,4 @@ _T = TypeVar("_T") # but it leads to circular import error in pytype tool. # That's why the import order is reversed. def isfuture(obj: object) -> TypeIs[Future[Any]]: ... - -class Future(Awaitable[_T], Iterable[_T]): - _state: str - @property - def _exception(self) -> BaseException | None: ... - _blocking: bool - @property - def _log_traceback(self) -> bool: ... - @_log_traceback.setter - def _log_traceback(self, val: Literal[False]) -> None: ... - _asyncio_future_blocking: bool # is a part of duck-typing contract for `Future` - def __init__(self, *, loop: AbstractEventLoop | None = ...) -> None: ... - def __del__(self) -> None: ... - def get_loop(self) -> AbstractEventLoop: ... - @property - def _callbacks(self) -> list[tuple[Callable[[Self], Any], Context]]: ... - def add_done_callback(self, fn: Callable[[Self], object], /, *, context: Context | None = None) -> None: ... - if sys.version_info >= (3, 9): - def cancel(self, msg: Any | None = None) -> bool: ... - else: - def cancel(self) -> bool: ... - - def cancelled(self) -> bool: ... - def done(self) -> bool: ... - def result(self) -> _T: ... - def exception(self) -> BaseException | None: ... - def remove_done_callback(self, fn: Callable[[Self], object], /) -> int: ... - def set_result(self, result: _T, /) -> None: ... - def set_exception(self, exception: type | BaseException, /) -> None: ... - def __iter__(self) -> Generator[Any, None, _T]: ... - def __await__(self) -> Generator[Any, None, _T]: ... - @property - def _loop(self) -> AbstractEventLoop: ... - if sys.version_info >= (3, 9): - def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - def wrap_future(future: _ConcurrentFuture[_T] | Future[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]: ... diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index bb423e857399..0f93c07bf335 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -1,8 +1,8 @@ import concurrent.futures import sys +from _asyncio import Task as Task from collections.abc import Awaitable, Coroutine, Generator, Iterable, Iterator -from types import FrameType -from typing import Any, Literal, Protocol, TextIO, TypeVar, overload +from typing import Any, Literal, Protocol, TypeVar, overload from typing_extensions import TypeAlias from . import _CoroutineLike @@ -10,7 +10,7 @@ from .events import AbstractEventLoop from .futures import Future if sys.version_info >= (3, 9): - from types import GenericAlias + pass if sys.version_info >= (3, 11): from contextvars import Context @@ -400,58 +400,6 @@ elif sys.version_info >= (3, 9): else: _TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co] -# mypy and pyright complain that a subclass of an invariant class shouldn't be covariant. -# While this is true in general, here it's sort-of okay to have a covariant subclass, -# since the only reason why `asyncio.Future` is invariant is the `set_result()` method, -# and `asyncio.Task.set_result()` always raises. -class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportInvalidTypeArguments] - if sys.version_info >= (3, 12): - def __init__( - self, - coro: _TaskCompatibleCoro[_T_co], - *, - loop: AbstractEventLoop = ..., - name: str | None = ..., - context: Context | None = None, - eager_start: bool = False, - ) -> None: ... - elif sys.version_info >= (3, 11): - def __init__( - self, - coro: _TaskCompatibleCoro[_T_co], - *, - loop: AbstractEventLoop = ..., - name: str | None = ..., - context: Context | None = None, - ) -> None: ... - else: - def __init__( - self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ... - ) -> None: ... - - if sys.version_info >= (3, 12): - def get_coro(self) -> _TaskCompatibleCoro[_T_co] | None: ... - else: - def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ... - - def get_name(self) -> str: ... - def set_name(self, value: object, /) -> None: ... - if sys.version_info >= (3, 12): - def get_context(self) -> Context: ... - - def get_stack(self, *, limit: int | None = None) -> list[FrameType]: ... - def print_stack(self, *, limit: int | None = None, file: TextIO | None = None) -> None: ... - if sys.version_info >= (3, 11): - def cancelling(self) -> int: ... - def uncancel(self) -> int: ... - if sys.version_info < (3, 9): - @classmethod - def current_task(cls, loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... - @classmethod - def all_tasks(cls, loop: AbstractEventLoop | None = None) -> set[Task[Any]]: ... - if sys.version_info >= (3, 9): - def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... - def all_tasks(loop: AbstractEventLoop | None = None) -> set[Task[Any]]: ... if sys.version_info >= (3, 11): From 44cddf684fa24a51281c107cad7f3c141b95e657 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Wed, 9 Oct 2024 17:47:54 -0700 Subject: [PATCH 2/4] clean up --- stdlib/asyncio/futures.pyi | 4 ---- stdlib/asyncio/tasks.pyi | 2 -- 2 files changed, 6 deletions(-) diff --git a/stdlib/asyncio/futures.pyi b/stdlib/asyncio/futures.pyi index d9d7d1cb0139..28e6ca8c86a3 100644 --- a/stdlib/asyncio/futures.pyi +++ b/stdlib/asyncio/futures.pyi @@ -1,4 +1,3 @@ -import sys from _asyncio import Future as Future from concurrent.futures._base import Future as _ConcurrentFuture from typing import Any, TypeVar @@ -6,9 +5,6 @@ from typing_extensions import TypeIs from .events import AbstractEventLoop -if sys.version_info >= (3, 9): - pass - __all__ = ("Future", "wrap_future", "isfuture") _T = TypeVar("_T") diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index 0f93c07bf335..37030511ac74 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -9,8 +9,6 @@ from . import _CoroutineLike from .events import AbstractEventLoop from .futures import Future -if sys.version_info >= (3, 9): - pass if sys.version_info >= (3, 11): from contextvars import Context From ed64a5ed2c27dc4e93ce759cd5d3cff4f811fca7 Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Wed, 9 Oct 2024 18:21:34 -0700 Subject: [PATCH 3/4] more functions --- stdlib/_asyncio.pyi | 9 +++++++++ stdlib/asyncio/events.pyi | 10 ++++++---- stdlib/asyncio/tasks.pyi | 18 +++++++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/stdlib/_asyncio.pyi b/stdlib/_asyncio.pyi index 109fe4f8e482..18920cd8a8a4 100644 --- a/stdlib/_asyncio.pyi +++ b/stdlib/_asyncio.pyi @@ -109,3 +109,12 @@ class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportIn def get_event_loop() -> AbstractEventLoop: ... def get_running_loop() -> AbstractEventLoop: ... +def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ... +def _get_running_loop() -> AbstractEventLoop: ... +def _register_task(task: Task[Any]) -> None: ... +def _unregister_task(task: Task[Any]) -> None: ... +def _enter_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... +def _leave_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... + +if sys.version_info >= (3, 12): + def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... diff --git a/stdlib/asyncio/events.pyi b/stdlib/asyncio/events.pyi index 443a5e67031f..ead64070671f 100644 --- a/stdlib/asyncio/events.pyi +++ b/stdlib/asyncio/events.pyi @@ -1,6 +1,11 @@ import ssl import sys -from _asyncio import get_event_loop as get_event_loop, get_running_loop as get_running_loop +from _asyncio import ( + _get_running_loop as _get_running_loop, + _set_running_loop as _set_running_loop, + get_event_loop as get_event_loop, + get_running_loop as get_running_loop, +) from _typeshed import FileDescriptorLike, ReadableBuffer, StrPath, Unused, WriteableBuffer from abc import ABCMeta, abstractmethod from collections.abc import Callable, Sequence @@ -646,6 +651,3 @@ if sys.version_info < (3, 14): else: def get_child_watcher() -> AbstractChildWatcher: ... def set_child_watcher(watcher: AbstractChildWatcher) -> None: ... - -def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ... -def _get_running_loop() -> AbstractEventLoop: ... diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index 37030511ac74..47d1bb13e4a3 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -1,6 +1,12 @@ import concurrent.futures import sys -from _asyncio import Task as Task +from _asyncio import ( + Task as Task, + _enter_task as _enter_task, + _leave_task as _leave_task, + _register_task as _register_task, + _unregister_task as _unregister_task, +) from collections.abc import Awaitable, Coroutine, Generator, Iterable, Iterator from typing import Any, Literal, Protocol, TypeVar, overload from typing_extensions import TypeAlias @@ -406,9 +412,10 @@ if sys.version_info >= (3, 11): else: def create_task(coro: _CoroutineLike[_T], *, name: str | None = None) -> Task[_T]: ... -def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... -def _enter_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... -def _leave_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ... +if sys.version_info >= (3, 12): + from _asyncio import current_task as current_task +else: + def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ... if sys.version_info >= (3, 12): _TaskT_co = TypeVar("_TaskT_co", bound=Task[Any], covariant=True) @@ -445,6 +452,3 @@ if sys.version_info >= (3, 12): name: str | None = None, context: Context | None = None, ) -> Task[_T_co]: ... - -def _register_task(task: Task[Any]) -> None: ... -def _unregister_task(task: Task[Any]) -> None: ... From b391448f2e9dd7a5dc5183df9963d2623c79e4cb Mon Sep 17 00:00:00 2001 From: Stephen Morton Date: Wed, 9 Oct 2024 18:27:36 -0700 Subject: [PATCH 4/4] sort order --- stdlib/VERSIONS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index f33ca81acf8f..ed23ee6ddcea 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -19,8 +19,8 @@ __future__: 3.0- __main__: 3.0- -_asyncio: 3.0- _ast: 3.0- +_asyncio: 3.0- _bisect: 3.0- _bootlocale: 3.4-3.9 _codecs: 3.0-