markitdown / app /main.py
free-apis-collection's picture
Deploy 7de0c2ab99518aefca3cce1496d6b74cae82705f
d77da37 verified
Raw History Blame Contribute Delete
5.85 kB
"""Application entrypoint β€” single monolithic FastAPI app.
Routes are grouped by the deployed service they replace (executor, csv,
db, chatbot, interpreter, embeddings). All request validation, error handling,
CORS and auth wiring lives here.
"""
from __future__ import annotations
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from app.config import settings
from app.core.banner import print_startup_banner
from app.core.deps import ServiceAuth
from app.core.exceptions import register_exception_handlers
from app.core.logging import setup_logging, get_logger
from app.core.pool import shutdown
from app.core.redis import get_redis
# from app.monitoring.manager import manager as ws_manager
# from app.monitoring.metrics import get_snapshot, system_sampler
# from app.monitoring.middleware import MonitoringMiddleware
# from app.monitoring.router import router as monitoring_router
from app.routers import chatbot, csv_service, db, embeddings, executor, files, interpreter, report_jobs, db_sync_jobs
from app.services.db.mongo import close_mongo_clients
from app.services.db.sql import close_db_pools
from app.services.cron_scheduler import start_cron_scheduler
setup_logging("DEBUG" if settings.debug else "INFO")
logger = get_logger(__name__)
# Extreme top: ASCII banner β†’ details β†’ huge horizontal line, then the app starts.
print_startup_banner()
async def _self_ping() -> None:
"""Background self-ping: keeps the HF Space warm via its https /ping URL.
Sends Bearer <HF_TOKEN (fallback app auth token)>. /ping itself is
public, so the header is ignored today β€” it only matters if /ping ever
becomes protected. Runs every SELF_PING_INTERVAL seconds.
"""
if not settings.self_ping_enabled:
logger.info("self-ping disabled (SELF_PING_ENABLED=false) β€” background task exiting")
return
url = settings.effective_self_ping_url
if not url or not url.startswith("https://"):
logger.warning("self-ping enabled but no https URL resolved (set SELF_PING_URL or SPACE_ID) β€” skipping")
return
interval = int(settings.self_ping_interval or 600)
token = settings.effective_ping_token
headers = {"Authorization": f"Bearer {token}"} if token else None
logger.info("self-ping enabled: url=%s interval=%ss auth=%s", url, interval, "Bearer ***" if token else "none")
import httpx
while True:
try:
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.get(url, headers=headers)
if resp.status_code == 200:
logger.info("self-ping successful: %s", url)
else:
logger.warning("self-ping returned: %s β€” %s", url, resp.status_code)
except Exception as exc: # noqa: BLE001 β€” keep the loop alive
logger.warning("self-ping error: %s", exc)
await asyncio.sleep(interval)
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("app starting", extra={"version": settings.app_version})
# sampler_task = asyncio.create_task(system_sampler())
# broadcast_task = asyncio.create_task(
# ws_manager.broadcast_loop(get_snapshot)
# )
if settings.self_ping_enabled:
logger.info(
"scheduling self-ping task -> %s (every %ss)",
settings.effective_self_ping_url,
settings.self_ping_interval,
)
asyncio.create_task(_self_ping())
else:
logger.info("self-ping disabled (SELF_PING_ENABLED=false) β€” not scheduling")
if settings.cron_enabled:
logger.info(
"scheduling frontend credit crons -> %s/api/cron + %s/api/v1/cron (daily 00:00)",
(settings.frontend_base_url or "").rstrip("/") or "(FRONTEND_BASE_URL missing)",
(settings.frontend_base_url or "").rstrip("/") or "(FRONTEND_BASE_URL missing)",
)
else:
logger.info("frontend credit crons disabled (CRON_ENABLED=false) β€” not scheduling /api/cron nor /api/v1/cron")
cron_scheduler = start_cron_scheduler()
if cron_scheduler is None:
logger.warning("frontend credit cron scheduler NOT running β€” /api/cron and /api/v1/cron will not fire (see reason above)")
yield
if cron_scheduler is not None:
cron_scheduler.shutdown(wait=False)
logger.info("cron scheduler stopped")
# sampler_task.cancel()
# broadcast_task.cancel()
await shutdown()
await close_db_pools()
await close_mongo_clients()
await get_redis().close()
logger.info("app stopped")
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
lifespan=lifespan,
docs_url="/docs" if settings.debug else None,
redoc_url="/redoc" if settings.debug else None,
)
# Monitoring middleware β€” must be first to capture all requests
# app.add_middleware(MonitoringMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
register_exception_handlers(app)
# app.include_router(monitoring_router)
app.include_router(executor.router)
app.include_router(csv_service.router)
app.include_router(db.router)
app.include_router(chatbot.router)
app.include_router(interpreter.router)
app.include_router(embeddings.router)
app.include_router(files.router)
app.include_router(report_jobs.router)
app.include_router(db_sync_jobs.router)
@app.get("/", tags=["meta"])
async def root() -> dict[str, str]:
return {"message": f"{settings.app_name} is running"}
@app.get("/health", tags=["meta"])
async def health() -> dict[str, str]:
return {"status": "ok"}