Bridge — API reference¶
Interface¶
BridgeChannel
¶
Bases: ABC
A swappable Bridge transport.
Subclasses declare their identity and config, know how to test themselves,
and implement the two transport verbs (:meth:drain inbound,
:meth:publish outbound). Register a subclass in
:mod:~mait_code.bridge.registry and it becomes selectable everywhere the
gate, form and hooks already handle — with no change to those callers.
Class attributes
type_id: Stable, lowercase identifier (the bridge-type value).
display_name: Human label for the channel selector.
hidden: True keeps the channel out of the user-facing picker while
still usable by id (the loopback test double sets this).
config_schema
abstractmethod
classmethod
¶
config_schema() -> Sequence[ConfigField]
Return the config inputs this channel needs, in display order.
from_config
abstractmethod
classmethod
¶
from_config(config: Mapping[str, str]) -> BridgeChannel
Build a channel from resolved config values.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If a required value is missing or malformed. Callers
turn this into a form error or a |
test_connection
abstractmethod
¶
test_connection() -> TestResult
Attempt a lightweight round-trip and report the outcome.
Must never raise: connection/credential failures come back as a
TestResult(ok=False, …) so the form can show them inline.
drain
abstractmethod
¶
drain(since: str | None) -> DrainResult
Fetch inbound captures after the since watermark.
| PARAMETER | DESCRIPTION |
|---|---|
since
|
The opaque watermark from the previous drain, or
TYPE:
|
publish
abstractmethod
¶
publish(message: OutboundMessage) -> None
Publish an outbound notification. (Reminders half — #78.)
ConfigField
dataclass
¶
ConfigField(
key: str,
label: str,
help: str = "",
kind: str = "str",
secret: bool = False,
required: bool = True,
placeholder: str = "",
)
One user-facing config input a channel needs, declared by the channel.
The Bridge settings form renders one labelled input per field returned by
:meth:BridgeChannel.config_schema, so the form is generic over channels
rather than hard-coded to any one transport.
| ATTRIBUTE | DESCRIPTION |
|---|---|
key |
Stable identifier; the storage key within the channel's config.
TYPE:
|
label |
Human label shown beside the input.
TYPE:
|
help |
One-line hint rendered under the input.
TYPE:
|
kind |
TYPE:
|
secret |
TYPE:
|
required |
TYPE:
|
placeholder |
Example value shown in the empty input.
TYPE:
|
TestResult
dataclass
¶
Outcome of :meth:BridgeChannel.test_connection — shown inline in the form.
| ATTRIBUTE | DESCRIPTION |
|---|---|
ok |
Whether the probe round-trip succeeded.
TYPE:
|
message |
Human-readable detail (the error, or a success note).
TYPE:
|
Capture
dataclass
¶
One inbound item drained from a channel, bound for the inbox.
| ATTRIBUTE | DESCRIPTION |
|---|---|
body |
The captured text, filed verbatim into
TYPE:
|
external_id |
The channel's own id for the source message, for tracing and de-duplication (empty when the transport has none).
TYPE:
|
DrainResult
dataclass
¶
DrainResult(
captures: list[Capture] = list(),
watermark: str | None = None,
)
What a single :meth:BridgeChannel.drain returned.
The watermark is opaque to the caller: each channel defines what it means (ntfy: the last message id; loopback: a running count). The drain service persists it verbatim per-machine and hands it back on the next drain, so re-drains are idempotent without the service knowing the transport.
| ATTRIBUTE | DESCRIPTION |
|---|---|
captures |
Inbound items, oldest first.
TYPE:
|
watermark |
Channel-defined resume token, or
TYPE:
|
OutboundMessage
dataclass
¶
OutboundMessage(
body: str,
title: str | None = None,
actions: tuple[Mapping[str, str], ...] = (),
)
A notification to publish outward (exercised by the reminders half, #78).
Defined here so the interface is complete and both channels implement it now;
the reminders publish path that fills in actions lands in a follow-up.
| ATTRIBUTE | DESCRIPTION |
|---|---|
body |
The notification text.
TYPE:
|
title |
Optional title/heading.
TYPE:
|
actions |
Transport-specific action descriptors (e.g. an ntfy "Done" button); empty for a plain notification.
TYPE:
|
Channels¶
NtfyChannel
¶
Bases: BridgeChannel
Publish/drain a private ntfy topic over HTTP.
test_connection
¶
test_connection() -> TestResult
Poll the topic with a tiny window to prove reachability + auth.
drain
¶
drain(since: str | None) -> DrainResult
Poll messages published since the watermark (a message id, or 'all').
publish
¶
publish(message: OutboundMessage) -> None
POST a notification to the notify topic.
Any control actions become ntfy action buttons that POST the control body back to the capture topic, where the next drain acts on it.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If no notify topic is configured — outbound needs one. |
LoopbackChannel
¶
Bases: BridgeChannel
An in-memory transport whose queue is seeded and inspected by tests.
Registry¶
CHANNELS
module-attribute
¶
CHANNELS: dict[str, type[BridgeChannel]] = {
(type_id): cls for cls in (NtfyChannel, LoopbackChannel)
}
get_channel_class
¶
get_channel_class(
type_id: str,
) -> type[BridgeChannel] | None
Return the channel class for type_id, or None if unregistered.
selectable_channels
¶
selectable_channels() -> list[type[BridgeChannel]]
Return the channels offered in the user-facing picker (hidden ones out).
Config & state¶
bridge_enabled
¶
Whether the Bridge is switched on. Off by default — the safety spine.
active_channel
¶
active_channel() -> BridgeChannel
Build the currently-selected channel from its stored config.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the type is unknown or its config is incomplete. |
build_channel
¶
build_channel(
type_id: str, values: dict[str, str]
) -> BridgeChannel
Construct a channel from values.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If type_id is unregistered or the config is incomplete. |
load_channel_config
¶
Return the stored config for one channel type ({} if none).
save_channel_config
¶
Persist config for one channel type, leaving other channels untouched.
get_watermark
¶
Return the last drain watermark for a channel on this machine.
set_watermark
¶
Record the drain watermark for a channel on this machine.
missing_required
¶
Return the labels of required config fields left blank.
config_problems
¶
Human-readable configuration problems for doctor (empty when healthy).
Only meaningful when the gate is on: a disabled Bridge needs no config.
Drain & publish service¶
run_drain
¶
run_drain() -> DrainOutcome
Drain the active channel into the inbox, best-effort.
Never raises: every failure mode comes back as a :class:DrainOutcome so a
session-start hook can swallow it to a log line and carry on.
drain_channel
¶
drain_channel(
channel: BridgeChannel, type_id: str
) -> tuple[int, int]
Drain one channel; file captures, act on control messages.
Returns (filed, dismissed). Idempotent across runs: the per-machine
watermark is read before and advanced after, so a re-drain from the same
position yields nothing new.
DrainOutcome
dataclass
¶
Result of a drain attempt.
| ATTRIBUTE | DESCRIPTION |
|---|---|
status |
TYPE:
|
count |
Number of captures filed into the inbox.
TYPE:
|
dismissed |
Number of reminders dismissed by control messages.
TYPE:
|
detail |
Human-readable context for the non-ok statuses.
TYPE:
|
publish_due_reminders
¶
publish_due_reminders() -> PublishOutcome
Publish due, not-yet-notified reminders outward, best-effort.
Each carries a "Done" action that round-trips a dismissal back through the
capture topic. Publishes each reminder once (stamping notified_at), so a
still-overdue reminder isn't re-sent every session. Never raises.
PublishOutcome
dataclass
¶
Result of an outbound reminder-publish attempt.
| ATTRIBUTE | DESCRIPTION |
|---|---|
status |
TYPE:
|
count |
Number of reminders published outward.
TYPE:
|
detail |
Human-readable context for the non-ok statuses.
TYPE:
|