"""FastAPI application initialization is performed here.""" import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.docs import get_swagger_ui_html from fastapi.responses import JSONResponse from {{project_slug}}.config import {{ProjectName}}Config from {{project_slug}}.db.connection.manager import PostgresConnectionManager from {{project_slug}}.dependencies import connection_manager_dep, logger_dep from {{project_slug}}.exceptions.mapper import ExceptionMapper from {{project_slug}}.handlers.debug import DebugException, DebugExceptionWithParams from {{project_slug}}.middlewares.exception_handler import ExceptionHandlerMiddleware from {{project_slug}}.middlewares.observability import ObservabilityMiddleware from {{project_slug}}.observability.metrics import init_metrics from {{project_slug}}.observability.otel_agent import OpenTelemetryAgent from {{project_slug}}.utils.observability import URLsMapper, configure_logging from .handlers import list_of_routers from .version import LAST_UPDATE, VERSION def _get_exception_mapper(debug: bool) -> ExceptionMapper: mapper = ExceptionMapper() if debug: mapper.register_simple(DebugException, 506, "That's how a debug exception look like") mapper.register_func( DebugExceptionWithParams, lambda exc: JSONResponse( {"error": "That's how a debug exception with params look like", "message": exc.message}, status_code=exc.status_code, ), ) return mapper def bind_routes(application: FastAPI, prefix: str, debug: bool) -> None: """Bind all routes to application.""" for router in list_of_routers: if not debug: to_remove = [] for i, route in enumerate(router.routes): if "debug" in route.path: to_remove.append(i) for i in to_remove[::-1]: del router.routes[i] if len(router.routes) > 0: application.include_router(router, prefix=(prefix if "/" not in {r.path for r in router.routes} else "")) def get_app(prefix: str = "/api") -> FastAPI: """Create application and all dependable objects.""" if "CONFIG_PATH" not in os.environ: raise ValueError("CONFIG_PATH environment variable is not set") app_config: {{ProjectName}}Config = {{ProjectName}}Config.from_file(os.getenv("CONFIG_PATH")) description = "{{project_description}}" application = FastAPI( title="{{project_name}}", description=description, docs_url=None, openapi_url=f"{prefix}/openapi", version=f"{VERSION} ({LAST_UPDATE})", terms_of_service="http://swagger.io/terms/", contact={"email": "idu@itmo.ru"}, license_info={"name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html"}, lifespan=lifespan, ) bind_routes(application, prefix, app_config.app.debug) @application.get(f"{prefix}/docs", include_in_schema=False) async def custom_swagger_ui_html(): return get_swagger_ui_html( openapi_url=application.openapi_url, title=application.title + " - Swagger UI", oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, swagger_js_url="https://unpkg.com/swagger-ui-dist@5.11.7/swagger-ui-bundle.js", swagger_css_url="https://unpkg.com/swagger-ui-dist@5.11.7/swagger-ui.css", ) application.add_middleware( CORSMiddleware, allow_origins=app_config.app.cors.allow_origins, allow_credentials=app_config.app.cors.allow_credentials, allow_methods=app_config.app.cors.allow_methods, allow_headers=app_config.app.cors.allow_headers, ) application.state.config = app_config exception_mapper = _get_exception_mapper(app_config.app.debug) metrics = init_metrics() urls_mapper = URLsMapper(app_config.observability.prometheus.urls_mapping) application.add_middleware( ObservabilityMiddleware, exception_mapper=exception_mapper, metrics=metrics, urls_mapper=urls_mapper, ) application.add_middleware( ExceptionHandlerMiddleware, debug=[False], # reinitialized on startup exception_mapper=exception_mapper, ) return application @asynccontextmanager async def lifespan(application: FastAPI): """Lifespan function. Initializes database connection in pass_services_dependencies middleware. """ app_config: {{ProjectName}}Config = application.state.config loggers_dict = {logger_config.filename: logger_config.level for logger_config in app_config.logging.files} logger = configure_logging(app_config.logging.level, loggers_dict) await logger.ainfo("application is being configured", config=app_config.to_order_dict()) connection_manager = PostgresConnectionManager( master=app_config.db.master, replicas=app_config.db.replicas, logger=logger, application_name=f"{{project_slug}}_{VERSION}", ) connection_manager_dep.init_dispencer(application, connection_manager) logger_dep.init_dispencer(application, logger) for middleware in application.user_middleware: if middleware.cls == ExceptionHandlerMiddleware: middleware.kwargs["debug"][0] = app_config.app.debug otel_agent = OpenTelemetryAgent( app_config.observability.prometheus, app_config.observability.jaeger, ) yield otel_agent.shutdown() app = get_app()