Files
ros_control/app/main.py
T

71 lines
2.7 KiB
Python
Raw Normal View History

import asyncio
from contextlib import asynccontextmanager, suppress
from pathlib import Path
from botocore.exceptions import BotoCoreError, ClientError
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from app import process_lock
from app.api import v1
from app.config import get_settings, insecure_settings
from app.db import init_db
from app.services import backups, jobs, poller, rotation
from app.ui import routes as ui
@asynccontextmanager
async def lifespan(app: FastAPI):
problems = insecure_settings(get_settings())
if problems:
raise RuntimeError("Небезопасная конфигурация: " + "; ".join(problems) + "; см. README «Настройки»")
process_lock.acquire(get_settings().database_url) # один процесс на файловую БД (--workers 1)
try:
init_db()
jobs.fail_stale_jobs()
task = asyncio.create_task(poller.run()) if get_settings().poll_interval > 0 else None
reconcile = asyncio.create_task(backups.reconcile()) # связать файлы бакета с метаданными (best-effort)
rotator = asyncio.create_task(rotation.run()) # ротация журнала по настройкам
yield
for t in (task, reconcile, rotator):
if t:
t.cancel()
with suppress(asyncio.CancelledError):
await t
finally:
process_lock.release()
def create_app() -> FastAPI:
app = FastAPI(title="ros_control", lifespan=lifespan)
app.add_middleware(
SessionMiddleware, secret_key=get_settings().session_secret,
same_site="strict", https_only=get_settings().session_cookie_secure,
)
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "ui" / "static"), name="static")
app.include_router(v1.router)
app.include_router(ui.router)
@app.exception_handler(ui.LoginRequired)
async def _login(_: Request, e: ui.LoginRequired):
return RedirectResponse("/login", status_code=303)
@app.exception_handler(LookupError)
async def _404(_: Request, e: LookupError):
return JSONResponse({"detail": str(e).strip("'\"")}, status_code=404)
@app.exception_handler(ValueError)
async def _400(_: Request, e: ValueError):
return JSONResponse({"detail": str(e)}, status_code=400)
@app.exception_handler(BotoCoreError)
@app.exception_handler(ClientError)
async def _502(_: Request, e: Exception):
return JSONResponse({"detail": f"S3: {e}"}, status_code=502)
return app
app = create_app()