Files
CosmicLaca-ComfyUI_Primere_…/components/API/api_schema_registry.py
T

144 lines
6.5 KiB
Python

from __future__ import annotations
import os
from copy import deepcopy
from typing import Any
from pathlib import Path
import json
from ..tree import PRIMERE_ROOT
def _is_service_schema(node: Any) -> bool:
return isinstance(node, dict) and isinstance(node.get("request"), dict)
def normalize_registry(raw: dict[str, Any] | None) -> dict[str, dict[str, dict[str, Any]]]:
if not isinstance(raw, dict):
return {}
if _is_service_schema(raw):
provider = str(raw.get("provider") or "custom")
service = str(raw.get("service") or "default")
return {provider: {service: deepcopy(raw)}}
registry: dict[str, dict[str, dict[str, Any]]] = {}
for provider, services in raw.items():
if not isinstance(services, dict):
continue
service_map: dict[str, dict[str, Any]] = {}
for service_name, schema in services.items():
if not _is_service_schema(schema):
continue
item = deepcopy(schema)
item.setdefault("provider", str(provider))
item.setdefault("service", str(service_name))
service_map[str(service_name)] = item
if service_map:
registry[str(provider)] = service_map
return registry
def list_providers(registry: dict[str, dict[str, dict[str, Any]]]) -> list[str]:
return sorted(registry.keys())
def list_services(registry: dict[str, dict[str, dict[str, Any]]], provider: str) -> list[str]:
return sorted(registry.get(provider, {}).keys())
def get_schema(
registry: dict[str, dict[str, dict[str, Any]]],
provider: str,
service: str | None,
) -> tuple[dict[str, Any] | None, str | None]:
service_map = registry.get(provider, {})
if not service_map:
return None, None
if service and service in service_map:
return deepcopy(service_map[service]), service
first_service = next(iter(service_map.keys()))
return deepcopy(service_map[first_service]), first_service
def _load_json_file(file_path: str, label: str) -> Any:
path = str(file_path or "").strip()
if not path:
raise RuntimeError(f"Missing path for {label} JSON file")
if not Path(path).exists():
raise RuntimeError(f"{label} JSON file not found: {path}")
try:
return json.loads(Path(path).read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise RuntimeError(f"Invalid JSON syntax in {label} file '{path}' at line {exc.lineno}, column {exc.colno}: {exc.msg}") from exc
def load_and_validate_api_schema_registry(schema_path: str, apiconfig_path: str | None = None) -> dict[str, dict[str, dict[str, Any]]]:
schema_data = _load_json_file(schema_path, "API schema registry")
if not isinstance(schema_data, dict):
raise RuntimeError("API schema registry root must be a JSON object mapping providers to services.")
config_path = apiconfig_path or os.path.join(PRIMERE_ROOT, "json", "apiconfig.example.json")
apiconfig_data = _load_json_file(config_path, "API config")
if not isinstance(apiconfig_data, dict):
raise RuntimeError("API config root must be a JSON object mapping provider names.")
allowed_providers = {str(name).strip() for name in apiconfig_data.keys() if str(name).strip()}
if len(allowed_providers) == 0:
raise RuntimeError("API config does not define any provider names.")
validated: dict[str, dict[str, dict[str, Any]]] = {}
for provider_key, services in schema_data.items():
provider_name = str(provider_key).strip()
if not provider_name:
raise RuntimeError("Schema provider key cannot be empty.")
if provider_name not in allowed_providers:
allowed = ", ".join(sorted(allowed_providers))
raise RuntimeError(f"Provider '{provider_name}' in API schema is not registered in API config '{config_path}'. Allowed providers: {allowed}")
if not isinstance(services, dict):
raise RuntimeError(f"Provider '{provider_name}' value must be an object mapping services to schemas.")
validated_services: dict[str, dict[str, Any]] = {}
for service_key, schema in services.items():
service_name = str(service_key).strip()
if not service_name:
raise RuntimeError(f"Provider '{provider_name}' contains an empty service key.")
if not isinstance(schema, dict):
raise RuntimeError(f"Schema for provider '{provider_name}' service '{service_name}' must be a JSON object.")
inner_provider = str(schema.get("provider") or "").strip()
inner_service = str(schema.get("service") or "").strip()
if inner_provider != provider_name:
raise RuntimeError(f"Provider/service mismatch at '{provider_name}/{service_name}': schema field 'provider' must equal '{provider_name}', got '{inner_provider or '<missing>'}'.")
if inner_service != service_name:
raise RuntimeError(f"Provider/service mismatch at '{provider_name}/{service_name}': schema field 'service' must equal '{service_name}', got '{inner_service or '<missing>'}'.")
request = schema.get("request")
if not isinstance(request, dict):
raise RuntimeError(f"Schema '{provider_name}/{service_name}' must contain object key 'request'.")
method = str(request.get("method") or "").strip().upper()
if not method:
raise RuntimeError(f"Schema '{provider_name}/{service_name}' request.method is required.")
endpoint = str(request.get("endpoint") or "").strip()
if not endpoint:
raise RuntimeError(f"Schema '{provider_name}/{service_name}' request.endpoint is required.")
possible_parameters = schema.get("possible_parameters", {})
if not isinstance(possible_parameters, dict):
raise RuntimeError(f"Schema '{provider_name}/{service_name}' key 'possible_parameters' must be an object.")
import_modules = schema.get("import_modules", [])
if not isinstance(import_modules, list):
raise RuntimeError(f"Schema '{provider_name}/{service_name}' key 'import_modules' must be a list of import statements." )
for idx, import_line in enumerate(import_modules):
if not isinstance(import_line, str) or not import_line.strip():
raise RuntimeError(f"Schema '{provider_name}/{service_name}' import_modules[{idx}] must be a non-empty string.")
validated_services[service_name] = schema
validated[provider_name] = validated_services
return validated