Method contract
The registry (openremap/api/registry.py) is ~140 lines and has four public
functions. Everything else in the API layer is handlers.
register
from openremap.api.registry import register
@register(
"identify",
kind="command",
description="Identify an ECU binary — manufacturer, family, confidence, VIN.",
input_schema={
"path": {"type": "str", "required": True},
"include_vin": {"type": "bool", "default": False},
},
)
def identify_cmd(params: dict) -> dict:
...
| Argument | Meaning |
|---|---|
name |
the string callers dispatch on; registering a duplicate replaces it |
kind |
"command" (default), "composite" or "workflow" |
description |
one line, shown by list_methods and by the TUI's HELP screen |
input_schema |
parameter contract — see below |
steps |
optional list of step names, for composites; surfaced as metadata only |
A handler takes one positional dict and returns a dict. It is not called
with keyword arguments, and it does not receive self, a context, or a session.
That is what makes the same handler usable in-process and over a pipe.
list_methods
import openremap.api as api
for m in api.list_methods(kind="composite"):
print(m["name"], len(m["input_schema"]), "params")
Returns metadata only — name, kind, description, input_schema, steps —
sorted by name. The handler is never included, so the result is JSON-safe and can
be published. This is what the TUI's HELP page renders, which is why a new method
appears in the app without anyone editing a list.
dispatch and call
api.dispatch("identify", {"path": "a.bin"}) # same thing
api.call("identify", {"path": "a.bin"}) # in-process convenience wrapper
Order of operations inside dispatch:
- method lookup — unknown name raises
UnknownMethodError(-32601); - defaults are merged in from the schema, so a handler never sees a missing optional parameter;
- every declared parameter is type-checked and range-checked
→
InvalidParamsError(-32602); - the handler runs;
- the return value passes through
to_json_safe().
Note the consequence: validation happens before your code, so a handler can assume its parameters are the declared types.
Input schema spec
An entry is {"type": ..., "required": ..., "default": ..., "min": ..., "max": ...}.
| Key | Type | Notes |
|---|---|---|
type |
"str" | "int" | "float" | "bool" | "bytes" | "dict" | "list" |
checked with an exact predicate |
required |
bool |
missing and no default → -32602 |
default |
any | merged in before validation |
min / max |
number | numeric bounds, applied to int/float |
Validation details that matter in practice:
boolis rejected whereintis expected — Python'sTrue == 1does not let a boolean through a numeric parameter;listalso accepts tuples, so callers may pass either;- an undeclared parameter is not rejected. Handlers take a dict; extra keys are simply ignored. If you need strictness, validate inside the handler.
Serialisation
from openremap.api.serialize import to_json_safe, SerializationError
One boundary, one conversion. Enums become their value, bytes become
base64-capable structure, dataclasses become dicts, tuples become lists, paths
become strings. SerializationError is raised only for something genuinely
unrepresentable — which is a bug in a handler, not a caller's fault.
Because of this, the in-process result and the RPC result are identical. That is not a convention, it is asserted by the tests.
Errors → JSON-RPC codes
| Exception | code |
When |
|---|---|---|
UnknownMethodError |
-32601 |
no such method name |
InvalidParamsError |
-32602 |
missing or wrong-typed parameter |
GuardError |
-32603 |
the domain refused, unexpectedly — normally a refusal is data |
InternalError |
-32603 |
a bug |
| (parse failure in transport) | -32700 |
the line was not JSON |
ApiError is the base class; each carries .code and a message. Typical messages
are literal about the fix:
-32601 Unknown method: 'no_such_method'
-32602 identify: missing required param 'path'
-32700 Parse error
The three kinds, precisely
| Kind | Where it lives | Definition |
|---|---|---|
command |
api/commands/ |
one cohesive operation; path I/O + exception translation at most |
composite |
api/composite/ |
several engine calls that must produce one coherent report; a refusal is returned, not raised |
workflow |
api/workflows/*.toml |
a linear list of calls with data plumbing; loaded at import |
If a pipeline needs branching or a loop, it is a composite (code), not a workflow — the workflow format deliberately has no control flow.