checking system…
Docs / back / src/maf/dashboard/routers/sources.py · line 104
Python · 190 lines
  1"""Source-adapter catalog + module config + app-config endpoints.
  2
  3* ``GET /api/sources``         — every registered adapter, with module + usage.
  4* ``POST /api/sources/test``   — instantiate an adapter and call ``.fetch()``.
  5* ``GET /api/modules``         — every loaded DataModule + its adapters.
  6* ``PUT /api/modules/{name}``  — save module config back to default.yaml.
  7* ``GET /api/config``          — read the live app config (with secrets masked).
  8* ``PUT /api/config/llm``      — save the llm section back to default.yaml.
  9"""
 10
 11from __future__ import annotations
 12
 13import logging
 14from pathlib import Path
 15from typing import Any
 16
 17import yaml
 18from fastapi import APIRouter, HTTPException
 19from pydantic import BaseModel
 20
 21from maf.dashboard import state
 22
 23logger = logging.getLogger(__name__)
 24
 25router = APIRouter()
 26
 27
 28class SourceTestRequest(BaseModel):
 29    adapter: str
 30    params: dict[str, Any] = {}
 31
 32
 33class UpdateModuleRequest(BaseModel):
 34    enabled: bool = True
 35    config: dict[str, Any] = {}
 36
 37
 38class UpdateConfigRequest(BaseModel):
 39    config: dict[str, Any]
 40
 41
 42@router.get("/api/sources")
 43async def list_sources() -> list[dict[str, Any]]:
 44    """List all available source adapters grouped by module."""
 45    from maf.sources.registry import _ADAPTER_REGISTRY
 46
 47    maf_app = state.get_maf_app()
 48    module_map: dict[str, str] = {}
 49    if maf_app:
 50        for module in maf_app.modules:
 51            for adapter_name in module.get_adapters():
 52                module_map[adapter_name] = module.module_name
 53
 54    arena_usage: dict[str, list[str]] = {}
 55    if maf_app:
 56        for arena_name in maf_app.list_arenas():
 57            arena = maf_app.get_arena(arena_name)
 58            if arena:
 59                for src in arena.config.sources:
 60                    arena_usage.setdefault(src.adapter, []).append(arena_name)
 61
 62    return [
 63        {
 64            "name": name,
 65            "class": cls.__name__,
 66            "module": module_map.get(name, "built-in"),
 67            "used_in_arenas": arena_usage.get(name, []),
 68        }
 69        for name, cls in _ADAPTER_REGISTRY.items()
 70    ]
 71
 72
 73@router.post("/api/sources/test")
 74async def test_source(req: SourceTestRequest) -> dict[str, Any]:
 75    """Test-fetch from a source adapter with given params."""
 76    from maf.sources.registry import _ADAPTER_REGISTRY
 77
 78    adapter_cls = _ADAPTER_REGISTRY.get(req.adapter)
 79    if not adapter_cls:
 80        raise HTTPException(404, f"Adapter {req.adapter!r} not found")
 81
 82    try:
 83        instance = adapter_cls(req.params)
 84        result = await instance.fetch(req.params)
 85        result_str = str(result)
 86        return {
 87            "adapter": req.adapter,
 88            "status": "error" if result.get("error") else "ok",
 89            "result": result,
 90            "result_keys": list(result.keys()),
 91            "truncated": len(result_str) > 5000,
 92        }
 93    except Exception as exc:
 94        return {
 95            "adapter": req.adapter,
 96            "status": "error",
 97            "error": str(exc),
 98            "result": {},
 99            "result_keys": [],
100        }
101
102
103@router.get("/api/modules")
104async def list_modules() -> list[dict[str, Any]]:
105    """List all loaded data modules and their adapters."""
106    maf_app = state.get_maf_app()
107    if not maf_app:
108        return []
109
110    result = []
111    for module in maf_app.modules:
112        adapters = module.get_adapters()
113        result.append({
114            "name": module.module_name,
115            "class": module.__class__.__name__,
116            "initialized": module._initialized,
117            "config": module.config,
118            "adapters": list(adapters.keys()),
119            "adapter_count": len(adapters),
120        })
121    return result
122
123
124@router.put("/api/modules/{name}")
125async def update_module_config(name: str, req: UpdateModuleRequest) -> dict[str, Any]:
126    """Update module configuration and write to default.yaml."""
127    state.require_maf_app()
128
129    config_path = Path("config/default.yaml")
130    if not config_path.exists():
131        raise HTTPException(404, "Default config not found")
132
133    try:
134        with open(config_path) as f:
135            raw = yaml.safe_load(f)
136
137        modules = raw.get("modules", [])
138        found = False
139        for m in modules:
140            if m.get("name") == name:
141                m["enabled"] = req.enabled
142                m["config"] = req.config
143                found = True
144                break
145        if not found:
146            modules.append({"name": name, "enabled": req.enabled, "config": req.config})
147        raw["modules"] = modules
148
149        with open(config_path, "w") as f:
150            yaml.dump(raw, f, default_flow_style=False, sort_keys=False)
151
152        return {"status": "ok", "message": f"Module {name} config saved. Restart to apply."}
153    except Exception as exc:
154        raise HTTPException(500, f"Failed to save module config: {exc}")
155
156
157@router.get("/api/config")
158async def get_app_config() -> dict[str, Any]:
159    """Get the full app config (with secrets masked)."""
160    maf_app = state.get_maf_app()
161    if not maf_app:
162        return {}
163
164    config = maf_app.config.model_dump(exclude_none=True)
165    for provider_cfg in config.get("llm", {}).get("providers", {}).values():
166        if "api_key" in provider_cfg and provider_cfg["api_key"]:
167            provider_cfg["api_key"] = provider_cfg["api_key"][:8] + "..."
168    return config
169
170
171@router.put("/api/config/llm")
172async def update_llm_config(req: UpdateConfigRequest) -> dict[str, Any]:
173    """Update LLM configuration in default.yaml."""
174    config_path = Path("config/default.yaml")
175    if not config_path.exists():
176        raise HTTPException(404, "Default config not found")
177
178    try:
179        with open(config_path) as f:
180            raw = yaml.safe_load(f)
181
182        raw["llm"] = req.config
183
184        with open(config_path, "w") as f:
185            yaml.dump(raw, f, default_flow_style=False, sort_keys=False)
186
187        return {"status": "ok", "message": "LLM config saved. Restart to apply."}
188    except Exception as exc:
189        raise HTTPException(500, f"Failed to save LLM config: {exc}")