Files
prod-end-2026/backend/app/main.py
T
2026-03-17 18:32:44 +03:00

189 lines
6.0 KiB
Python

import sys
import asyncio
import os
import uuid
import logging
from time import perf_counter
from contextlib import asynccontextmanager
from prometheus_fastapi_instrumentator import Instrumentator
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from app.api.ping.router import router as health_router
from app.api.actions.router import router as actions_router
from app.api.capabilities.router import router as capabilities_router
from app.api.executions.router import router as executions_router
from app.api.pipelines.router import router as pipelines_router
from app.utils.error_handlers import (
validation_exception_handler,
http_exception_handler,
unhandled_exception_handler,
)
from app.utils.log_context import clear_log_context, set_request_context
from app.core.logging import configure_logging
from app.core.database.init import init_db
try:
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from redis import asyncio as aioredis
except ModuleNotFoundError:
FastAPICache = None
RedisBackend = None
aioredis = None
try:
from app.api.auth.register import router as auth_router
from app.api.auth.login import router as login_router
except ModuleNotFoundError as exc:
auth_router = None
login_router = None
print(f"Auth routes are disabled: {exc}")
try:
from app.api.users.get_me import router as get_me_router
from app.api.users.list_users import router as list_users_router
from app.api.users.update_me import router as update_me_router
from app.api.users.update_user import router as update_user_router
from app.api.users.update_password import router as update_password_router
from app.api.users.delete_user import router as delete_user_router
except ModuleNotFoundError as exc:
get_me_router = None
list_users_router = None
update_me_router = None
update_user_router = None
update_password_router = None
delete_user_router = None
print(f"User routes are disabled: {exc}")
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
configure_logging()
http_logger = logging.getLogger("app.http")
@asynccontextmanager
async def lifespan(app: FastAPI):
try:
await init_db()
except Exception as e:
print(f"Database initialization error: {e}")
redis_host = os.getenv("REDIS_HOST", "localhost")
redis_port = os.getenv("REDIS_PORT", "6379")
redis_url = os.getenv("REDIS_URL", f"redis://{redis_host}:{redis_port}")
redis = None
if FastAPICache and RedisBackend and aioredis:
try:
redis = aioredis.from_url(redis_url, encoding="utf8", decode_responses=True)
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
print(f"Redis initialized successfully at {redis_url}!")
except Exception as e:
print(f"Redis initialization error: {e}")
else:
print("fastapi-cache2 is not installed; Redis cache is disabled.")
yield
if redis:
await redis.close()
app = FastAPI(lifespan=lifespan, redirect_slashes=False)
@app.middleware("http")
async def add_trace_id(request, call_next):
trace_id = request.headers.get("X-Trace-Id") or str(uuid.uuid4())
request.state.traceId = trace_id
set_request_context(
trace_id=trace_id,
path=request.url.path,
method=request.method,
)
started_at = perf_counter()
try:
try:
response = await call_next(request)
except Exception:
duration_ms = int((perf_counter() - started_at) * 1000)
http_logger.exception(
"http_request_failed",
extra={
"event": "http_request_failed",
"trace_id": trace_id,
"method": request.method,
"path": request.url.path,
"duration_ms": duration_ms,
},
)
raise
duration_ms = int((perf_counter() - started_at) * 1000)
http_logger.info(
"http_request",
extra={
"event": "http_request",
"trace_id": trace_id,
"method": request.method,
"path": request.url.path,
"status_code": response.status_code,
"duration_ms": duration_ms,
},
)
response.headers["X-Trace-Id"] = trace_id
return response
finally:
clear_log_context()
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(Exception, unhandled_exception_handler)
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(health_router, prefix="/api")
app.include_router(actions_router, prefix="/api")
app.include_router(capabilities_router, prefix="/api")
app.include_router(pipelines_router, prefix="/api")
app.include_router(executions_router, prefix="/api")
if auth_router is not None and login_router is not None:
app.include_router(auth_router, prefix="/api")
app.include_router(login_router, prefix="/api")
if all(
router is not None
for router in (
get_me_router,
list_users_router,
update_me_router,
update_user_router,
update_password_router,
delete_user_router,
)
):
app.include_router(get_me_router, prefix="/api/users")
app.include_router(list_users_router, prefix="/api/users")
app.include_router(update_me_router, prefix="/api/users")
app.include_router(update_user_router, prefix="/api/users")
app.include_router(update_password_router, prefix="/api/users")
app.include_router(delete_user_router, prefix="/api/users")
Instrumentator().instrument(app).expose(app)