""" Main FastAPI application """ import os from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.gzip import GZipMiddleware from fastapi.responses import RedirectResponse from app.core.config import settings from app.api.v1.api import api_router from app.core.database import engine, Base from app.middleware.error_logger import ErrorLoggingMiddleware, start_request_log_flusher from app.models import error_log, request_log, fred_data, filing, finra_short_volume, alpaca_price # Import to register models from app.models import overlay_registry, overlay_raw_event, overlay_feature # Overlay models # Create database tables @asynccontextmanager async def lifespan(app: FastAPI): # Startup - ensure tables exist async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) # Start async batch request log flusher start_request_log_flusher() # Start overlay batch scheduler (graceful no-op if apscheduler not installed) try: from app.services.overlay.scheduler import start_scheduler start_scheduler() except Exception: pass # Start SEC ingest scheduler (Form 4 + 13D/G daily/weekly) try: from app.services.sec_ingest.scheduler import start_sec_ingest_scheduler start_sec_ingest_scheduler() except Exception: pass yield # Shutdown try: from app.services.overlay.scheduler import stop_scheduler stop_scheduler() except Exception: pass try: from app.services.sec_ingest.scheduler import stop_sec_ingest_scheduler stop_sec_ingest_scheduler() except Exception: pass try: from app.core.http_client import close_http_session await close_http_session() except Exception: pass await engine.dispose() # Create FastAPI app app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, openapi_url=f"{settings.API_PREFIX}/openapi.json", docs_url=f"{settings.API_PREFIX}/docs", redoc_url=f"{settings.API_PREFIX}/redoc", lifespan=lifespan ) # Add error logging middleware app.add_middleware(ErrorLoggingMiddleware) # Set up CORS - use configured origins if available, otherwise allow all cors_origins = [str(o) for o in settings.BACKEND_CORS_ORIGINS] if settings.BACKEND_CORS_ORIGINS else ["*"] app.add_middleware( CORSMiddleware, allow_origins=cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # GZip compression for responses > 1KB app.add_middleware(GZipMiddleware, minimum_size=1000) # Include API router app.include_router(api_router, prefix=settings.API_PREFIX) # Root — redirect to Swagger UI @app.get("/", include_in_schema=False) async def root_redirect(): return RedirectResponse(url=f"{settings.API_PREFIX}/docs") # Additional metadata for OpenAPI app.openapi_tags = [ { "name": "health", "description": "Health check endpoints" }, { "name": "financial", "description": "Financial data retrieval endpoints" }, { "name": "price", "description": "Price data endpoints (OHLCV)" }, { "name": "news", "description": "News and social media endpoints" }, { "name": "metadata", "description": "Data catalog and metadata endpoints" }, { "name": "filings", "description": "SEC filings search, document listing, and exhibit extraction (8-K, 6-K, 20-F, 40-F)" }, { "name": "etf", "description": "ETF holdings endpoints" }, { "name": "alpaca", "description": "Alpaca Market Data endpoints (OHLCV bars, connection status)" }, { "name": "finra", "description": "FINRA RegSHO short sale volume data (ingest, query, ratio history)" }, { "name": "admin", "description": "Administrative endpoints (migration, etc.)" }, { "name": "overlay", "description": "Attention Overlay - retail investor interest, media diffusion, crowding signals" }, { "name": "overlay-admin", "description": "Overlay administrative endpoints (pipeline trigger, health, job log)" }, { "name": "screener", "description": "Stock screener — condition-based filtering by market cap, volume, price, P/E, sector, exchange" }, { "name": "stocks", "description": "Stock market data — most active, 52-week gainers, trending, and index constituents (S&P 500 / Nasdaq 100)" }, { "name": "attention", "description": "Attention signals — Wikipedia pageview spikes and GDELT news article counts for event-centric backtesting" }, { "name": "attention-admin", "description": "Attention administrative endpoints — entity resolution, Wikipedia and GDELT data collection" }, { "name": "database", "description": "Database inspection — record counts, date ranges, raw data browsing, and ETF snapshot history" }, { "name": "fred", "description": "FRED (Federal Reserve Economic Data) — macroeconomic series via FRED API proxy" }, { "name": "ownership", "description": "SEC 13D/13G activist ownership events — activist filings, active positions (PIT-safe)" }, { "name": "error-logs", "description": "Error log management — browse and clear server-side error records" }, { "name": "request-logs", "description": "Request log management — browse API request history and latency records" } ] if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host="0.0.0.0", port=settings.API_PORT, reload=settings.DEBUG )