Files
ruff/crates/ty_python_semantic/resources/mdtest/async.md
Douglas Creager 2b949b3e67 Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: (67 commits)
  Move `Token`, `TokenKind` and `Tokens` to `ruff-python-ast` (#21760)
  [ty] Don't confuse multiple occurrences of `typing.Self` when binding bound methods (#21754)
  Use our org-wide Renovate preset (#21759)
  Delete `my-script.py` (#21751)
  [ty] Move `all_members`, and related types/routines, out of `ide_support.rs` (#21695)
  [ty] Fix find-references for import aliases (#21736)
  [ty] add tests for workspaces (#21741)
  [ty] Stop testing the (brittle) constraint set display implementation (#21743)
  [ty] Use generator over list comprehension to avoid cast (#21748)
  [ty] Add a diagnostic for prohibited `NamedTuple` attribute overrides (#21717)
  [ty] Fix subtyping with `type[T]` and unions (#21740)
  Use `npm ci --ignore-scripts` everywhere (#21742)
  [`flake8-simplify`] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (`SIM222`, `SIM223`) (#21479)
  [`flake8-use-pathlib`] Mark fixes unsafe for return type changes (`PTH104`, `PTH105`, `PTH109`, `PTH115`) (#21440)
  [ty] Fix auto-import code action to handle pre-existing import
  Enable PEP 740 attestations when publishing to PyPI (#21735)
  [ty] Fix find references for type defined in stub (#21732)
  Use OIDC instead of codspeed token (#21719)
  [ty] Exclude `typing_extensions` from completions unless it's really available
  [ty] Fix false positives for `class F(Generic[*Ts]): ...` (#21723)
  ...
2025-12-02 14:23:15 -05:00

2.2 KiB

async / await

Basic

async def retrieve() -> int:
    return 42

async def main():
    result = await retrieve()

    reveal_type(result)  # revealed: int

Generic async functions

from typing import TypeVar

T = TypeVar("T")

async def persist(x: T) -> T:
    return x

async def f(x: int):
    result = await persist(x)

    reveal_type(result)  # revealed: int

Use cases

Future

import asyncio
import concurrent.futures

def blocking_function() -> int:
    return 42

async def main():
    loop = asyncio.get_event_loop()
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, blocking_function)
        reveal_type(result)  # revealed: int

asyncio.Task

import asyncio

async def f() -> int:
    return 1

async def main():
    task = asyncio.create_task(f())

    result = await task

    reveal_type(result)  # revealed: int

asyncio.gather

import asyncio

async def task(name: str) -> int:
    return len(name)

async def main():
    (a, b) = await asyncio.gather(
        task("A"),
        task("B"),
    )

    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Under the hood

[environment]
python-version = "3.12"  # Use 3.12 to be able to use PEP 695 generics

Let's look at the example from the beginning again:

async def retrieve() -> int:
    return 42

When we look at the signature of this function, we see that it actually returns a CoroutineType:

reveal_type(retrieve)  # revealed: def retrieve() -> CoroutineType[Any, Any, int]

The expression await retrieve() desugars into a call to the __await__ dunder method on the CoroutineType object, followed by a yield from. Let's first see the return type of __await__:

reveal_type(retrieve().__await__())  # revealed: Generator[Any, None, int]

We can see that this returns a Generator that yields Any, and eventually returns int. For the final type of the await expression, we retrieve that third argument of the Generator type:

from typing import Generator

def _():
    result = yield from retrieve().__await__()
    reveal_type(result)  # revealed: int