Event Patterns
The Pattern class provides a composable algebra for matching events. Patterns
are used everywhere in PyRapide: in @when handlers, connections, and constraints. They range from simple name matching to complex
temporal and causal compositions.
Basic Patterns
The three foundational patterns match events by name, source, or payload content:
Pattern.match(name)
Matches events whose name equals or starts with the given string.
from pyrapide import Pattern
# Exact match
p1 = Pattern.match("user.login")
# Prefix match (matches "api.request.get", "api.request.post", etc.)
p2 = Pattern.match("api.request")
Pattern.source(name)
Matches events from a specific source.
# Only events from the worker process
p = Pattern.source("worker-1")
Pattern.payload(key, value)
Matches events where a specific payload key has a specific value.
# Events with status code 500
p = Pattern.payload("status_code", 500)
Composite Operators
Patterns compose through operators that build complex event specifications from simpler ones. Here is the full operator table:
| Operator | Syntax | Meaning |
|---|---|---|
| Sequence | A >> B | A must occur before B (causally ordered) |
| Join | A & B | Both A and B must occur (in any order) |
| Independence | A | B | A and B must occur independently (no causal link) |
| Disjunction | A.or_(B) | Either A or B (or both) must match |
| Guard | A.where(fn) | A must match and the predicate function must return True |
| Timed | A.timed(max_dt) | A must match within the given time window |
| Repeat | A.repeat(n) | A must match exactly n times |
Sequence (>>)
The sequence operator requires that the left pattern matches before the right pattern in causal order. This is the most common composition for modeling workflows.
# Request must be followed by a response
workflow = Pattern.match("api.request") >> Pattern.match("api.response")
# Three-step pipeline
pipeline = (
Pattern.match("ingest.start")
>> Pattern.match("transform.complete")
>> Pattern.match("load.complete")
)
Join (&)
The join operator requires that both patterns match, in any order. It models synchronization points where multiple prerequisites must be satisfied.
# Both the database and cache must respond before proceeding
ready = Pattern.match("db.ready") & Pattern.match("cache.ready")
# Use in a sequence: wait for both, then proceed
startup = ready >> Pattern.match("system.serve")
Independence (|)
The independence operator asserts that two events occurred without any causal relationship. This is a strong constraint that verifies genuine concurrency.
# These two events must be causally independent
concurrent = Pattern.match("sensor.a.reading") | Pattern.match("sensor.b.reading")
|) is not the same as disjunction (.or_).
Independence requires both events to exist but be causally unrelated. Disjunction requires
at least one to match.Disjunction (.or_)
The disjunction operator matches if either (or both) patterns match. Use it for branching logic.
# Match either a success or a failure
outcome = Pattern.match("task.success").or_(Pattern.match("task.failure"))
# Route based on whichever event arrives
handler = Pattern.match("request") >> outcome
Guard (.where)
The guard operator adds an arbitrary predicate that must return True for the pattern
to match. This allows payload inspection, threshold checks, and custom logic.
# Only match high-priority errors
critical = Pattern.match("system.error").where(
lambda e: e.payload.get("severity") == "critical"
)
# Only match requests with large payloads
large_request = Pattern.match("api.request").where(
lambda e: len(e.payload.get("body", "")) > 10_000
)
Timed (.timed)
The timed modifier constrains matching to a time window. If the pattern does not match within the specified duration, it fails.
from datetime import timedelta
# Request must be followed by response within 5 seconds
sla = (
Pattern.match("api.request")
>> Pattern.match("api.response").timed(max_dt=timedelta(seconds=5))
)
Repeat (.repeat)
The repeat modifier requires the pattern to match a specific number of times. This is useful for detecting repeated events like retries or bursts.
# Three consecutive failures
triple_fault = Pattern.match("system.error").repeat(3)
# Detect retry storms: 5 retries followed by a circuit-breaker trip
retry_storm = (
Pattern.match("http.retry").repeat(5)
>> Pattern.match("circuit_breaker.open")
)
Combining Operators
The real power of patterns emerges when you combine operators. Because every operator returns a new Pattern, compositions are fully nestable.
1# Complex monitoring pattern:
2# 1. User logs in
3# 2. Then, concurrently: profile loads AND preferences load
4# 3. Then dashboard renders within 2 seconds
5user_flow = (
6 Pattern.match("auth.login")
7 >> (Pattern.match("profile.loaded") & Pattern.match("prefs.loaded"))
8 >> Pattern.match("dashboard.render").timed(max_dt=timedelta(seconds=2))
9)
10
11# Alert pattern: 3 errors from any source, unless a recovery occurs
12alert = (
13 Pattern.match("error").repeat(3)
14 .where(lambda e: e.payload.get("recoverable") is False)
15)
Pattern.match(), verify it
works, then layer on guards, sequences, and temporal constraints. Each operator is
independently testable.Patterns become even more powerful when used inside Constraints for runtime monitoring and invariant checking.