from collections.abc import AsyncGenerator from contextlib import asynccontextmanager {% if serve_spa %}from pathlib import Path {% endif %} {% if use_dishka %}from dishka.integrations.fastapi import setup_dishka {% endif %}from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware {% if serve_spa %}from fastapi.responses import FileResponse {% endif %} from api import routers {% if use_dishka %}from dependencies.container import container {% endif %}{% if database != 'none' %}from utils.db import init_db {% endif %}from utils.env import env from utils.logging import setup_logging {% if serve_spa %} IMMUTABLE_HEADERS = {"Cache-Control": "public, max-age=31536000, immutable"} NO_CACHE_HEADERS = {"Cache-Control": "no-cache"} {% endif %} @asynccontextmanager async def lifespan(app_: FastAPI) -> AsyncGenerator[None]: setup_logging() {%- if database != 'none' %} await init_db() {%- endif %} yield {%- if use_dishka %} await app_.state.dishka_container.close() {%- endif %} app = FastAPI( title="{{ project_name }} API", lifespan=lifespan, openapi_url="/openapi.json" if env.api.docs else None, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ) app.include_router(routers.router) {% if serve_spa %} spa_dir = Path(env.api.static_dir).resolve() if spa_dir.is_dir(): @app.get("/{spa_path:path}") async def serve_spa(spa_path: str) -> FileResponse: candidate = (spa_dir / spa_path).resolve() if spa_path and candidate.is_relative_to(spa_dir) and candidate.is_file(): immutable = spa_path.startswith("_app/immutable/") headers = IMMUTABLE_HEADERS if immutable else NO_CACHE_HEADERS return FileResponse(candidate, headers=headers) return FileResponse(spa_dir / "index.html", headers=NO_CACHE_HEADERS) {% endif %} {% if use_dishka %}setup_dishka(container, app) {% endif %}