Spaces:
Running
Running
File size: 6,436 Bytes
bb74b82 b4cc1c5 bb74b82 b4cc1c5 bb74b82 b4cc1c5 bb74b82 b4cc1c5 bb31829 de46be0 890e849 bb74b82 890e849 bb74b82 b4cc1c5 0c91c9b f14102c bb74b82 ecbec06 f14102c ecbec06 f14102c c9d381c 243a84a b4cc1c5 bb74b82 b4cc1c5 890e849 bb74b82 890e849 781c660 bb74b82 890e849 0c91c9b 890e849 0c91c9b de46be0 890e849 bb74b82 b4cc1c5 bb74b82 b4cc1c5 890e849 b4cc1c5 781c660 d86e268 c9d381c bb74b82 2ff13b5 b4cc1c5 bb74b82 f14102c ecbec06 f14102c ecbec06 f14102c bb74b82 f14102c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | """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
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@asynccontextmanager
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)
|