Source code for swcstudio.plugins.contracts

"""Plugin contract models for swcstudio.

This module defines a minimal, versioned contract for external plugins.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

SUPPORTED_PLUGIN_API_VERSIONS: set[str] = {"1"}


[docs] @dataclass(frozen=True) class PluginManifest: """Structured plugin metadata required by the swcstudio plugin loader.""" plugin_id: str name: str version: str api_version: str = "1" description: str = "" author: str = "" capabilities: tuple[str, ...] = () entrypoint: str = ""
[docs] def parse_plugin_manifest(raw: dict[str, Any]) -> PluginManifest: """Validate and normalize a plugin manifest dictionary.""" if not isinstance(raw, dict): raise TypeError("Plugin manifest must be a dictionary.") plugin_id = str(raw.get("plugin_id", "")).strip() name = str(raw.get("name", "")).strip() version = str(raw.get("version", "")).strip() api_version = str(raw.get("api_version", "1")).strip() or "1" description = str(raw.get("description", "")).strip() author = str(raw.get("author", "")).strip() entrypoint = str(raw.get("entrypoint", "")).strip() if not plugin_id: raise ValueError("Plugin manifest requires non-empty 'plugin_id'.") if not name: raise ValueError("Plugin manifest requires non-empty 'name'.") if not version: raise ValueError("Plugin manifest requires non-empty 'version'.") if api_version not in SUPPORTED_PLUGIN_API_VERSIONS: supported = ", ".join(sorted(SUPPORTED_PLUGIN_API_VERSIONS)) raise ValueError( f"Unsupported plugin api_version '{api_version}'. Supported: {supported}." ) raw_caps = raw.get("capabilities", ()) if raw_caps is None: raw_caps = () if not isinstance(raw_caps, (list, tuple, set)): raise ValueError("'capabilities' must be a list/tuple/set of strings.") capabilities = tuple( sorted( { str(cap).strip() for cap in raw_caps if str(cap).strip() } ) ) return PluginManifest( plugin_id=plugin_id, name=name, version=version, api_version=api_version, description=description, author=author, capabilities=capabilities, entrypoint=entrypoint, )
[docs] def plugin_manifest_to_dict(manifest: PluginManifest) -> dict[str, Any]: """Serialize a PluginManifest into JSON-friendly dictionary.""" return { "plugin_id": manifest.plugin_id, "name": manifest.name, "version": manifest.version, "api_version": manifest.api_version, "description": manifest.description, "author": manifest.author, "capabilities": list(manifest.capabilities), "entrypoint": manifest.entrypoint, }