We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 13 of 24
TypedDict: Required, NotRequired and the ReadOnly unlock
TypedDict is how you type the JSON crossing every service boundary. Get
requiredness wrong and you either ship a KeyError or you defend with
.get() on keys that are always present, which is the same thing as having
no types.
Requiredness
Per-item Required[...] / NotRequired[...] (3.11) is strictly better than
the class-level total=False: it is local (you read it on the line that
declares the item) and composable (a class can mix both). Prefer it.
Why plain TypedDicts barely compose
Every item of an ordinary TypedDict is mutable, and a mutable item is invariant — the exact same rule as protocol attributes. So a “view” type declared
class EventLike(TypedDict):
name: str
seq: int
rejects a concrete event whose name is Literal["ping"], because
Literal["ping"] is a subtype of str, not str itself. In practice
teams gave up and typed the seam dict[str, Any].
ReadOnly (3.13)
ReadOnly[...] marks an item as not writable through this type, and a
read-only item is covariant. Two consequences, both exercised here:
-
a source may supply a narrower value type (
Literal["ping"]forReadOnly[str]); -
a source may supply a Required item where the view asks only for
NotRequired— a required key is a perfectly good non-required one, as long as nobody candelit through the view.
Do not over-read the second point: mypy 2.3 still requires the source to
declare the key. A view item that the source omits entirely is rejected
regardless of ReadOnly.
Your task
Rewrite EventLike as a read-only view so that all three concrete shapes
are assignable to list[EventLike]:
-
ClickEvent—name: str,seq: int, plusx/y, and aNotRequired[str]detail it never sets -
ErrorEvent—detail: strrequired, plus aseveritythe view never mentions -
PingEvent—name: Literal["ping"], and aNotRequired[Literal["pong"]]detail
summarise(event: EventLike) -> str renders
f"{event['name']}#{event['seq']}({event.get('detail', '-')})".
def solve(seqs: list[int], details: list[str]) -> list[str]:
For each (seq, detail) pair build one of each event (click has no detail;
error’s detail is the input; ping’s detail is "pong"), put them in
list[EventLike], and summarise each in that order.
Two things the checker will not tell you
Inside summarise, assigning to event["name"] is a static error — that is
the guarantee ReadOnly buys, and it is why the view is safe to hand out.
And a verified divergence worth carrying: mypy --strict does not flag
reading a NotRequired key with []. Pyright reports
reportTypedDictNotRequiredAccess at basic, standard and strict. If mypy
is your only checker, event["detail"] on a not-required item is a
KeyError waiting to happen; use .get().
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.