Search code examples
pythonpylintmypy

Which python static checker can catch 'forgotten await' problems?


Code:

from typing import AsyncIterable

import asyncio


async def agen() -> AsyncIterable[str]:
    print('agen start')
    yield '1'
    yield '2'


async def agenmaker() -> AsyncIterable[str]:
    print('agenmaker start')
    return agen()


async def amain():
    print('amain')
    async for item in agen():
        pass
    async for item in await agenmaker():
        pass
    # Error:
    async for item in agenmaker():
        pass


def main():
    asyncio.get_event_loop().run_until_complete(amain())


if __name__ == '__main__':
    main()

As you can see, it is type-annotated, and contains an easy-to-miss error.

However, neither pylint nor mypy find that error.

Aside from unit tests, what options are there for catching such errors?


Solution

  • MyPy is perfectly capable of finding this issue. The problem is that unannotated functions are not inspected. Annotate the offending function as -> None and it is correctly inspected and rejected.

    # annotated with return type
    async def amain() -> None:
        print('amain')
        async for item in agen():
            pass
        async for item in await agenmaker():
            pass
        # Error:
        async for item in agenmaker(): # error: "Coroutine[Any, Any, AsyncIterable[str]]" has no attribute "__aiter__" (not async iterable)
    
            pass
    

    If you want to eliminate such issues slipping through, use the flags --disallow-untyped-defs or --check-untyped-defs.


    MyPy: Function signatures and dynamic vs static typing

    A function without type annotations is considered to be dynamically typed by mypy:

    def greeting(name):
        return 'Hello ' + name
    

    By default, mypy will not type check dynamically typed functions. This means that with a few exceptions, mypy will not report any errors with regular unannotated Python.