Files

124 lines
3.7 KiB
Python
Raw Permalink Normal View History

2026-04-28 10:41:48 +08:00
import logging
import sys
from contextlib import asynccontextmanager
2026-04-28 10:41:48 +08:00
from pathlib import Path
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from loguru import logger
from app.api import router as api_router
from app.config import settings
from app.database import check_database, engine
from app.dependencies import build_container
2026-04-28 10:41:48 +08:00
def _configure_uvicorn_access_log_filters() -> None:
"""第三方或 Demo 若轮询 pending-confirmation无条目时 404 为常态;压低 uvicorn access 刷屏。"""
class _SuppressPendingPoll404(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
try:
msg = record.getMessage()
except Exception:
return True
if "/pending-confirmation" in msg and "GET" in msg and " 404 " in msg:
return False
return True
logging.getLogger("uvicorn.access").addFilter(_SuppressPendingPoll404())
def configure_logging() -> None:
"""集中配置 loguru sink由 create_app 显式调用,避免 import-time 副作用。"""
logger.remove()
logger.add(
sys.stderr,
format=(
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan> - <level>{message}</level>"
),
)
@asynccontextmanager
async def lifespan(app: FastAPI):
await check_database()
logger.info(
"Database connection verified; ensure schema is applied with "
"`alembic upgrade head` before serving traffic"
)
container = build_container(settings)
app.state.container = container
await container.start()
try:
yield
finally:
await container.shutdown()
await engine.dispose()
logger.info("Database engine disposed")
def create_app() -> FastAPI:
configure_logging()
2026-04-28 10:41:48 +08:00
_configure_uvicorn_access_log_filters()
application = FastAPI(
title="Operation Room Monitor",
lifespan=lifespan,
)
if settings.demo_cors_enabled:
origins = settings.parsed_demo_cors_origins()
if origins:
application.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=origins != ["*"],
allow_methods=["*"],
allow_headers=["*"],
)
logger.info("CORS enabled for demo client; origins={}", origins)
application.include_router(api_router)
if settings.demo_orchestrator_enabled:
from app.routers import demo_orch
application.include_router(demo_orch.router)
logger.info(
"Demo orchestrator enabled: POST /internal/demo/orchestrate-and-start",
)
else:
logger.info(
"Demo orchestrator disabled (DEMO_ORCHESTRATOR_ENABLED=false): "
"GET /internal/demo/orchestrator-status for status; "
"POST /internal/demo/orchestrate-and-start is not registered",
)
return application
app = create_app()
def main() -> None:
2026-04-28 10:41:48 +08:00
root = Path(__file__).resolve().parent
kwargs: dict = {
"host": settings.server_host,
"port": settings.server_port,
}
if settings.server_reload:
kwargs["reload"] = True
kwargs["reload_dirs"] = [str(root)]
kwargs["reload_includes"] = ["*.py"]
kwargs["reload_excludes"] = [
"**/.venv/**",
"**/__pycache__/**",
"**/web/**",
"**/scripts/demo_client/**",
"**/.git/**",
]
uvicorn.run("main:app", **kwargs)
if __name__ == "__main__":
main()