CommandMetadata Effect

The upsert_metadata method on any command class provides a flexible key-value storage system for command-specific data within the Canvas system. This method enables the creation and updating of custom metadata entries associated with command records, allowing for extensible command information storage beyond standard command fields.

Overview #

Command metadata serves as a powerful extension mechanism for storing custom command-related information that doesn’t fit within the standard command data model. Metadata is managed through the upsert_metadata method available on all command effect classes.

Method #

upsert_metadata(key: str, value: str) → Effect #

Creates or updates a metadata entry for the specified command.

Parameters #

ParameterTypeDescriptionRequired
keystrUnique identifier for the metadata entry within the command contextYes
valuestrThe metadata value to storeYes

Prerequisites #

The command effect must be initialized with a command_uuid corresponding to an existing command.

AttributeTypeDescriptionRequired
command_uuidstrId of the command record to associate metadata withYes

Returns #

An Effect object configured for upserting command metadata.

Behavior #

  • If a metadata entry with the specified key already exists for the command, it will be updated with the new value
  • If no entry exists, a new metadata entry will be created
  • The operation is idempotent - repeated calls with the same key and value will not create duplicate entries
  • Raises ValueError if command_uuid is not set on the command effect

Implementation Details #

Validation #

The effect performs comprehensive validation before execution:

  1. Command Existence Validation: Verifies that the referenced command exists with the given command_uuid and schema_key

    • Returns a descriptive error if the command is not found
  2. Field Validation: Ensures all required fields are provided and properly formatted

    • command_uuid must be set on the command effect
    • Both key and value must be provided

Example Usage #

Basic Usage #

from canvas_sdk.commands import PlanCommand

plan = PlanCommand(command_uuid="63hdik")
effect = plan.upsert_metadata(key="my_plugin:priority", value="high")

Example: Tagging a command on commit #

from canvas_sdk.commands import PlanCommand
from canvas_sdk.effects import Effect
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler


class TagPlanOnCommit(BaseHandler):
    """Tags a plan command with a workflow stage when it is committed."""

    RESPONDS_TO = EventType.Name(EventType.PLAN_COMMAND__POST_COMMIT)

    def compute(self) -> list[Effect]:
        plan = PlanCommand(command_uuid=self.event.target.id)
        return [plan.upsert_metadata(key="my_plugin:workflow_stage", value="committed")]

Responding to metadata events #

Once metadata is upserted, COMMAND_METADATA_CREATED and COMMAND_METADATA_UPDATED events are emitted and can be handled by plugins:

from canvas_sdk.effects import Effect
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from canvas_sdk.v1.data.command import CommandMetadata
from logger import log


class CommandMetadataListener(BaseHandler):
    """Reacts to command metadata changes."""

    RESPONDS_TO = [
        EventType.Name(EventType.COMMAND_METADATA_CREATED),
        EventType.Name(EventType.COMMAND_METADATA_UPDATED),
    ]

    def compute(self) -> list[Effect]:
        metadata = CommandMetadata.objects.get(id=self.event.target.id)
        log.info(f"Command {metadata.command.id}: {metadata.key}={metadata.value}")
        return []

Best Practices #

Key Naming Conventions #

  1. Use Descriptive Names: Choose keys that clearly indicate the purpose of the metadata

    • Good: workflow_stage, external_id, review_status
    • Avoid: data1, temp, misc
  2. Namespace Your Keys: Prefix keys with your plugin name to avoid collisions with other plugins

    • Example: my_plugin:workflow_stage, my_plugin:external_id

Value Storage #

String Serialization: All values are stored as strings. For complex data types, serialize to JSON:

   import json
   from canvas_sdk.commands import DiagnoseCommand

   cmd = DiagnoseCommand(command_uuid="abc123")
   data = {"reviewer": "user-id", "approved_at": "2025-01-15T10:30:00Z"}
   cmd.upsert_metadata(key="my_plugin:review", value=json.dumps(data))

Notes #

  • Metadata entries are command-specific — the same key can have different values for different commands
  • There is no built-in versioning; updating a key overwrites the previous value
  • The system does not enforce any schema on metadata values — validation is the responsibility of the implementing code
  • The key field supports up to 256 characters