"""Shared FastAPI dependencies. Pulls singletons stashed in `app.state` by the lifespan handler. Database is created ONCE per uvicorn process; per-request creation would defeat connection pooling. Workflows are different — they live in YAML files that the user can edit / create at runtime via the workflow generator UI. `get_workflows` does a cheap mtime check on every call and reloads when any file in the seed or user workflow directory has changed. No file watcher / inotify needed — the directories are tiny (≤ dozens of files) and stat is cheap. """ from __future__ import annotations from pathlib import Path from typing import TYPE_CHECKING from fastapi import Request from ..config import Config from ..persistence.db import Database from ..user_dirs import load_combined_workflows, user_workflows_dir if TYPE_CHECKING: from ..persona import Persona from ..workflow import WorkflowTemplate _DOCS_SCHEMAS = Path(__file__).resolve().parents[3] / "docs" / "schemas" def get_db(request: Request) -> Database: """Return the shared Database instance from app state.""" return request.app.state.db # type: ignore[no-any-return] def get_config(request: Request) -> Config: return request.app.state.config # type: ignore[no-any-return] def get_personas(request: Request) -> list[Persona]: return request.app.state.personas # type: ignore[no-any-return] def _workflow_dir_signature(config: Config) -> tuple[tuple[str, float], ...]: """Cheap mtime-tuple fingerprint of all YAMLs in seed + user dirs. Two stat calls per file; the fingerprint changes when any file is created / modified / deleted. Used as the cache key for :func:`get_workflows` so the API picks up new templates without a process restart. """ sig: list[tuple[str, float]] = [] for d in (_DOCS_SCHEMAS / "workflows", user_workflows_dir(config)): if not d.is_dir(): continue for p in sorted(d.glob("*.yaml")): try: sig.append((str(p), p.stat().st_mtime)) except OSError: continue return tuple(sig) def get_workflows(request: Request) -> list[tuple[Path, WorkflowTemplate]]: """Return (path, template) list with mtime-based hot-reload. On every request, computes the mtime fingerprint of the workflow dirs. If it differs from the cached signature, calls :func:`load_combined_workflows` again to pick up new / edited files. """ app = request.app config: Config = app.state.config current_sig = _workflow_dir_signature(config) cached_sig: tuple[tuple[str, float], ...] | None = getattr(app.state, "workflows_sig", None) if cached_sig != current_sig: app.state.workflows = load_combined_workflows(config, _DOCS_SCHEMAS / "workflows") app.state.workflows_sig = current_sig return app.state.workflows # type: ignore[no-any-return] def seed_root() -> Path: """Filesystem root for `docs/schemas/` seed assets.""" return _DOCS_SCHEMAS