Skip to content

← Interfaces with Protocols and ABCs step 11 of 26

Medium Primitives

Accept Three Event Types with a Read-Only TypedDict

Your app logs click, error and ping events, and each kind is a TypedDict. One function, summarise, formats any of them. It reads name, seq and detail, and never changes them. Its parameter type, EventLike, is too strict. Submit the starter, and mypy rejects two of the three events:

L48:43 [list-item]
List item 1 has incompatible type "ErrorEvent"; expected "EventLike"

L48:50 [list-item]
List item 2 has incompatible type "PingEvent"; expected "EventLike"

A writable key must match exactly

Every key in a TypedDict is writable unless you say otherwise. So code that receives an EventLike may assign event["name"] = "click". It may also delete event["detail"], because detail is NotRequired, as in the previous exercise. mypy accepts an event type only if both changes keep it valid:

  • PingEvent has name: Literal["ping"]. Writing "click" through EventLike would leave a PingEvent whose name is "click".
  • ErrorEvent has a required detail. Deleting it through EventLike would leave an ErrorEvent without one.
  • ClickEvent matches every key exactly, so it already fits. Its extra x and y keys do not matter.

You met this rule in the read-only protocol exercise earlier in this track. A writable member must match exactly, but a read-only member can accept a narrower type. TypedDict follows the same rule. ReadOnly marks a key that code cannot assign or delete through this type. A read-only str key accepts Literal["ping"]. A read-only NotRequired key also accepts a required one, because mypy rejects deleting a read-only key.

What to write

Import ReadOnly from typing, and change only EventLike:

  • name is a required, read-only str.
  • seq is a required, read-only int.
  • detail is a read-only str that may be missing.

Keep ClickEvent, ErrorEvent, PingEvent, summarise and solve as they are. The starter already returns the right strings, so strict mypy is what this exercise grades. It must accept the line events: list[EventLike] = [click, error, ping].

What the driver returns

For each pair from seqs and details, solve builds a click, an error and a ping event, in that order, and returns their summaries. Each summary is name#seq(detail). A missing detail shows as -, and an empty one stays empty. For seqs=[2] and details=[""], the result is ["click#2(-)", "error#2()", "ping#2(pong)"]. Empty lists give [], and lists of different lengths raise ValueError.

ReadOnly is a rule for the type checker only. At runtime each event is still a plain dict, and code that holds it under another type can change it.