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.

  1. 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): a native field (labels, state, …), author (handle, native id or address) or grade. A pattern without wildcards also matches the conversations under it, so github:example/app matches github:example/app#12.

  2. Thread continuity: threads maps a thread root, a replied-to message id or a conversation reference to the target it already belongs to.

  3. Metadata rules: callables message -> target | None, tried in order (metadata_rule() builds one from field=glob conditions).

  4. The classifier: one optional callable, last, for what rules cannot see. It returns a target, (target, reason), or None.

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

Module Attributes

RULES

The order the chain runs in.

MESSAGE_FIELDS

Condition fields every message has, whatever its channel's native fields.

Functions

binding_matches(pattern, message)

The reason pattern matches message, or None.

check_binding(pattern, *[, registry])

What would make a binding never match, found when bindings are loaded instead of by messages quietly going unrouted.

metadata_rule(target, *[, name])

A rule sending messages to target when every field=glob condition holds.

route(message, *[, bindings, threads, ...])

Run the chain (bindings, thread continuity, metadata rules, classifier) and return the first decision, or None.

Classes

RouteDecision(*, target, rule, reason)

Where a message goes, which rule decided, and why.

correspond.routing.MESSAGE_FIELDS = ('author', 'grade')#

Condition fields every message has, whatever its channel’s native fields.

correspond.routing.RULES = ('binding', 'thread', 'metadata', 'classifier')#

The order the chain runs in.

class correspond.routing.RouteDecision(*, target, rule, reason)[source]#

Bases: object

Where a message goes, which rule decided, and why.

to_dict()[source]#

JSON-ready.

Return type:

dict

correspond.routing.binding_matches(pattern, message)[source]#

The reason pattern matches message, or None.

Return type:

str | None

correspond.routing.check_binding(pattern, *, registry=None)[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, a ref that is not in the channel’s canonical form (e.g. GitHub references are lower-cased by parse_ref(), so binding_matches, which compares the pattern literally, never matches a differently-cased pattern against it), and a condition on a field the channel’s messages never carry (its native_fields, plus author and grade). A channel written as a wildcard, or one that does not declare its fields (native_fields is None), is not checked for fields. A ref with wildcards, or one the channel’s own parser rejects outright, is not checked for canonical form – a malformed ref is a different problem than a mis-cased one.

>>> 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'
>>> check_binding("github:Example/App", registry={"github": GitHub()})
["github:Example/App is not in canonical form: github references are normalised to 'github:example/app'; binding_matches compares refs literally, so this pattern will not match github:example/app"]
Return type:

list[str]

correspond.routing.metadata_rule(target, *, name=None, **conditions)[source]#

A rule sending messages to target when every field=glob condition 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)
Return type:

Callable[[Message], str | None]

correspond.routing.route(message, *, bindings=None, threads=None, rules=(), classifier=None)[source]#

Run the chain (bindings, thread continuity, metadata rules, classifier) and return the first decision, or None.

Return type:

RouteDecision | None