correspond.routing
Deciding what a message is about: a transparent rule chain.
The rules belong to the caller; correspond runs them in a fixed order and reports which rule decided and why.
Bindings,
pattern → target. A pattern is a conversation-reference glob (*and[…];?always starts the conditions), optionally followed by?field=glob&…conditions on the message (%-escapes decoded,+kept): anativefield (labels,state, …),author(handle, native id or address) orgrade. A pattern without wildcards also matches the conversations under it, sogithub:example/appmatchesgithub:example/app#12.Thread continuity:
threadsmaps a thread root, a replied-to message id or a conversation reference to the target it already belongs to.Metadata rules: callables
message -> target | None, tried in order (metadata_rule()builds one fromfield=globconditions).The classifier: one optional callable, last, for what rules cannot see. It returns a target,
(target, reason), orNone.
Nothing matched: route returns None and the message is unrouted. What that means
(an operator queue, a drop) is the caller’s decision. A condition on a field the channel’s
messages never carry (?label= where GitHub messages carry labels) never matches;
check_binding() reports that when bindings are loaded.
>>> from correspond.testing import demo_message
>>> message = demo_message(conversation="github:example/app#12", labels=["partner:ada"])
>>> route(message, bindings={"github:example/app?labels=partner:*": "subject:app"})
RouteDecision(target='subject:app', rule='binding', reason='github:example/app?labels=partner:* matched github:example/app#12')
>>> route(message, bindings={"github:example/other": "subject:other"}) is None
True
- correspond.routing.MESSAGE_FIELDS = ('author', 'grade')
Condition fields every message has, whatever its channel’s
nativefields.
- correspond.routing.RULES = ('binding', 'thread', 'metadata', 'classifier')
The order the chain runs in.
- class correspond.routing.RouteDecision(*, target: str, rule: str, reason: str)[source]
Where a message goes, which rule decided, and why.
- correspond.routing.binding_matches(pattern: str, message: Message) str | None[source]
The reason
patternmatchesmessage, orNone.
- correspond.routing.check_binding(pattern: str, *, registry: Mapping[str, Any] | None = None) list[str][source]
What would make a binding never match, found when bindings are loaded instead of by messages quietly going unrouted.
Reports a pattern without a channel, an unknown channel, and a condition on a field the channel’s messages never carry (its
native_fields, plusauthorandgrade). A channel written as a wildcard, or one that does not declare its fields (native_fieldsisNone), is not checked for fields.>>> from correspond.channels.github import GitHub >>> check_binding("github:example/app?labels=bug", registry={"github": GitHub()}) [] >>> check_binding("github:example/app?label=bug", registry={"github": GitHub()})[0].split(":")[0] 'the condition label=bug never matches'
- correspond.routing.metadata_rule(target: str, *, name: str | None = None, **conditions: str) Callable[[Message], str | None][source]
A rule sending messages to
targetwhen everyfield=globcondition holds.>>> from correspond.testing import demo_message >>> rule = metadata_rule("urgent-queue", labels="priority:high") >>> rule(demo_message(labels=["priority:high"])), rule(demo_message()) ('urgent-queue', None)
- correspond.routing.route(message: Message, *, bindings: Mapping[str, str] | Iterable[tuple[str, str]] | None = None, threads: Mapping[str, str] | None = None, rules: Iterable[Callable[[Message], str | None]] = (), classifier: Callable[[Message], str | tuple[str, str] | None] | None = None) RouteDecision | None[source]
Run the chain (bindings, thread continuity, metadata rules, classifier) and return the first decision, or
None.