Search code examples
pythonpython-typingmypy

Generate TypedDict from function's keyword arguments


foo.py:

kwargs = {"a": 1, "b": "c"}

def consume(*, a: int, b: str) -> None:
    pass

consume(**kwargs)

mypy foo.py:

error: Argument 1 to "consume" has incompatible type "**Dict[str, object]"; expected "int"
error: Argument 1 to "consume" has incompatible type "**Dict[str, object]"; expected "str"

This is because object is a supertype of int and str, and is therefore inferred. If I declare:

from typing import TypedDict

class KWArgs(TypedDict):
    a: int
    b: str

and then annotate kwargs as KWArgs, the mypy check passes. This achieves type safety, but requires me to duplicate the keyword argument names and types for consume in KWArgs. Is there a way to generate this TypedDict from the function signature at type checking time, such that I can minimize the duplication in maintenance?


Solution

  • This will be available in Python 3.12 via PEP 692:

    from typing import TypedDict, Unpack, Required, NotRequired
    
    class KWArgs(TypedDict):
        a: Required[int]
        b: NotRequired[str]
    
    def consume(**kwargs: Unpack[KWArgs]) -> None:
        a = kwargs["a"]
        b = kwargs.get("b", ...)
    
    consume()              # Not allowed.
    consume(a=1)           # Allowed.
    consume(a=1, b="abc")  # Allowed.