PEP 692 in 90 seconds — TypedDict for **kwargs
Type-checker-friendly **kwargs, finally.
Type-hinting **kwargs used to mean lying: ```python def build_query(**kwargs: Any) -> str: ... # Caller passes anything — type checker can't help. build_query(timeout=10) # OK build_query(timeoot=10) # Typo, silently accepted build_query(timeout="forever") # Wrong type, silently accepted ``` PEP 692 (Python 3.12, 2023) added `typing.Unpack` so you can describe **kwargs with a `TypedDict`. The type checker now treats kwargs the same way it treats explicit params.
Recipe: ```python from typing import TypedDict, Unpack class HttpParams(TypedDict, total=False): url: str timeout: int retries: int def get(**kwargs: Unpack[HttpParams]) -> str: ... ``` `Unpack[HttpParams]` says "kwargs has exactly these keys with these types — anything else is an error". `total=False` makes every field optional. The runtime behaviour is unchanged — kwargs is still just a dict — but mypy and Pyright now know what's in it.
from typing import Any
# All you could write — no per-key types
def get(**kwargs: Any) -> str:
return f"GET {kwargs.get('url')} (timeout={kwargs.get('timeout', 30)})"
# Type checker is blind:
get(url="https://x.com", timeout=10) # OK
get(url="https://x.com", timeoot=10) # Typo accepted
get(url="https://x.com", timeout="forever") # Wrong type accepted
# Workaround was to use an explicit params dict
# but that defeats the purpose of **kwargs ergonomics.from typing import TypedDict, Unpack
class HttpParams(TypedDict, total=False):
url: str
timeout: int
retries: int
def get(**kwargs: Unpack[HttpParams]) -> str:
return f"GET {kwargs.get('url')} (timeout={kwargs.get('timeout', 30)})"
get(url="https://x.com", timeout=10) # OK
# get(url="https://x.com", timeoot=10) # mypy error: extra key
# get(url="https://x.com", timeout="forever") # mypy error: wrong type
# Runtime is unchanged — kwargs is still a dict. Unpack is a
# typing-only hint that mypy + Pyright + Pydantic v2 understand.🎯 Predict the output
What does this print? `Unpack[HttpParams]` is purely a type annotation — runtime behaviour of **kwargs is unchanged.
from typing import TypedDict, Unpack
class HttpParams(TypedDict, total=False):
url: str
timeout: int
retries: int
def get(**kwargs: Unpack[HttpParams]) -> str:
return f"GET {kwargs.get('url')} (timeout={kwargs.get('timeout', 30)})"
print(get(url="https://api.example.com", timeout=10))
print(get(url="https://x.com"))