Release v0.1.6
This commit is contained in:
@@ -2,12 +2,13 @@ from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from govoplan_core.core.modules import ModuleContext
|
||||
from govoplan_core.core.lifecycle import ModuleLifecycleManager
|
||||
from govoplan_core.core.module_management import load_startup_enabled_modules, startup_candidate_module_ids
|
||||
from govoplan_core.db.session import configure_database
|
||||
from govoplan_core.server.config import GovoplanServerConfig, load_server_config
|
||||
from govoplan_core.server.fastapi import create_govoplan_app
|
||||
from govoplan_core.server.platform import create_platform_router
|
||||
from govoplan_core.server.registry import build_platform_registry
|
||||
from govoplan_core.server.registry import available_module_manifests, build_platform_registry
|
||||
|
||||
|
||||
def create_app(config: GovoplanServerConfig | str | None = None):
|
||||
@@ -20,17 +21,30 @@ def create_app(config: GovoplanServerConfig | str | None = None):
|
||||
if database_url:
|
||||
configure_database(str(database_url))
|
||||
|
||||
registry = build_platform_registry(server_config.enabled_modules, manifest_factories=server_config.manifest_factories)
|
||||
raw_enabled_modules = load_startup_enabled_modules(server_config.enabled_modules)
|
||||
candidate_modules = startup_candidate_module_ids(server_config.enabled_modules, raw_enabled_modules)
|
||||
startup_available_modules = available_module_manifests(
|
||||
server_config.manifest_factories,
|
||||
enabled_modules=candidate_modules,
|
||||
ignore_load_errors=True,
|
||||
)
|
||||
available_modules = available_module_manifests(server_config.manifest_factories, ignore_load_errors=True)
|
||||
enabled_modules = load_startup_enabled_modules(server_config.enabled_modules, available=startup_available_modules)
|
||||
registry = build_platform_registry(enabled_modules, manifest_factories=server_config.manifest_factories)
|
||||
api_router = APIRouter(prefix=server_config.api_prefix)
|
||||
|
||||
for router in server_config.base_routers:
|
||||
api_router.include_router(router)
|
||||
|
||||
module_context = ModuleContext(registry=registry, settings=server_config.settings, data=server_config.module_context_data)
|
||||
registry.configure_capability_context(module_context)
|
||||
for manifest in registry.manifests():
|
||||
if manifest.route_factory is not None:
|
||||
api_router.include_router(manifest.route_factory(module_context))
|
||||
lifecycle = ModuleLifecycleManager(
|
||||
registry=registry,
|
||||
available_modules=available_modules,
|
||||
settings=server_config.settings,
|
||||
api_prefix=server_config.api_prefix,
|
||||
manifest_factories=tuple(server_config.manifest_factories),
|
||||
module_context_data=server_config.module_context_data,
|
||||
)
|
||||
lifecycle.configure_runtime()
|
||||
|
||||
api_router.include_router(create_platform_router(settings=server_config.settings))
|
||||
|
||||
@@ -49,6 +63,8 @@ def create_app(config: GovoplanServerConfig | str | None = None):
|
||||
lifespan=server_config.lifespan,
|
||||
cors_origins=server_config.cors_origins,
|
||||
)
|
||||
lifecycle.attach_app(app)
|
||||
lifecycle.apply_enabled_modules(tuple(manifest.id for manifest in registry.manifests()), migrate=False)
|
||||
for configurator in server_config.app_configurators:
|
||||
configurator(app, registry, server_config.settings)
|
||||
return app
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from importlib import import_module
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
from govoplan_core.auth.dependencies import ApiPrincipal, require_scope
|
||||
from govoplan_access.backend.auth.dependencies import ApiPrincipal, require_scope
|
||||
from govoplan_core.core.registry import PlatformRegistry
|
||||
from govoplan_core.db.bootstrap import bootstrap_dev_data, create_all_tables
|
||||
from govoplan_core.db.session import get_database
|
||||
from govoplan_core.server.config import GovoplanServerConfig, RouterContribution
|
||||
from govoplan_core.server.config import GovoplanServerConfig
|
||||
from govoplan_core.settings import Settings, settings
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
if settings.app_env.lower() == "dev" and settings.dev_auto_migrate_enabled:
|
||||
from govoplan_core.db.migrations import migrate_database
|
||||
|
||||
migrate_database(database_url=settings.database_url)
|
||||
if settings.app_env.lower() == "dev" and settings.dev_bootstrap_enabled:
|
||||
create_all_tables()
|
||||
with get_database().SessionLocal() as session:
|
||||
@@ -30,22 +33,6 @@ def _cors_origins(value: str) -> list[str]:
|
||||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
|
||||
|
||||
def _settings_module_enabled(module_id: str) -> bool:
|
||||
return module_id in {item.strip() for item in settings.enabled_modules.split(",") if item.strip()}
|
||||
|
||||
|
||||
def _dev_mail_enabled(config_settings: object | None, registry: PlatformRegistry) -> bool:
|
||||
active_settings = config_settings if isinstance(config_settings, Settings) else settings
|
||||
return active_settings.app_env == "dev" and active_settings.dev_mailbox_api_enabled and registry.has_module("mail")
|
||||
|
||||
|
||||
def _dev_mail_router_contributions() -> tuple[RouterContribution, ...]:
|
||||
if not (settings.app_env == "dev" and settings.dev_mailbox_api_enabled and _settings_module_enabled("mail")):
|
||||
return ()
|
||||
module = import_module("govoplan_mail.backend.dev_router")
|
||||
return (RouterContribution(module.router, required_module="mail", enabled=_dev_mail_enabled),)
|
||||
|
||||
|
||||
def register_health_details(app: FastAPI, registry: PlatformRegistry, config_settings: object | None) -> None:
|
||||
active_settings = config_settings if isinstance(config_settings, Settings) else settings
|
||||
|
||||
@@ -70,19 +57,12 @@ def register_health_details(app: FastAPI, registry: PlatformRegistry, config_set
|
||||
|
||||
|
||||
def get_server_config() -> GovoplanServerConfig:
|
||||
from govoplan_core.api.v1.admin import router as admin_router
|
||||
from govoplan_core.api.v1.audit import router as audit_router
|
||||
from govoplan_core.api.v1.auth import router as auth_router
|
||||
from govoplan_core.api.v1.system import router as system_router
|
||||
|
||||
return GovoplanServerConfig(
|
||||
title="GovOPlaN Server",
|
||||
version="0.3.0",
|
||||
settings=settings,
|
||||
enabled_modules=settings.enabled_modules,
|
||||
api_prefix="/api/v1",
|
||||
base_routers=(auth_router, admin_router, audit_router, system_router),
|
||||
extra_routers=_dev_mail_router_contributions(),
|
||||
lifespan=lifespan,
|
||||
cors_origins=_cors_origins(settings.cors_origins),
|
||||
app_configurators=(register_health_details,),
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from govoplan_core.core.maintenance import saved_maintenance_mode
|
||||
from govoplan_core.core.modules import FrontendModule, FrontendRoute, NavItem
|
||||
from govoplan_core.core.registry import PlatformRegistry
|
||||
from govoplan_core.db.session import get_database
|
||||
|
||||
|
||||
def _registry(request: Request) -> PlatformRegistry:
|
||||
@@ -42,6 +45,10 @@ def _frontend_payload(frontend: FrontendModule | None) -> dict[str, object] | No
|
||||
"module_id": frontend.module_id,
|
||||
"package_name": frontend.package_name,
|
||||
"asset_manifest": frontend.asset_manifest,
|
||||
"asset_manifest_signature": frontend.asset_manifest_signature,
|
||||
"asset_manifest_public_key_id": frontend.asset_manifest_public_key_id,
|
||||
"asset_manifest_integrity": frontend.asset_manifest_integrity,
|
||||
"asset_manifest_contract_version": frontend.asset_manifest_contract_version,
|
||||
"routes": [_frontend_route_payload(route) for route in frontend.routes],
|
||||
"nav": [_nav_item_payload(item) for item in frontend.nav_items],
|
||||
"settings_routes": [_frontend_route_payload(route) for route in frontend.settings_routes],
|
||||
@@ -60,6 +67,17 @@ def _runtime_ui_capabilities(manifest_id: str, settings: object | None, registry
|
||||
def create_platform_router(settings: object | None = None) -> APIRouter:
|
||||
router = APIRouter(prefix="/platform", tags=["platform"])
|
||||
|
||||
@router.get("/status")
|
||||
def status():
|
||||
try:
|
||||
with get_database().session() as session:
|
||||
maintenance_mode = saved_maintenance_mode(session)
|
||||
except (RuntimeError, SQLAlchemyError):
|
||||
maintenance_mode = None
|
||||
return {
|
||||
"maintenance_mode": maintenance_mode.as_dict() if maintenance_mode is not None else {"enabled": False, "message": None},
|
||||
}
|
||||
|
||||
@router.get("/modules")
|
||||
def modules(request: Request):
|
||||
registry = _registry(request)
|
||||
|
||||
@@ -2,10 +2,11 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
|
||||
from govoplan_core.access.manifest import get_manifest as get_access_manifest
|
||||
from govoplan_access.backend.manifest import get_manifest as get_access_manifest
|
||||
from govoplan_core.core.discovery import discover_module_manifests
|
||||
from govoplan_core.core.modules import ModuleManifest
|
||||
from govoplan_core.core.registry import PlatformRegistry, RegistryError
|
||||
from govoplan_tenancy.backend.manifest import get_manifest as get_tenancy_manifest
|
||||
|
||||
ManifestFactory = Callable[[], ModuleManifest]
|
||||
|
||||
@@ -18,8 +19,20 @@ def parse_enabled_modules(value: str | Iterable[str]) -> list[str]:
|
||||
return [item for item in items if item]
|
||||
|
||||
|
||||
def available_module_manifests(manifest_factories: Sequence[ManifestFactory] = (), *, enabled_modules: Iterable[str] | None = None) -> dict[str, ModuleManifest]:
|
||||
manifests = {manifest.id: manifest for manifest in discover_module_manifests(enabled_modules=enabled_modules)}
|
||||
def available_module_manifests(
|
||||
manifest_factories: Sequence[ManifestFactory] = (),
|
||||
*,
|
||||
enabled_modules: Iterable[str] | None = None,
|
||||
ignore_load_errors: bool = False,
|
||||
) -> dict[str, ModuleManifest]:
|
||||
manifests = {
|
||||
manifest.id: manifest
|
||||
for manifest in discover_module_manifests(
|
||||
enabled_modules=enabled_modules,
|
||||
ignore_load_errors=ignore_load_errors,
|
||||
)
|
||||
}
|
||||
manifests.setdefault("tenancy", get_tenancy_manifest())
|
||||
manifests.setdefault("access", get_access_manifest())
|
||||
for factory in manifest_factories:
|
||||
manifest = factory()
|
||||
@@ -29,8 +42,15 @@ def available_module_manifests(manifest_factories: Sequence[ManifestFactory] = (
|
||||
|
||||
def build_platform_registry(enabled_modules: str | Iterable[str], *, manifest_factories: Sequence[ManifestFactory] = ()) -> PlatformRegistry:
|
||||
requested = parse_enabled_modules(enabled_modules)
|
||||
if "tenancy" not in requested:
|
||||
requested.insert(0, "tenancy")
|
||||
if "access" not in requested:
|
||||
requested.insert(0, "access")
|
||||
tenancy_index = requested.index("tenancy")
|
||||
requested.insert(tenancy_index + 1, "access")
|
||||
elif requested.index("access") < requested.index("tenancy"):
|
||||
requested.remove("access")
|
||||
tenancy_index = requested.index("tenancy")
|
||||
requested.insert(tenancy_index + 1, "access")
|
||||
|
||||
available = available_module_manifests(manifest_factories, enabled_modules=requested)
|
||||
registry = PlatformRegistry()
|
||||
|
||||
Reference in New Issue
Block a user