from __future__ import annotations import hashlib import json import time import uuid from pathlib import Path from fastapi import FastAPI, File, Request, UploadFile from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from pageparse.config import settings from pageparse.store import Store from pageparse.telemetry import get_telemetry app = FastAPI(title="PageParse") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) HERE = Path(__file__).resolve().parent.parent.parent web_static = HERE / "web" / "static" web_templates = HERE / "web" / "templates" if not web_static.exists(): cwd_static = Path.cwd() / "web" / "static" if cwd_static.exists(): web_static = cwd_static if not web_templates.exists(): cwd_templates = Path.cwd() / "web" / "templates" if cwd_templates.exists(): web_templates = cwd_templates if web_static.exists(): app.mount("/static", StaticFiles(directory=str(web_static)), name="static") _store_instance: Store | None = None def _get_store() -> Store: global _store_instance if _store_instance is None: _store_instance = Store() _store_instance.init_db() return _store_instance def _get_ocr(): from pageparse.ocr.handwriting import HandwritingOCR return HandwritingOCR() def _get_printed_ocr(): from pageparse.ocr.printed import PrintedOCR return PrintedOCR() def _get_extractor(): from pageparse.extract import Extractor return Extractor() # Structured logging import structlog logger = structlog.get_logger() # Request tracing TRACING_ENABLED = settings.tracing_enabled # Prometheus metrics if settings.prometheus_enabled: try: from prometheus_client import Counter, Histogram, generate_latest, REGISTRY, CONTENT_TYPE_LATEST from starlette.responses import Response HTTP_REQUESTS = Counter("pageparse_http_requests_total", "Total HTTP requests", ["method", "endpoint", "status"]) HTTP_REQUEST_DURATION = Histogram("pageparse_http_request_duration_seconds", "HTTP request duration", ["method", "endpoint"]) PROCESSED_SOURCES = Counter("pageparse_processed_sources_total", "Total processed sources", ["source_type", "status"]) OCR_INFERENCES = Counter("pageparse_ocr_inferences_total", "Total OCR inferences", ["ocr_type"]) except ImportError: settings.prometheus_enabled = False @app.middleware("http") async def add_tracing_and_metrics(request: Request, call_next): request_id = str(uuid.uuid4())[:8] request.state.request_id = request_id start_time = time.time() response = await call_next(request) duration = time.time() - start_time status_code = response.status_code if settings.prometheus_enabled: try: HTTP_REQUESTS.labels(method=request.method, endpoint=request.url.path, status=status_code).inc() HTTP_REQUEST_DURATION.labels(method=request.method, endpoint=request.url.path).observe(duration) except Exception: pass logger.info( "request", request_id=request_id, method=request.method, path=request.url.path, status=status_code, duration_ms=round(duration * 1000), ) response.headers["X-Request-ID"] = request_id return response if settings.prometheus_enabled: @app.get("/metrics") async def metrics(): return Response(content=generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST) @app.get("/", response_class=HTMLResponse) async def index() -> str: static_index = web_static / "index.html" if static_index.exists(): html = static_index.read_text(encoding="utf-8") btn = ( '' '📊 DSA Visualizer' ) close_tag = '