Handlers and the context
module.py holds one Module subclass with three kinds of handler, all receiving an injected ModuleContext.
Handler kinds#
| Decorator | Registers | Notes |
|---|---|---|
@route.get("/path") | An HTTP route under /api/v1/modules/{id}/* | route.get/post/put/patch/delete; FastAPI semantics for path parameters and Pydantic bodies; name= sets the OpenAPI operation id, response_model= the shape. |
@on_event("topic") | A bus subscription | Dotted topics; wildcards such as log.* match. Declare it in consumes:. |
@job(...) | Makes the handler a persisted job | Stack it under a route or event handler. A route answers with {"job_id": …} immediately. |
@job parameter | Default | Effect |
|---|---|---|
progress | False | Enables ctx.progress.update(...) streaming. |
title / subtitle | derived | Toast and drawer text; a (ctx, payload) -> str callable works in-process. |
priority | 0 | Higher is scheduled sooner. |
cancellable | True | Shows the cancel affordance. |
result_url | None | URL template for the toast's Open action. |
Handlers may be async def or plain def; sync handlers are wrapped so they cannot block the event loop (routes ride FastAPI's thread pool, event and job handlers run through asyncio.to_thread).
Module context#
Every member is a Protocol — depend on the contract, not an implementation.
| Member | Purpose |
|---|---|
ctx.log_id · ctx.module_id · ctx.user_id | Scope of this invocation; log_id is empty for global routes. |
ctx.event_log · ctx.object_log | Lazy views of the log; the object-centric one is bound only for object_centric modules. |
ctx.open_event_log(log_id, filters) | A second, ownership-checked log — for comparisons. |
ctx.cache | Per-(log_id, module_id) results: get, set, exists, delete. |
ctx.config | The user's configuration, validated against your schema; read it defensively (ctx.config.get(key, default)). |
ctx.progress | update(fraction, stage), update(current=…, total=…, stage=…), or a running counter. |
ctx.bus · ctx.registry | Emit topics; call other modules' capabilities. |
ctx.logger | Structured logging bound with module and log ids. print() is dropped. |
ctx.workdir | Scratch space, removed after the invocation. |
ctx.run_in_process(fn, …) | Offload CPU-bound work to the platform's process pool. |
ctx.is_cancelled() · await ctx.check_cancelled() | Cooperative cancellation. |
for i, chunk in enumerate(chunks):
await ctx.check_cancelled() # raises mate.sdk.Cancelled
process(chunk)
await ctx.progress.update(current=i + 1, total=len(chunks), stage="slices")
Cancelled derives from BaseException on purpose, so a broad except Exception cannot swallow a cancellation request. Progress is optional but a job that runs for minutes should emit something — the UI flags a silent job as stalled after three minutes.
Configuration#
config_schema is JSON-Schema-flavoured with a ui hint the form renderer understands: enum becomes a select, bounded numbers a slider, booleans a switch, arrays repeating inputs, and ui: { widget: textarea } a text area. Values are per user and per module, so a fresh account has none — always pass defaults to ctx.config.get. Configuration changes do not invalidate caches automatically: delete the keys whose values they affect.
@route.put("/config-hook")
async def on_config_changed(self, ctx: ModuleContext) -> dict:
await ctx.cache.delete("ranking")
return {"invalidated": ["ranking"]}