Typing async/await

Mypy supports the ability to type coroutines that use the async/awaitsyntax introduced in Python 3.5. For more information regarding coroutines andthis new syntax, see PEP 492.

Functions defined using async def are typed just like normal functions.The return type annotation should be the same as the type of the value youexpect to get back when await-ing the coroutine.

  1. import asyncio
  2.  
  3. async def format_string(tag: str, count: int) -> str:
  4. return 'T-minus {} ({})'.format(count, tag)
  5.  
  6. async def countdown_1(tag: str, count: int) -> str:
  7. while count > 0:
  8. my_str = await format_string(tag, count) # has type 'str'
  9. print(my_str)
  10. await asyncio.sleep(0.1)
  11. count -= 1
  12. return "Blastoff!"
  13.  
  14. loop = asyncio.get_event_loop()
  15. loop.run_until_complete(countdown_1("Millennium Falcon", 5))
  16. loop.close()

The result of calling an async def function without awaiting will be avalue of type Coroutine[Any, Any, T], which is a subtype ofAwaitable[T]:

  1. my_coroutine = countdown_1("Millennium Falcon", 5)
  2. reveal_type(my_coroutine) # has type 'Coroutine[Any, Any, str]'

Note

reveal_type() displays the inferred static type ofan expression.

If you want to use coroutines in Python 3.4, which does not supportthe async def syntax, you can instead use the @asyncio.coroutinedecorator to convert a generator into a coroutine.

Note that we set the YieldType of the generator to be Any in thefollowing example. This is because the exact yield type is an implementationdetail of the coroutine runner (e.g. the asyncio event loop) and yourcoroutine shouldn’t have to know or care about what precisely that type is.

  1. from typing import Any, Generator
  2. import asyncio
  3.  
  4. @asyncio.coroutine
  5. def countdown_2(tag: str, count: int) -> Generator[Any, None, str]:
  6. while count > 0:
  7. print('T-minus {} ({})'.format(count, tag))
  8. yield from asyncio.sleep(0.1)
  9. count -= 1
  10. return "Blastoff!"
  11.  
  12. loop = asyncio.get_event_loop()
  13. loop.run_until_complete(countdown_2("USS Enterprise", 5))
  14. loop.close()

As before, the result of calling a generator decorated with @asyncio.coroutinewill be a value of type Awaitable[T].

Note

At runtime, you are allowed to add the @asyncio.coroutine decorator toboth functions and generators. This is useful when you want to mark awork-in-progress function as a coroutine, but have not yet added yield oryield from statements:

  1. import asyncio
  2.  
  3. @asyncio.coroutine
  4. def serialize(obj: object) -> str:
  5. # todo: add yield/yield from to turn this into a generator
  6. return "placeholder"

However, mypy currently does not support converting functions intocoroutines. Support for this feature will be added in a future version, butfor now, you can manually force the function to be a generator by doingsomething like this:

  1. from typing import Generator
  2. import asyncio
  3.  
  4. @asyncio.coroutine
  5. def serialize(obj: object) -> Generator[None, None, str]:
  6. # todo: add yield/yield from to turn this into a generator
  7. if False:
  8. yield
  9. return "placeholder"

You may also choose to create a subclass of Awaitable instead:

  1. from typing import Any, Awaitable, Generator
  2. import asyncio
  3.  
  4. class MyAwaitable(Awaitable[str]):
  5. def __init__(self, tag: str, count: int) -> None:
  6. self.tag = tag
  7. self.count = count
  8.  
  9. def __await__(self) -> Generator[Any, None, str]:
  10. for i in range(n, 0, -1):
  11. print('T-minus {} ({})'.format(i, tag))
  12. yield from asyncio.sleep(0.1)
  13. return "Blastoff!"
  14.  
  15. def countdown_3(tag: str, count: int) -> Awaitable[str]:
  16. return MyAwaitable(tag, count)
  17.  
  18. loop = asyncio.get_event_loop()
  19. loop.run_until_complete(countdown_3("Heart of Gold", 5))
  20. loop.close()

To create an iterable coroutine, subclass AsyncIterator:

  1. from typing import Optional, AsyncIterator
  2. import asyncio
  3.  
  4. class arange(AsyncIterator[int]):
  5. def __init__(self, start: int, stop: int, step: int) -> None:
  6. self.start = start
  7. self.stop = stop
  8. self.step = step
  9. self.count = start - step
  10.  
  11. def __aiter__(self) -> AsyncIterator[int]:
  12. return self
  13.  
  14. async def __anext__(self) -> int:
  15. self.count += self.step
  16. if self.count == self.stop:
  17. raise StopAsyncIteration
  18. else:
  19. return self.count
  20.  
  21. async def countdown_4(tag: str, n: int) -> str:
  22. async for i in arange(n, 0, -1):
  23. print('T-minus {} ({})'.format(i, tag))
  24. await asyncio.sleep(0.1)
  25. return "Blastoff!"
  26.  
  27. loop = asyncio.get_event_loop()
  28. loop.run_until_complete(countdown_4("Serenity", 5))
  29. loop.close()

For a more concrete example, the mypy repo has a toy webcrawler thatdemonstrates how to work with coroutines. One versionuses async/awaitand oneuses yield from.