Declarations
Declare an application and register ordinary functions. The first argument is
an injected PluginContext; it is not part of the request sent over the bridge.
Plugin id and title are optional; actions and streams require both.
from dataclasses import dataclassfrom qgis_sdk import QgisSdkPlugin, describefrom qgis_sdk.core import PluginContext
@dataclassclass RunRequest: count: int label: str | None = None
reports = QgisSdkPlugin(id="example.reports")
@reports.action(id="run", title="Run")def run(ctx: PluginContext, request: RunRequest) -> int: return request.count
app = QgisSdkPlugin(id="example.tools", title="Example tools")app.include(reports)
print(describe(app).to_json()) # app.describe() uses the same implementationprint(describe(run).bindings) # run.describe() also worksapp.include(reports) snapshots the child’s actions, streams, surfaces and
providers into the parent. It creates no child runtime: the parent’s identity,
context, grants, lifecycle and authority apply to all included declarations.
The child must not have a context_factory. IDs are not prefixed. Duplicate
action/stream IDs or handler names, surface IDs, provider IDs, repeated
inclusion (including a shared descendant), and cycles are rejected without
partially changing the parent. Later additions to a child are not included in
an earlier snapshot; functions and their decorator metadata are shared objects.
No global current plugin or module scanning is used. Registration keeps each
function intact so it can also be called directly with a fake context.
Use @app.stream(id=..., title=..., events=...) for generator streams. Actions
and streams share an ID namespace. Provider algorithms and jobs retain their
own default ID and title rules.
Ids and titles default from the name
Section titled “Ids and titles default from the name”QgisSdkPlugin() derives its ID from the declaring module’s qualified name;
the title is derived from the last component (or the package component when
the last component is plugin). Explicit id= and title= override these
defaults independently. Pass an explicit ID in __main__ or when the module
name is unavailable. Moving or renaming a module changes the generated ID,
and multiple instances in one module get the same default ID. Use explicit,
stable IDs for published plugins and to distinguish same-module instances.
Actions and streams still require explicit IDs and titles.
What does describe_plugin do, and not do?
Section titled “What does describe_plugin do, and not do?”describe_plugin was removed with the class API; use the unified describe.
describe(app), app.describe(), describe(run) and run.describe() use the
same dispatcher. It also describes jobs, algorithms, providers, surfaces and UI
handlers. A plugin description exposes registered surfaces and bindings; an
action description exposes its stacked toolbar/menu/shortcut bindings and UI
wires. These are local metadata, not additions to the bridge JSON contract.
Wires execute on handlers declared on a Dialog or Dock; attaching @on
to an application action records its metadata but does not automatically
connect the action to a surface. Use a surface handler and call
self.ctx.actions.<id>(...) to dispatch an application action.
Description does not invoke handlers or dataclass default factories. Annotation
resolution can execute forward references: inspect trusted Python code only.
Put DTOs in module scope so postponed annotations resolve. An author-defined
describe attribute is not overwritten; registration fails rather than replacing it.
Version 1 description contract
Section titled “Version 1 description contract”The public PluginDescription contains schema_version: 1, id, title, and
an action tuple. Each ActionDescription contains id, title, handler (a
Python binding name), request, and result. to_dict() returns an independent
JSON-compatible value; to_json() sorts keys and uses compact JSON. Actions sort
by ID, not discovery order. The schema version is independent of package versions
and future bridge protocol versions. Consumers must reject unsupported versions;
no compatibility negotiation or reader-extension policy is implemented here.
Which schema subset is supported?
Section titled “Which schema subset is supported?”Request/result schemas use the JSON Schema 2020-12 keyword subset type,
properties, required, additionalProperties, anyOf, enum, items,
minimum, and maximum. Supported annotations are str, bool, float, int,
None, parameterized list[T], unions (including nullable unions), JSON-valued
Literal (strings, safe integers, booleans, null), and nonrecursive dataclasses.
Integers are bounded to ±9007199254740991, consistent with ADR-22. Any, arbitrary
host/native objects, mappings, recursive DTOs, and Annotated metadata (including
backend-specific constraints in the test-only spike sample) are rejected rather
than silently weakened. No validation dependency or runtime backend is selected.
What makes a valid action?
Section titled “What makes a valid action?”Each action must be a synchronous function whose first positional parameter is
annotated PluginContext, followed by annotated inputs and an explicit result
annotation (-> None for no result). Inputs may be positional-or-keyword or
keyword-only; variadic and positional-only inputs are unsupported. Generator
streams use the same injected context and require declared event types. The
context is omitted from the request schema. Duplicate IDs are errors.
Defaults, nullability and unknown properties
Section titled “Defaults, nullability and unknown properties”A default makes an input omittable, not nullable; only a union with None makes
it nullable. Dataclass request fields without defaults are required; result
writer schemas require all dataclass fields, including defaulted fields.
Defaults are not executed, validated, or emitted as schema values. Unknown object
properties are disallowed. These are structural writer/request schemas, not an
extensible result-reader policy or a serializer for result instances. Runtime
validation must additionally reject nonfinite numbers and implement ADR-22’s
explicit integer normalization; JSON Schema alone is not a Python validator.
What fails, and how
Section titled “What fails, and how”Invalid declarations raise QgisSdkDeclarationError (a QgisSdkError with code
invalid_declaration), naming the action/binding/field where applicable. IDs must
match [a-zA-Z][a-zA-Z0-9_.-]* and titles must be nonblank strings. The public API
is experimental 0.x. Runtime dispatch, lifecycle hooks, native UI declarations,
bridge routing and TypeScript generation use the resulting declarations.