Spaces:
Running
Running
| """Fin-DataPilot FastAPI application entrypoint. | |
| Triggered by the user adding HF_SSH_PRIVATE_KEY to GitHub Secrets. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from collections.abc import AsyncIterator | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import ORJSONResponse | |
| from slowapi import Limiter, _rate_limit_exceeded_handler | |
| from slowapi.errors import RateLimitExceeded | |
| from slowapi.middleware import SlowAPIMiddleware | |
| from slowapi.util import get_remote_address | |
| from app import __version__ | |
| from app.api import agent, auth, health, sessions, skills | |
| from app.config import get_settings | |
| from app.db_init import init_db | |
| from app.skills import registry as _skills_registry # noqa: F401 — trigger registration | |
| from app.skills.user_uploads import load_uploaded_skills_at_startup | |
| from app.utils.trace import setup_logging | |
| logger = logging.getLogger(__name__) | |
| async def lifespan(_app: FastAPI) -> AsyncIterator[None]: | |
| settings = get_settings() | |
| setup_logging(settings.log_level) | |
| await init_db() | |
| # After the 4 built-in skills have registered, re-import any | |
| # previously uploaded skills so they survive container restarts. | |
| n_loaded = load_uploaded_skills_at_startup() | |
| if n_loaded: | |
| logger.info( | |
| "Loaded %d uploaded skill(s) from %s", | |
| n_loaded, | |
| settings.user_skills_dir, | |
| ) | |
| yield | |
| def create_app() -> FastAPI: | |
| settings = get_settings() | |
| app = FastAPI( | |
| title="Fin-DataPilot", | |
| version=__version__, | |
| description="Natural-language financial data agent platform", | |
| default_response_class=ORJSONResponse, | |
| lifespan=lifespan, | |
| ) | |
| # Fail loudly before serving any request with an unsigned identity in | |
| # production. Accessing the property performs the environment check. | |
| _ = settings.effective_auth_secret | |
| # CORS: credentials are not used (the API uses bearer headers), so don't | |
| # opt browsers into cross-origin cookie semantics. | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=settings.cors_origins_list, | |
| allow_credentials=False, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| expose_headers=["X-Trace-Id"], | |
| ) | |
| # Rate limiting (per remote IP) | |
| limiter = Limiter(key_func=get_remote_address) | |
| app.state.limiter = limiter | |
| app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # type: ignore[arg-type] | |
| app.add_middleware(SlowAPIMiddleware) | |
| # Routers | |
| app.include_router(health.router, prefix="/api", tags=["health"]) | |
| app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) | |
| app.include_router(skills.router, prefix="/api", tags=["skills"]) | |
| app.include_router(sessions.router, prefix="/api", tags=["sessions"]) | |
| app.include_router(agent.router, prefix="/api/agent", tags=["agent"]) | |
| return app | |
| app = create_app() | |