from __future__ import annotations import os from collections.abc import Callable, Iterable, Mapping, Sequence from dataclasses import dataclass, field from importlib import import_module from typing import Any from fastapi import APIRouter, FastAPI from govoplan_core.core.modules import ModuleManifest from govoplan_core.core.registry import PlatformRegistry from govoplan_core.server.fastapi import LifespanFactory ManifestFactory = Callable[[], ModuleManifest] RouterEnabled = Callable[[object | None, PlatformRegistry], bool] AppConfigurator = Callable[[FastAPI, PlatformRegistry, object | None], None] @dataclass(frozen=True, slots=True) class RouterContribution: router: APIRouter required_module: str | None = None enabled: RouterEnabled | None = None def should_include(self, settings: object | None, registry: PlatformRegistry) -> bool: if self.required_module and not registry.has_module(self.required_module): return False if self.enabled is not None and not self.enabled(settings, registry): return False return True @dataclass(frozen=True, slots=True) class GovoplanServerConfig: title: str = "GovOPlaN Server" version: str = "0.1.0" settings: object | None = None enabled_modules: str | Iterable[str] = ("access",) api_prefix: str = "/api/v1" base_routers: Sequence[APIRouter] = () post_module_routers: Sequence[APIRouter] = () extra_routers: Sequence[RouterContribution] = () lifespan: LifespanFactory | None = None cors_origins: Iterable[str] = () manifest_factories: Sequence[ManifestFactory] = () module_context_data: Mapping[str, Any] = field(default_factory=dict) app_configurators: Sequence[AppConfigurator] = () def import_object(path: str) -> Any: module_name, separator, attribute = path.partition(":") if not separator: raise ValueError(f"Object path must use module:attribute syntax: {path!r}") module = import_module(module_name) value: Any = module for part in attribute.split("."): value = getattr(value, part) return value def load_server_config(path: str | None = None) -> GovoplanServerConfig: config_path = path or os.getenv("GOVOPLAN_SERVER_CONFIG") or "govoplan_core.server.default_config:get_server_config" try: loaded = import_object(config_path) except ModuleNotFoundError: if path or os.getenv("GOVOPLAN_SERVER_CONFIG"): raise return GovoplanServerConfig() config = loaded() if callable(loaded) else loaded if not isinstance(config, GovoplanServerConfig): raise TypeError(f"{config_path!r} did not return a GovoplanServerConfig") return config