from __future__ import annotations import asyncio import os import time from contextlib import asynccontextmanager from datetime import datetime, timezone from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from app.config import get_settings from app.core.banner import print_banner from app.core.constants import SUPPORTED_EXTENSIONS from app.core.logger import get_logger from app.api.v1.router import api_v1_router _logger = get_logger(__name__) _settings = get_settings() _START_TIME = time.time() async def _self_ping(): import httpx health_url = "https://aetherbase-llm-ready-data.hf.space/health" while True: try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.get(health_url) if response.status_code == 200: _logger.info("Self-ping successful: %s", health_url) else: _logger.warning("Self-ping returned: %s - %s", health_url, response.status_code) except Exception as exc: _logger.error("Self-ping error: %s", exc) await asyncio.sleep(900) @asynccontextmanager async def lifespan(app: FastAPI): print_banner() _logger.info("Server ready at http://%s:%s", _settings.host, _settings.port) _logger.info("Swagger UI : http://%s:%s/docs", _settings.host, _settings.port) _logger.info("ReDoc : http://%s:%s/redoc", _settings.host, _settings.port) _logger.info("Started at : %s", datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")) asyncio.create_task(_self_ping()) _logger.info("Self-ping task started (every hour)") yield _logger.info("Application shutting down gracefully") def create_application() -> FastAPI: app = FastAPI( title=_settings.app_name, description="Document-to-Markdown and structured data extraction API powered by Microsoft MarkItDown, RapidOCR, and spaCy.", version=_settings.app_version, docs_url="/docs", redoc_url="/redoc", openapi_tags=[ {"name": "Convert", "description": "Single-file and single-URL conversion"}, {"name": "Batch", "description": "Bulk conversion of files and URLs"}, {"name": "System", "description": "Health, info, and supported formats"}, ], lifespan=lifespan, ) app.add_middleware(GZipMiddleware, minimum_size=1000) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) app.include_router(api_v1_router, prefix="/v1") @app.get("/health", include_in_schema=False) async def root_health(): return {"status": "ok", "version": _settings.app_version} @app.get("/ping", include_in_schema=False) async def ping(): return {"message": f"{_settings.app_name} is running..."} _max_workers = min(32, (os.cpu_count() or 1) + 4) _logger.info("Initialized thread pool with %s workers", _max_workers) return app app = create_application()