blux-ca / ca /catalog.py
~JADIS
Build BLUX-cA Grand Universe CLI and safety suite
6d88ccb
from __future__ import annotations
import importlib
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Iterable, List
import yaml
REQUIRED_FIELDS = {"name", "type", "version", "description", "capabilities", "entrypoint"}
@dataclass
class CatalogEntry:
name: str
type: str
version: str
description: str
capabilities: List[str]
entrypoint: str
provider: str | None = None
def load(self) -> Any:
module_name, attr = self.entrypoint.rsplit(".", 1)
module = importlib.import_module(module_name)
return getattr(module, attr)
class CatalogRegistry:
def __init__(self, entries: List[CatalogEntry]) -> None:
self.entries = entries
@classmethod
def _load_file(cls, path: Path) -> List[CatalogEntry]:
data = yaml.safe_load(path.read_text()) if path.exists() else []
entries: List[CatalogEntry] = []
for raw in data or []:
missing = REQUIRED_FIELDS - set(raw)
if missing:
raise ValueError(f"Catalog entry missing fields {missing} in {path}")
entries.append(CatalogEntry(**raw))
return entries
@classmethod
def from_default(cls) -> "CatalogRegistry":
base = Path(__file__).parent.parent / "catalogs"
entries: List[CatalogEntry] = []
for name in ["models.yaml", "tools.yaml", "plugins.yaml"]:
entries.extend(cls._load_file(base / name))
return cls(entries)
def find(self, *, type: str | None = None, capability: str | None = None) -> Iterable[CatalogEntry]:
for entry in self.entries:
if type and entry.type != type:
continue
if capability and capability not in entry.capabilities:
continue
yield entry
def list_all(self) -> List[Dict[str, str]]:
return [
{"type": e.type, "name": e.name, "description": e.description, "version": e.version}
for e in self.entries
]