Spaces:
Running
Running
| """Interface web Picarones β orchestrateur FastAPI. | |
| Lance avec : | |
| .. code-block:: bash | |
| picarones serve [--port 8000] [--host 127.0.0.1] | |
| # ou directement : | |
| uvicorn picarones.web.app:app --reload --port 8000 | |
| L'application est intentionnellement minimaliste : elle se contente | |
| d'instancier ``FastAPI``, de monter le middleware de sΓ©curitΓ© (CSP, | |
| en-tΓͺtes durcis), de servir les fichiers statiques, puis d'inclure | |
| les 11 routers thΓ©matiques de :mod:`picarones.web.routers`. Toute la | |
| logique mΓ©tier vit dans les sous-modules : | |
| - :mod:`picarones.web.state` β singletons et helpers transverses | |
| - :mod:`picarones.web.models` β Pydantic schemas | |
| - :mod:`picarones.web.corpus_utils` β parsing XML, analyse corpus | |
| - :mod:`picarones.web.engine_utils` β dΓ©tection moteurs, capacitΓ©s | |
| - :mod:`picarones.web.benchmark_utils` β workers threadΓ©s | |
| - :mod:`picarones.web.config_utils` β validation config utilisateur | |
| - :mod:`picarones.web.routers.*` β 11 ``APIRouter`` thΓ©matiques | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import sqlite3 | |
| from contextlib import asynccontextmanager | |
| from pathlib import Path | |
| from fastapi import FastAPI | |
| from picarones import __version__ | |
| from picarones.web import state | |
| from picarones.web.routers import ( | |
| benchmark as _benchmark_router, | |
| config as _config_router, | |
| corpus as _corpus_router, | |
| engines as _engines_router, | |
| history as _history_router, | |
| home as _home_router, | |
| importers as _importers_router, | |
| normalization as _normalization_router, | |
| reports as _reports_router, | |
| synthesis as _synthesis_router, | |
| system as _system_router, | |
| ) | |
| from picarones.web.security import csp_middleware, csrf_middleware | |
| _logger = logging.getLogger(__name__) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Lifespan | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def _lifespan(app: FastAPI): | |
| """Hook de dΓ©marrage : marque les jobs orphelins comme ``interrupted``. | |
| Au dΓ©marrage d'un nouveau processus, tous les jobs | |
| encore en statut ``pending`` ou ``running`` en base sont | |
| forcΓ©ment orphelins (le processus prΓ©cΓ©dent est mort sans les | |
| finir). On les bascule en ``interrupted`` pour ne pas laisser | |
| d'Γ©tat mensonger sur le tableau de bord. | |
| """ | |
| # NB : on accède via ``state.JOB_STORE`` (pas un import direct) pour | |
| # que les fixtures de tests qui rΓ©-affectent ``state.JOB_STORE`` Γ | |
| # un store isolΓ© soient effectivement vues par le lifespan. | |
| try: | |
| state.JOB_STORE.mark_orphaned_jobs_interrupted() | |
| except sqlite3.Error as exc: # pragma: no cover β dΓ©fense en profondeur | |
| # Si la base de jobs est cassΓ©e au dΓ©marrage, on log en ``error`` | |
| # (pas ``warning``) β c'est un signal opΓ©rationnel : l'app | |
| # tourne dans un Γ©tat dΓ©gradΓ©, le tableau de bord va Γͺtre incorrect. | |
| _logger.error( | |
| "[jobs] mark_orphaned_jobs_interrupted ΓCHOUΓ β " | |
| "base SQLite inaccessible (%s) : le tableau de bord " | |
| "affichera des jobs zombies.", exc, | |
| ) | |
| yield | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Instance FastAPI | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI( | |
| title="Picarones", | |
| description=( | |
| "Plateforme de comparaison de moteurs OCR/HTR pour documents patrimoniaux" | |
| ), | |
| version=__version__, | |
| docs_url="/api/docs", | |
| redoc_url="/api/redoc", | |
| lifespan=_lifespan, | |
| ) | |
| # Middleware CSP + en-tΓͺtes durcis (X-Frame-Options, etc.) | |
| app.middleware("http")(csp_middleware) | |
| # Sprint A4 (B-11) β protection CSRF, gated par PICARONES_CSRF_REQUIRED. | |
| # En mode public (HuggingFace Space) : bypass complet, pas de cookie. | |
| # En mode institutionnel : double-submit cookie + signature HMAC-SHA256. | |
| # Le middleware s'enregistre toujours mais devient no-op si la variable | |
| # d'env n'est pas activΓ©e β coΓ»t ~0 en mode public. | |
| app.middleware("http")(csrf_middleware) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Fichiers statiques | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _STATIC_DIR = Path(__file__).parent / "static" | |
| if _STATIC_DIR.is_dir(): | |
| from fastapi.staticfiles import StaticFiles | |
| app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static") | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Routers thΓ©matiques | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Ordre indiffΓ©rent fonctionnellement, mais regroupΓ© par domaine | |
| # pour la lisibilitΓ© (info β donnΓ©es β processus β prΓ©sentation). | |
| app.include_router(_system_router.router) | |
| app.include_router(_engines_router.router) | |
| app.include_router(_corpus_router.router) | |
| app.include_router(_normalization_router.router) | |
| app.include_router(_config_router.router) | |
| app.include_router(_synthesis_router.router) | |
| app.include_router(_history_router.router) | |
| app.include_router(_reports_router.router) | |
| app.include_router(_importers_router.router) | |
| app.include_router(_benchmark_router.router) | |
| app.include_router(_home_router.router) | |