New signal-cli-rest-api version
Upgrading to a new signal-cli-rest-api version means regenerating the generated models and fixing whatever changed.
The version signalbot currently targets is in MIN_SIGNAL_CLI_REST_API_VERSION.
Each signalbot release targets one schema version and refuses to start against an older
signal-cli-rest-api version.
Generated models¶
The models in
src/signalbot/_generated/
are generated with datamodel-codegen from upstream
signal-cli-rest-api's own swagger schema, stored
in
src/signalbot/_generated/json_schema/signal-cli-rest-api.json.
Don't hand-add fields or models to _generated: if a field or endpoint is missing, it is missing
upstream. Open an issue or a PR against that repo, and once it is released follow the steps below
to pull the updated schema in.
Steps¶
-
Update the schema and regenerate the models, following
src/signalbot/_generated/README.md. Fields that need renaming go in thealiases.jsonandserialization-aliases.jsonfiles next to the schema. -
Review the generated diff. Look for new, removed or renamed models and fields, fields that became required, and changed response types.
-
Fix the code that uses the changed models. Run
uv run ty checkto find the breakages in the domain packages,_client/and_actions/. If a public type now exposes a new generated type, wrap it as described in Wrapped types. New endpoints or message types are better added in their own PR, following New incoming messages or New outgoing messages. -
Bump
MIN_SIGNAL_CLI_REST_API_VERSIONinbot.pyto the new version. -
Update the mocks. The chat testing mocks in
src/signalbot/test_utils/chat_testing.pyreport a signal-cli-rest-api version (AboutMock) and build API responses, so update the version and any response whose shape changed. Do the same for the fixtures and raw JSON intests/unit, e.g. a new required field infake_groupintests/unit/conftest.py.
Wrapped types¶
No signalbot._generated type may appear on the public API, directly or nested inside a field —
tests/unit/test_public_api_surface.py
enforces this. Each domain package wraps the generated types it exposes:
- If nothing about a generated type needs to change, it still gets a thin domain subclass with a
docstring —
class GroupInfo(GeneratedGroupInfo): """..."""— even with zero added fields. The generated tree is produced from a bare JSON schema and carries no docstrings of its own, so this subclass is the only place that documentation exists, and it's what gets rendered under Reference. - Real fields or methods are added only when the domain type needs state or behavior the wire
format doesn't have — pattern:
Attachment.base64_contentinsrc/signalbot/attachments/attachment.py. - If a container field then needs to point at one of these wrapped types instead of the generated
type it replaces, that's a genuine override of the generated base class. pyright flags this as
reportIncompatibleVariableOverride(and ty asinvalid-mutable-override, orinvalid-attribute-overrideforlist[...]fields) because narrowing a mutable attribute's type in a subclass is unsound in general — but it's sound here: every wrapped type is a strict superset of the generated type it replaces (same fields, same validation, plus extra), and pydantic validates the value against the narrower type on construction, so nothing bypasses it. Annotate the field with a bare# pyright: ignore[reportIncompatibleVariableOverride]followed by the matching# ty: ignore[...]— the rationale above is why. SeeGroupEntry.permissionsinsrc/signalbot/groups/group_entry.pyorQuote.attachmentsinsrc/signalbot/messages/data_message_content.pyfor the pattern.