On this page

Handlers and the context

module.py holds one Module subclass with three kinds of handler, all receiving an injected ModuleContext.

Handler kinds#

DecoratorRegistersNotes
@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 subscriptionDotted topics; wildcards such as log.* match. Declare it in consumes:.
@job(...)Makes the handler a persisted jobStack it under a route or event handler. A route answers with {"job_id": …} immediately.
@job parameterDefaultEffect
progressFalseEnables ctx.progress.update(...) streaming.
title / subtitlederivedToast and drawer text; a (ctx, payload) -> str callable works in-process.
priority0Higher is scheduled sooner.
cancellableTrueShows the cancel affordance.
result_urlNoneURL 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.

MemberPurpose
ctx.log_id · ctx.module_id · ctx.user_idScope of this invocation; log_id is empty for global routes.
ctx.event_log · ctx.object_logLazy 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.cachePer-(log_id, module_id) results: get, set, exists, delete.
ctx.configThe user's configuration, validated against your schema; read it defensively (ctx.config.get(key, default)).
ctx.progressupdate(fraction, stage), update(current=…, total=…, stage=…), or a running counter.
ctx.bus · ctx.registryEmit topics; call other modules' capabilities.
ctx.loggerStructured logging bound with module and log ids. print() is dropped.
ctx.workdirScratch 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.
Cancellation and progress in a long loop
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.

Invalidating on a configuration change
@route.put("/config-hook")
async def on_config_changed(self, ctx: ModuleContext) -> dict:
    await ctx.cache.delete("ranking")
    return {"invalidated": ["ranking"]}