Skip to content

← Structural Typing and the Hard Parts step 15 of 24

Medium Framework

Unpack[TypedDict]: typing **kwargs precisely

**kwargs: Any in a thin wrapper is the second-largest Any-hole in most codebases, after Callable[..., Any]. Every config key passing through it is unchecked, and — the part that actually costs money — a typo becomes a silent no-op. timeoutt=30 is not an error; it lands in the dict, nothing reads it, and the connection keeps the default timeout forever.

PEP 692 closes it:

class ConnOpts(TypedDict):
    port: int
    timeout: NotRequired[float]
    tls: NotRequired[bool]

def open_connection(host: str, **kwargs: Unpack[ConnOpts]) -> Connection: ...

Now three separate things are checked:

  • a missing required key is call-arg
  • a wrong-typed value is arg-type
  • an unknown keyword is rejected outright

and inside the body kwargs has type ConnOpts, so kwargs["timeoutt"] is a typeddict-item error rather than a KeyError at 3am.

Your task

The starter runs, passes mypy --strict, and is wrong. It has exactly one bug, two lines from the top of open_connection, and **kwargs: Any is why nothing caught it.

Replace the annotation with Unpack[ConnOpts] and fix the bug. Defaults: timeout is 5.0, tls is False.

def solve(host: str, port: int, timeout: float) -> list[str]:

returns three descriptions, in order: port only; port and timeout; port, timeout and tls=True. Connection.describe() renders f"{scheme}://{host}:{port}?timeout={timeout:.2f}" where the scheme is "tls" or "tcp".

A live gap

PEP 692 covers a function definition. It does not cover a callable type: you cannot write Callable[[str, Unpack[ConnOpts]], Connection]. PEP 821 proposes that and is still Draft. Until it lands, a wrapper you store in a registry still needs a callback protocol with a __call__ whose **kwargs is annotated Unpack[ConnOpts].