Source code for swcstudio.plugins.registry

"""Plugin registry for swcstudio.

Supports:
1) Legacy flat keys (`register("name", func)` / `get("name")`)
2) Feature method keys (`register_method("tool.feature", "method_name", func)`)
3) Plugin-aware registration with versioned manifest metadata

The feature-based API is preferred for modular override of algorithms used by
CLI and GUI layers.
"""

from __future__ import annotations

from typing import Any, Callable, Dict, Optional

from .contracts import PluginManifest, parse_plugin_manifest, plugin_manifest_to_dict

FlatRegistry = Dict[str, Callable]
FeatureRegistry = Dict[str, Dict[str, Callable]]

_REGISTRY: FlatRegistry = {}
_FEATURE_METHODS: FeatureRegistry = {}
_BUILTIN_METHODS: FeatureRegistry = {}
_FEATURE_METHOD_OWNERS: Dict[str, Dict[str, str]] = {}
_PLUGIN_MANIFESTS: Dict[str, PluginManifest] = {}


[docs] def register(name: str, func: Callable) -> None: """Register a callable under a flat legacy key.""" _REGISTRY[name] = func
[docs] def get(name: str) -> Optional[Callable]: """Retrieve a flat legacy callable or None.""" return _REGISTRY.get(name)
[docs] def unregister(name: str) -> None: _REGISTRY.pop(name, None)
[docs] def clear() -> None: """Clear all registry content (legacy + feature methods).""" _REGISTRY.clear() _FEATURE_METHODS.clear() _BUILTIN_METHODS.clear() _FEATURE_METHOD_OWNERS.clear() _PLUGIN_MANIFESTS.clear()
[docs] def registered_names() -> list[str]: return sorted(_REGISTRY.keys())
[docs] def register_builtin_method(feature_key: str, method_name: str, func: Callable) -> None: """Register an internal builtin method for a feature.""" _BUILTIN_METHODS.setdefault(feature_key, {})[method_name] = func
[docs] def register_plugin_manifest(manifest: PluginManifest | dict[str, Any]) -> PluginManifest: """Register and validate a plugin manifest.""" out = manifest if isinstance(manifest, PluginManifest) else parse_plugin_manifest(manifest) _PLUGIN_MANIFESTS[out.plugin_id] = out return out
[docs] def unregister_plugin(plugin_id: str) -> None: """Remove plugin manifest and all methods owned by this plugin.""" _PLUGIN_MANIFESTS.pop(plugin_id, None) to_drop: list[tuple[str, str]] = [] for feature_key, owner_map in _FEATURE_METHOD_OWNERS.items(): for method_name, owner in owner_map.items(): if owner == plugin_id: to_drop.append((feature_key, method_name)) for feature_key, method_name in to_drop: unregister_method(feature_key, method_name)
[docs] def register_plugin_method( plugin_id: str, feature_key: str, method_name: str, func: Callable, ) -> None: """Register a feature method owned by a named plugin.""" plugin_key = str(plugin_id).strip() or "user.local" if plugin_key not in _PLUGIN_MANIFESTS: register_plugin_manifest( { "plugin_id": plugin_key, "name": plugin_key, "version": "0.0.0", "api_version": "1", "description": "Implicit plugin manifest from register_method().", "capabilities": [], } ) _FEATURE_METHODS.setdefault(feature_key, {})[method_name] = func _FEATURE_METHOD_OWNERS.setdefault(feature_key, {})[method_name] = plugin_key
[docs] def register_method( feature_key: str, method_name: str, func: Callable, *, plugin_id: str | None = None, ) -> None: """Register a user/plugin method that overrides builtins with same name.""" register_plugin_method(plugin_id or "user.local", feature_key, method_name, func)
[docs] def unregister_method(feature_key: str, method_name: str) -> None: methods = _FEATURE_METHODS.get(feature_key) if not methods: return methods.pop(method_name, None) if not methods: _FEATURE_METHODS.pop(feature_key, None) owners = _FEATURE_METHOD_OWNERS.get(feature_key) if owners is not None: owners.pop(method_name, None) if not owners: _FEATURE_METHOD_OWNERS.pop(feature_key, None)
[docs] def resolve_method( feature_key: str, method_name: str, fallback: Optional[Callable] = None, ) -> Callable: """Resolve method by priority: plugin override -> builtin -> fallback.""" plugin_func = _FEATURE_METHODS.get(feature_key, {}).get(method_name) if plugin_func is not None: return plugin_func builtin_func = _BUILTIN_METHODS.get(feature_key, {}).get(method_name) if builtin_func is not None: return builtin_func if fallback is not None: return fallback raise KeyError( f"No method registered for feature '{feature_key}' and method '{method_name}'." )
[docs] def list_feature_methods(feature_key: str) -> dict: """Return plugin + builtin method names for a feature.""" owners = dict(_FEATURE_METHOD_OWNERS.get(feature_key, {})) return { "feature": feature_key, "plugin_methods": sorted(_FEATURE_METHODS.get(feature_key, {}).keys()), "builtin_methods": sorted(_BUILTIN_METHODS.get(feature_key, {}).keys()), "plugin_method_owners": {k: owners[k] for k in sorted(owners.keys())}, }
[docs] def list_all_feature_methods() -> dict: """Return all feature method registrations.""" keys = sorted(set(_FEATURE_METHODS.keys()) | set(_BUILTIN_METHODS.keys())) return {k: list_feature_methods(k) for k in keys}
[docs] def list_plugins() -> list[dict[str, Any]]: """Return registered plugin manifests.""" return [ plugin_manifest_to_dict(_PLUGIN_MANIFESTS[k]) for k in sorted(_PLUGIN_MANIFESTS.keys()) ]
[docs] def get_plugin(plugin_id: str) -> Optional[dict[str, Any]]: """Return one plugin manifest as dictionary.""" manifest = _PLUGIN_MANIFESTS.get(plugin_id) if manifest is None: return None return plugin_manifest_to_dict(manifest)