-
-
Notifications
You must be signed in to change notification settings - Fork 362
Use TypeVarTuple in our APIs #2881
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Changes from all commits
bd5a451
97d6e0e
9bcb185
6f8153f
52c69c8
13d5d63
9ed1d08
0933718
a0e9966
5899ad4
e12273f
5a39c8b
5b0215c
342733c
749ef94
1a0daa6
ff01a82
b91b4b9
972961e
bbd436d
bac6558
9783f8b
e1edda0
176092b
2903a59
d9d7a3d
580627d
e012611
9f94f97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `TypeVarTuple <https://docs.python.org/3.12/library/typing.html#typing.TypeVarTuple>`_ is now used to fully type :meth:`nursery.start_soon() <trio.Nursery.start_soon>`, :func:`trio.run()`, :func:`trio.to_thread.run_sync()`, and other similar functions accepting ``(func, *args)``. This means type checkers will be able to verify types are used correctly. :meth:`nursery.start() <trio.Nursery.start>` is not fully typed yet however. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -53,6 +53,12 @@ | |||||
| if sys.version_info < (3, 11): | ||||||
| from exceptiongroup import BaseExceptionGroup | ||||||
|
|
||||||
| FnT = TypeVar("FnT", bound="Callable[..., Any]") | ||||||
| StatusT = TypeVar("StatusT") | ||||||
| StatusT_co = TypeVar("StatusT_co", covariant=True) | ||||||
| StatusT_contra = TypeVar("StatusT_contra", contravariant=True) | ||||||
| RetT = TypeVar("RetT") | ||||||
|
|
||||||
|
|
||||||
| if TYPE_CHECKING: | ||||||
| import contextvars | ||||||
|
|
@@ -70,19 +76,25 @@ | |||||
| # for some strange reason Sphinx works with outcome.Outcome, but not Outcome, in | ||||||
| # start_guest_run. Same with types.FrameType in iter_await_frames | ||||||
| import outcome | ||||||
| from typing_extensions import Self | ||||||
| from typing_extensions import Self, TypeVarTuple, Unpack | ||||||
|
|
||||||
| PosArgT = TypeVarTuple("PosArgT") | ||||||
|
|
||||||
| # Needs to be guarded, since Unpack[] would be evaluated at runtime. | ||||||
| class _NurseryStartFunc(Protocol[Unpack[PosArgT], StatusT_co]): | ||||||
A5rocks marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| """Type of functions passed to `nursery.start() <trio.Nursery.start>`.""" | ||||||
|
|
||||||
| def __call__( | ||||||
| self, *args: Unpack[PosArgT], task_status: TaskStatus[StatusT_co] | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
edit: I thought that would work (and it does if Errr, so we're promising that anything with this protocol can call it as such. But we're also passing functions that don't allow that? (actually, maybe not, and that's an actual bug. But as is our protocol shouldn't accept edit edit: ok my analysis above is totally incorrect, only keeping it to prevent someone from going down the same path i went down. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately no, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah sorry about the repeated confusion with this thread, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah it's real confusing. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. I took a look at mypy's code for the last 45 minutes and spotted:
I'm not really sure how to solve this myself, a bug report would probably work nicely. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @A5rocks probably worth cross-posting your analysis in the issue that teamspen opened. |
||||||
| ) -> Awaitable[object]: | ||||||
| ... | ||||||
|
|
||||||
|
|
||||||
| DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: Final = 1000 | ||||||
|
|
||||||
| # Passed as a sentinel | ||||||
| _NO_SEND: Final[Outcome[Any]] = cast("Outcome[Any]", object()) | ||||||
|
|
||||||
| FnT = TypeVar("FnT", bound="Callable[..., Any]") | ||||||
| StatusT = TypeVar("StatusT") | ||||||
| StatusT_co = TypeVar("StatusT_co", covariant=True) | ||||||
| StatusT_contra = TypeVar("StatusT_contra", contravariant=True) | ||||||
| RetT = TypeVar("RetT") | ||||||
|
|
||||||
|
|
||||||
| @final | ||||||
| class _NoStatus(metaclass=NoPublicConstructor): | ||||||
|
|
@@ -1119,9 +1131,8 @@ def aborted(raise_cancel: _core.RaiseCancelT) -> Abort: | |||||
|
|
||||||
| def start_soon( | ||||||
| self, | ||||||
| # TODO: TypeVarTuple | ||||||
| async_fn: Callable[..., Awaitable[object]], | ||||||
| *args: object, | ||||||
| async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]], | ||||||
| *args: Unpack[PosArgT], | ||||||
| name: object = None, | ||||||
| ) -> None: | ||||||
| """Creates a child task, scheduling ``await async_fn(*args)``. | ||||||
|
|
@@ -1170,7 +1181,7 @@ async def start( | |||||
| async_fn: Callable[..., Awaitable[object]], | ||||||
| *args: object, | ||||||
| name: object = None, | ||||||
| ) -> StatusT: | ||||||
| ) -> Any: | ||||||
| r"""Creates and initializes a child task. | ||||||
|
|
||||||
| Like :meth:`start_soon`, but blocks until the new task has | ||||||
|
|
@@ -1219,7 +1230,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED): | |||||
| # `run` option, which would cause it to wrap a pre-started() | ||||||
| # exception in an extra ExceptionGroup. See #2611. | ||||||
| async with open_nursery(strict_exception_groups=False) as old_nursery: | ||||||
| task_status: _TaskStatus[StatusT] = _TaskStatus(old_nursery, self) | ||||||
| task_status: _TaskStatus[Any] = _TaskStatus(old_nursery, self) | ||||||
| thunk = functools.partial(async_fn, task_status=task_status) | ||||||
| task = GLOBAL_RUN_CONTEXT.runner.spawn_impl( | ||||||
| thunk, args, old_nursery, name | ||||||
|
|
@@ -1232,7 +1243,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED): | |||||
| # (Any exceptions propagate directly out of the above.) | ||||||
| if task_status._value is _NoStatus: | ||||||
| raise RuntimeError("child exited without calling task_status.started()") | ||||||
| return task_status._value # type: ignore[return-value] # Mypy doesn't narrow yet. | ||||||
| return task_status._value | ||||||
| finally: | ||||||
| self._pending_starts -= 1 | ||||||
| self._check_nursery_closed() | ||||||
|
|
@@ -1690,9 +1701,8 @@ def reschedule( # type: ignore[misc] | |||||
|
|
||||||
| def spawn_impl( | ||||||
| self, | ||||||
| # TODO: TypeVarTuple | ||||||
| async_fn: Callable[..., Awaitable[object]], | ||||||
| args: tuple[object, ...], | ||||||
| async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]], | ||||||
| args: tuple[Unpack[PosArgT]], | ||||||
| nursery: Nursery | None, | ||||||
| name: object, | ||||||
| *, | ||||||
|
|
@@ -1721,7 +1731,8 @@ def spawn_impl( | |||||
| # Call the function and get the coroutine object, while giving helpful | ||||||
| # errors for common mistakes. | ||||||
| ###### | ||||||
| coro = context.run(coroutine_or_error, async_fn, *args) | ||||||
| # TypeVarTuple passed into ParamSpec function confuses Mypy. | ||||||
| coro = context.run(coroutine_or_error, async_fn, *args) # type: ignore[arg-type] | ||||||
|
|
||||||
| if name is None: | ||||||
| name = async_fn | ||||||
|
|
@@ -1808,12 +1819,11 @@ def task_exited(self, task: Task, outcome: Outcome[Any]) -> None: | |||||
| # System tasks and init | ||||||
| ################ | ||||||
|
|
||||||
| @_public # Type-ignore due to use of Any here. | ||||||
| def spawn_system_task( # type: ignore[misc] | ||||||
| @_public | ||||||
| def spawn_system_task( | ||||||
| self, | ||||||
| # TODO: TypeVarTuple | ||||||
| async_fn: Callable[..., Awaitable[object]], | ||||||
| *args: object, | ||||||
| async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]], | ||||||
| *args: Unpack[PosArgT], | ||||||
| name: object = None, | ||||||
| context: contextvars.Context | None = None, | ||||||
| ) -> Task: | ||||||
|
|
@@ -1878,10 +1888,9 @@ def spawn_system_task( # type: ignore[misc] | |||||
| ) | ||||||
|
|
||||||
| async def init( | ||||||
| # TODO: TypeVarTuple | ||||||
| self, | ||||||
| async_fn: Callable[..., Awaitable[object]], | ||||||
| args: tuple[object, ...], | ||||||
| async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]], | ||||||
| args: tuple[Unpack[PosArgT]], | ||||||
| ) -> None: | ||||||
| # run_sync_soon task runs here: | ||||||
| async with open_nursery() as run_sync_soon_nursery: | ||||||
|
|
@@ -2407,8 +2416,8 @@ def my_done_callback(run_outcome): | |||||
| # straight through. | ||||||
| def unrolled_run( | ||||||
| runner: Runner, | ||||||
| async_fn: Callable[..., object], | ||||||
| args: tuple[object, ...], | ||||||
| async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]], | ||||||
| args: tuple[Unpack[PosArgT]], | ||||||
| host_uses_signal_set_wakeup_fd: bool = False, | ||||||
| ) -> Generator[float, EventResult, None]: | ||||||
| locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.