Some checks failed
Run linters on applied template / Python 3.13 lint and build (push) Failing after 2m36s
Changes: - fix double exception message in main request_processing span - add OpenSearch to Jaeger and OpenTelemetry Logs - add optional OpenTelemetry Logs Exporter to structlog - update deploy README
157 lines
5.7 KiB
Django/Jinja
157 lines
5.7 KiB
Django/Jinja
"""FastAPI application initialization is performed here."""
|
|
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.middleware.gzip import GZipMiddleware
|
|
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, metrics_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.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5)
|
|
|
|
application.state.config = app_config
|
|
|
|
logger = configure_logging(
|
|
app_config.observability.logging,
|
|
tracing_enabled=app_config.observability.jaeger is not None,
|
|
)
|
|
metrics = init_metrics()
|
|
exception_mapper = _get_exception_mapper(app_config.app.debug)
|
|
connection_manager = PostgresConnectionManager(
|
|
master=app_config.db.master,
|
|
replicas=app_config.db.replicas,
|
|
logger=logger,
|
|
application_name=f"{{project_slug}}_{VERSION}",
|
|
)
|
|
urls_mapper = URLsMapper(app_config.observability.prometheus.urls_mapping)
|
|
|
|
connection_manager_dep.init_dispencer(application, connection_manager)
|
|
metrics_dep.init_dispencer(application, metrics)
|
|
logger_dep.init_dispencer(application, logger)
|
|
|
|
application.add_middleware(
|
|
ObservabilityMiddleware,
|
|
exception_mapper=exception_mapper,
|
|
metrics=metrics,
|
|
urls_mapper=urls_mapper,
|
|
)
|
|
application.add_middleware(
|
|
ExceptionHandlerMiddleware,
|
|
debug=[app_config.app.debug],
|
|
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
|
|
logger = logger_dep.obtain(application)
|
|
|
|
await logger.ainfo("application is being configured", config=app_config.to_order_dict())
|
|
|
|
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()
|