Skip to content

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 dataclass
from qgis_sdk import QgisSdkPlugin, describe
from qgis_sdk.core import PluginContext
@dataclass
class 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 implementation
print(describe(run).bindings) # run.describe() also works

app.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.

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.

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.

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.

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.

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.

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.