We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Pattern Matching, f-Strings and Newer Syntax step 12 of 23
Build an Event Router with Guards
A monitoring service receives events as dictionaries from web servers, timers and a sign-up form. Turn each one into a dataclass, then give it a label for the alert screen.
solve([
{"kind": "http", "status": 503, "path": "/x"},
{"kind": "timer", "seconds": 61},
{"kind": "nope"},
])
# ["alert:/x", "slow-timer:61", "invalid"]
You will use mapping patterns to read the dictionaries and class patterns to route the dataclasses. The new part is how a guard changes which case runs.
A guard runs only after its pattern matches
match event:
case Http(status=status, path=path) if status >= 500:
return f"alert:{path}"
case Http(path=path) if path.startswith("/admin"):
return "forbidden"
Python checks the pattern first. It runs the guard only when the pattern
matches, so the guard can use status and path. If the guard is false,
Python moves on to the next case. So an Http event with status 200 fails
the first guard and can still reach the /admin case. If the guard raises
an exception, match does not catch it, and the exception goes up to the
caller.
Combine | with as
(404 | 410) as status matches either number and stores the one that matched
in status. It fits inside a class pattern:
match event:
case Http(status=(404 | 410) as status, path=path):
return f"gone:{status}:{path}"
A guard applies to the whole case. If the left side of a | matches and the
guard is false, Python does not try the right side. It moves on to the next
case.
What to write
parse(raw) uses one mapping pattern per kind, with int() and str()
inside it to check the types:
| Required entries | Returns |
|---|---|
"kind": "http", an integer "status", a string "path" |
Http(status, path) |
"kind": "timer", an integer "seconds" |
Timer(seconds) |
"kind": "user", a string "name", an integer "age" |
User(name, age) |
Extra keys are fine. For a missing entry, a wrong type or an unknown kind,
raise ValueError(f"unparseable event: {raw!r}"). parse checks types, not
ranges, so a negative age still makes a User.
route(event) returns the label from the first row that fits:
| Event | Result |
|---|---|
Http with status 500 or more |
f"alert:{path}" |
Http with status 404 or 410 |
f"gone:{status}:{path}" |
Http whose path starts with /admin |
"forbidden" |
Any other Http |
f"ok:{path}" |
Timer with more than 60 seconds |
f"slow-timer:{seconds}" |
Any other Timer |
"timer" |
User aged 18 or more |
f"adult:{name}" |
Any other User |
f"minor:{name}" |
The admin test is exactly path.startswith("/admin"), so /administrator
matches and /Admin does not. The status rows come first, even on an admin
path. On /admin, status 503 gives "alert:/admin", 404 gives
"gone:404:/admin", and 200 gives "forbidden".
solve(events) parses and routes each dictionary, in order. When parse
raises ValueError, add "invalid" for that event and go on to the next.
An empty list gives []. Do not change the input dictionaries.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.