import logging import sys from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import select from app.database import async_session, engine, Base from app.models import Material from app.seed.materials import MATERIALS from app.routers import calculate, materials, orders, ai_advisor # Configure logging logging.basicConfig( level=logging.DEBUG, format="%(asctime)s | %(levelname)-8s | %(name)-30s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", stream=sys.stdout, ) logger = logging.getLogger("app.main") # Reduce noise from third-party libs logging.getLogger("uvicorn.access").setLevel(logging.INFO) logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.INFO) logging.getLogger("httpcore").setLevel(logging.INFO) logging.getLogger("trimesh").setLevel(logging.WARNING) @asynccontextmanager async def lifespan(app: FastAPI): logger.info("=== Application startup ===") logger.info("Creating database tables...") async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) logger.info("Database tables created successfully") logger.info("Checking seed data...") async with async_session() as session: result = await session.execute(select(Material).limit(1)) if result.scalar_one_or_none() is None: logger.info("No materials found, seeding %d materials...", len(MATERIALS)) for mat_data in MATERIALS: session.add(Material(**mat_data)) logger.debug(" Seeded material: %s", mat_data["name"]) await session.commit() logger.info("Materials seeded successfully") else: logger.info("Materials already exist, skipping seed") logger.info("=== Application ready ===") yield logger.info("=== Application shutdown ===") app = FastAPI(title="3D Print Calculator", version="1.0.0", lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.middleware("http") async def log_requests(request: Request, call_next): logger.info("--> %s %s (client: %s)", request.method, request.url.path, request.client.host if request.client else "unknown") logger.debug(" Headers: %s", dict(request.headers)) logger.debug(" Query params: %s", dict(request.query_params)) response = await call_next(request) logger.info("<-- %s %s -> %d", request.method, request.url.path, response.status_code) return response app.include_router(calculate.router, prefix="/api") app.include_router(materials.router, prefix="/api") app.include_router(orders.router, prefix="/api") app.include_router(ai_advisor.router, prefix="/api") @app.get("/api/health") async def health(): logger.debug("Health check requested") return {"status": "ok"}