from __future__ import annotations from collections.abc import AsyncIterator from contextlib import asynccontextmanager from apscheduler.schedulers.background import BackgroundScheduler from fastapi import FastAPI from sqlalchemy import Engine from sqlalchemy.orm import Session, sessionmaker from srht_contrib.api.routes_contributions import router as contributions_router from srht_contrib.api.routes_health import router as health_router from srht_contrib.api.routes_repositories import router as repositories_router from srht_contrib.config import Settings, get_settings from srht_contrib.db import make_engine, validate_db from srht_contrib.jobs.poller import PollerService from srht_contrib.logging import configure_logging from srht_contrib.services.git import GitIngestionService from srht_contrib.services.srht_client import SourceHutGraphQLClient from srht_contrib.services.todo import TodoIngestionService from srht_contrib.utils.identity import ActorIdentityResolver def build_poller(settings: Settings) -> PollerService: todo_client = SourceHutGraphQLClient(settings.todo_srht_endpoint, settings.srht_token) git_client = SourceHutGraphQLClient(settings.git_srht_endpoint, settings.srht_token) todo_service = TodoIngestionService(todo_client, settings) git_service = GitIngestionService(git_client, settings) return PollerService(todo_service=todo_service, git_service=git_service) def create_app( settings: Settings | None = None, *, engine: Engine | None = None, session_factory: sessionmaker[Session] | None = None, poller: PollerService | None = None, ) -> FastAPI: app_settings = settings or get_settings() configure_logging() app_engine = engine or make_engine(app_settings) app_session_factory = session_factory or sessionmaker( bind=app_engine, autoflush=False, autocommit=False, expire_on_commit=False, ) actor_identity_resolver = ActorIdentityResolver(app_settings.actor_aliases_json) @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: validate_db(app_engine) app_poller = poller or build_poller(app_settings) app.state.poller = app_poller app.state.settings = app_settings app.state.engine = app_engine app.state.session_factory = app_session_factory app.state.actor_identity_resolver = actor_identity_resolver scheduler: BackgroundScheduler | None = None if app_settings.enable_scheduler: scheduler = BackgroundScheduler() scheduler.add_job( _scheduled_poll, "interval", seconds=app_settings.poll_interval_seconds, args=[app], id="srht-poller", replace_existing=True, ) scheduler.start() app.state.scheduler = scheduler try: yield finally: if scheduler is not None: scheduler.shutdown(wait=False) app_poller.todo_service.client.close() app_poller.git_service.client.close() app = FastAPI(title=app_settings.app_name, lifespan=lifespan) app.include_router(health_router) app.include_router(contributions_router) app.include_router(repositories_router) return app def _scheduled_poll(app: FastAPI) -> None: settings: Settings = app.state.settings poller: PollerService = app.state.poller session_factory: sessionmaker[Session] = app.state.session_factory db = session_factory() try: poller.poll_all(db, settings.default_actor) finally: db.close() app = create_app()