Skip to main content
📦
PEP 692 · Speedrun · Python 3.12 (2023)

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.

Before — Python ≤ 3.11, **kwargs: Any (or lying)
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.
After — Python 3.12+, TypedDict + Unpack
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"))
Advanced typing & Pydantic → Senior track

Or speedrun another PEP

PEP 572Assignment as an expression — 3 lines become 1.
PEP 572 in 90 seconds — the walrus that ate your for-loop
PEP 484Python stays dynamic — but you can opt into typing.
PEP 484 in 90 seconds — type hints, without TypeScript-envy
PEP 622Switch statements are a 5% feature. Pattern matching is the 95%.
PEP 622 in 90 seconds — match / case isn't just a switch