Spaces:
Sleeping
Sleeping
File size: 14,148 Bytes
49cc409 628d92a 49cc409 628d92a bff1348 49cc409 cecde1f bff1348 49cc409 b283975 49cc409 89d5b21 49cc409 bff1348 49cc409 bff1348 49cc409 a0dc23e de46be0 49cc409 bff1348 cecde1f d0a3fab cecde1f bff1348 49cc409 89d5b21 6857b1f 89d5b21 bff1348 7afb12e 35001ff d733b48 6857b1f d733b48 bff1348 b4cc1c5 d0a3fab bff1348 001e605 bff1348 628d92a bff1348 628d92a bff1348 49cc409 05c538b 49cc409 628d92a 49cc409 8c509eb a0dc23e f53c0aa a0dc23e b6bdecc 76e79a0 a0dc23e 8c509eb 49cc409 4255304 71f166b 89d5b21 563a0f0 89d5b21 563a0f0 ce30e80 71f166b 89d5b21 6857b1f 89d5b21 6857b1f 89d5b21 a76711b | 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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | [build-system]
# Sprint A9 (M-5) : setuptools_scm dérive la version du tag git le
# plus proche. Le pipeline release.yml tag ``v1.2.3`` produit donc
# un wheel ``picarones-1.2.3-py3-none-any.whl`` sans toucher à
# pyproject.toml. Pour les builds non-tag (PR, dev) : version
# pseudo ``1.2.4.dev3+g<sha>``.
requires = ["setuptools>=68.0", "wheel", "setuptools_scm[toml]>=8.0"]
build-backend = "setuptools.build_meta"
[project]
name = "picarones"
# Sprint A9 (M-5) : ``version`` est désormais dynamique, dérivé du
# tag git via setuptools_scm. Voir [tool.setuptools_scm] plus bas.
dynamic = ["version"]
description = "Plateforme de comparaison de moteurs OCR/HTR pour documents patrimoniaux"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "Apache-2.0" }
authors = [{ name = "maribakulj" }]
keywords = ["ocr", "htr", "patrimoine", "benchmark", "cer", "wer", "gallica", "escriptorium", "iiif"]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Text Processing :: Linguistic",
"Intended Audience :: Science/Research",
"Natural Language :: French",
]
dependencies = [
"click>=8.1.0",
"jiwer>=3.0.0",
"Pillow>=10.0.0",
"pyyaml>=6.0.0",
"pytesseract>=0.3.10",
"tqdm>=4.66.0",
"numpy>=1.24.0",
"jinja2>=3.1.0",
# XML parsing sécurisé contre les attaques XXE / Billion Laughs.
# Utilisé par ``picarones.web.corpus_utils`` pour le parsing ALTO/PAGE
# quand un utilisateur uploade un corpus XML.
"defusedxml>=0.7.1",
]
[project.urls]
Homepage = "https://github.com/maribakulj/Picarones"
Documentation = "https://github.com/maribakulj/Picarones/blob/main/docs/index.md"
Repository = "https://github.com/maribakulj/Picarones"
Changelog = "https://github.com/maribakulj/Picarones/blob/main/CHANGELOG.md"
"Bug Tracker" = "https://github.com/maribakulj/Picarones/issues"
[project.optional-dependencies]
# Développement et tests.
# pytest-timeout (Sprint A1) garantit qu'aucun test individuel ne hang la CI
# au-delà de la limite définie dans [tool.pytest.ini_options].
# mypy (Sprint A1, M-4) : type-check strict sur picarones/domain/ + lax ailleurs.
# bandit (Sprint A1, B-7) : scanner sécurité statique du code Python.
# pip-audit (Sprint A1, B-7) : détection des CVE des dépendances installées.
dev = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-timeout>=2.3.0",
"httpx>=0.27.0",
"fastapi>=0.111.0",
"uvicorn[standard]>=0.29.0",
"python-multipart>=0.0.9",
"mypy>=1.10.0",
"bandit>=1.7.0",
"pip-audit>=2.7.0",
]
# Interface web FastAPI
web = ["fastapi>=0.111.0", "uvicorn[standard]>=0.29.0", "httpx>=0.27.0", "python-multipart>=0.0.9"]
# Tests statistiques avancés (Wilcoxon exact, Friedman chi² exact, Nemenyi)
# Sinon fallback pur Python (approximations normale / Wilson-Hilferty).
stats = ["scipy>=1.11.0"]
# Extracteurs d'entités nommées (Sprint 40 — A.II.1.a du plan d'évolution).
# Sans cet extra, picarones.evaluation.metrics.ner_backends.SpacyEntityExtractor tombe
# en mode dégradé silencieux et le runner saute le calcul NER.
ner = ["spacy>=3.7.0"]
# Import HuggingFace Datasets
hf = ["datasets>=2.19.0"]
# Site documentation auto-généré (mkdocstrings + mkdocs-material).
# Build : ``mkdocs build`` ; serve : ``mkdocs serve``. La référence
# d'API ``docs/api/`` est régénérée à chaque build depuis les
# docstrings — pas de drift possible avec le code.
docs = [
"mkdocs>=1.6.0",
"mkdocs-material>=9.5.0",
"mkdocstrings[python]>=0.25.0",
]
# Moteurs OCR optionnels
pero = ["pero-ocr>=0.1.0"]
kraken = ["kraken>=4.0.0"]
# Adaptateurs LLM
llm = [
"openai>=1.0.0",
"anthropic>=0.20.0",
"mistralai>=1.0.0",
]
# OCR cloud APIs
ocr-cloud = [
"google-cloud-vision>=3.0.0",
"boto3>=1.34.0",
"azure-ai-formrecognizer>=3.3.0",
]
# Sprint A9 (m-16) — les anciens placeholders ``[historical]`` et
# ``[importers]`` (qui valaient ``[]`` et n'apportaient rien à
# l'installation) sont retirés. La séparation future en packages PyPI
# distincts (``picarones-historical``, ``picarones-importers``) est
# documentée dans ``docs/developer/module-policy.md`` (Sprint 97) et
# n'a plus besoin d'être réservée par un extra vide.
# Installation complète (tous les extras sauf les OCR cloud)
all = [
"picarones[web,hf,llm,dev]",
]
[project.scripts]
picarones = "picarones.interfaces.cli._legacy:cli"
# ──────────────────────────────────────────────────────────────────
# Sprint A9 (M-5) — version dynamique via setuptools_scm.
#
# Comportement :
# - sur un tag ``v1.2.3`` → version ``1.2.3``
# - hors tag (PR, main) → ``1.2.4.dev<N>+g<sha>`` (PEP 440)
# - le ``write_to`` injecte ``picarones/_version.py`` au build, lu
# par ``picarones/__init__.py`` via ``__version__``.
# ``fallback_version`` est utilisé si l'historique git est absent
# (ex : tarball sdist) — doit être maintenu cohérent avec le dernier tag.
# ──────────────────────────────────────────────────────────────────
[tool.setuptools_scm]
write_to = "picarones/_version.py"
fallback_version = "1.0.0"
version_scheme = "release-branch-semver"
local_scheme = "no-local-version"
[tool.setuptools.packages.find]
where = ["."]
include = ["picarones*"]
[tool.setuptools.package-data]
picarones = [
"prompts/*.txt",
"interfaces/web/_legacy/static/*.css",
"interfaces/web/_legacy/static/*.js",
"interfaces/web/_legacy/templates/*.j2",
"interfaces/web/_legacy/templates/*.html",
"report/templates/*.j2",
"report/templates/*.html",
"report/templates/*.css",
"report/templates/*.js",
"report/i18n/*.json",
"data/*.yaml",
"report/glossary/*.yaml",
]
[tool.pytest.ini_options]
testpaths = ["tests"]
# Le repo root dans ``sys.path`` pour que ``tests.fixtures.*`` soit
# importable de manière déterministe sur tous les OS (Linux/macOS/
# Windows) — utilisé par les tests CLI E2E qui résolvent leurs mock
# adapters via dotted path (``importlib.import_module("tests.fixtures.…")``).
pythonpath = ["."]
# Exclusion par défaut : markers ``network``, ``live`` et
# ``regression`` non sélectionnés. Override en local via
# ``pytest -m network`` ou ``pytest -m live`` (avec env vars /
# binaires correctement configurés). Le marker ``regression``
# (harness legacy ↔ rewrite) est lent ; opt-in via
# ``pytest -m regression`` ou run dédié en CI. ``-m ""`` pour
# tout exécuter.
addopts = "-v --tb=short -m 'not network and not live and not regression'"
# Sprint A1 (M-15) : aucun test individuel ne doit dépasser 5 minutes.
# Mode "thread" car certains tests utilisent ProcessPoolExecutor qui est
# incompatible avec le timeout en mode "signal" sur certaines plateformes.
timeout = 300
timeout_method = "thread"
# Marqueurs personnalisés.
# - ``slow`` : tests longs (corpus de référence) ; désélectionnables
# via ``pytest -m "not slow"`` pour les boucles de dev.
# - ``network`` : tests qui font des requêtes HTTP réelles vers
# l'extérieur (HTR-United GitHub, HuggingFace Hub, Gallica…).
# Exclus du run local par défaut (sandbox sans accès réseau →
# timeout urllib 30s × N tests = suite bloquée). La CI les exécute
# explicitement via ``pytest -m network`` ou en levant l'exclusion
# par défaut.
markers = [
"slow: tests longs (corpus de référence, intégration cloud) ; non bloquants en dev local",
"network: tests qui hit le réseau réel ; exclus par défaut",
"live: tests d'intégration contre vraie API/binaire (Tesseract, Anthropic, OpenAI, Mistral) ; exclus par défaut, opt-in en local via 'pytest -m live'",
"regression: harness de régression legacy ↔ rewrite (tests/regression/legacy_vs_rewrite/) ; exclus par défaut, opt-in via 'pytest -m regression' ou job CI dédié",
]
# ──────────────────────────────────────────────────────────────────
# Sprint A1 (B-8) — seuil minimal de couverture appliqué en CI.
# Le baseline est mesuré en début de sprint puis le plancher est posé
# 2 points en dessous, pour laisser une marge de manœuvre aux PR
# tout en interdisant une dégradation franche.
# ──────────────────────────────────────────────────────────────────
[tool.coverage.run]
source = ["picarones"]
omit = [
"picarones/report/vendor/*", # Chart.js minifié vendoré
"picarones/report/templates/*", # templates Jinja2 + JS, pas du code Python
"*/tests/*",
]
parallel = true
[tool.coverage.report]
# Le seuil est appliqué via la flag CLI ``--cov-fail-under=N`` dans la CI
# (cf. .github/workflows/ci.yml) plutôt qu'ici, pour permettre aux
# développeurs de lancer ``pytest --cov`` localement sans échec sur les
# fichiers qu'ils ne touchent pas.
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"if TYPE_CHECKING:",
"if __name__ == .__main__.:",
]
# ──────────────────────────────────────────────────────────────────
# Sprint A1 (M-4) — type-checking gradient.
#
# Stratégie : ``picarones.domain`` est en mode ``strict`` car c'est la
# couche 1 du rewrite (types purs, Pydantic + stdlib only) — l'API
# publique stable et la base de l'architecture concentrique. Les autres
# couches passent en mode permissif (``ignore_missing_imports`` + pas
# de strict) — au fur et à mesure des sprints suivants, on monte le
# niveau (Sprint A11 resserre `picarones.measurements`).
# Note : avant les Lots A-G de la migration legacy → rewrite, ce strict
# ciblait ``picarones.core`` ; ce paquet a été entièrement supprimé,
# son contenu est désormais dans ``domain/``, ``evaluation/`` et ``formats/``.
# ──────────────────────────────────────────────────────────────────
[tool.mypy]
python_version = "3.11"
ignore_missing_imports = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
no_implicit_optional = true
# Les imports vers les autres cercles sont suivis silencieusement
# pour éviter de propager les erreurs des cercles non encore typés.
# Sprint A11 resserrera progressivement.
follow_imports = "silent"
[[tool.mypy.overrides]]
module = "picarones.domain.*"
strict = true
# A1 baseline : ces deux checks pré-existants génèrent ~70 % des erreurs
# (annotations ``dict``/``tuple`` sans paramètres génériques, retours typés
# ``Any``). Plutôt que de les fixer en bloc dans A1 et risquer une
# régression, on les laisse explicitement désactivés et on les ré-active
# en Sprint A11 (durcissement progressif du type-checking).
disallow_any_generics = false
warn_return_any = false
# ──────────────────────────────────────────────────────────────────
# Sprint A1 (B-7) — configuration bandit (scan sécurité statique).
#
# Politique : on refuse tout finding HIGH/CRITICAL en CI. Les MEDIUM
# documentés ci-dessous comme "accepté" font l'objet d'un suivi explicite
# (sprint cible mentionné).
#
# Exclusions documentées :
# - B101 (assert_used) : pytest utilise systématiquement ``assert`` ;
# - B105/B106 (hardcoded_password) : nos fixtures utilisent des chaînes
# ``"password"`` dans des contextes purement de test ;
# - B310 (urllib_urlopen) : tous nos appels ``urllib.urlopen`` ciblent
# des endpoints HTTPS connus (Mistral, Google Vision, Azure DI,
# Gallica, HF Hub, eScriptorium, Ollama). Un audit ligne par ligne
# est tracé dans docs/audits/security-urllib-audit.md ;
# - B608 (hardcoded_sql_expressions) : deux occurrences en
# ``measurements/history.py:341`` et ``web/jobs.py:235`` ; la seconde
# est un faux positif vérifié (audit institutional-readiness §6 F-1),
# la première utilise une whitelist de colonnes documentée ;
# - B615 (huggingface_unsafe_download) : à corriger en pinant la
# ``revision`` dans extras/importers/huggingface.py — Sprint A5 ;
# - B701 (jinja2_autoescape_false) : décision de design pré-existante
# (cf. report/generator.py:606-611) ; les variables injectées sont
# pré-échappées par les modules de rendu via ``html.escape``.
# Refactor à effectuer dans le scope a11y (Sprint A6 ou A7) en
# passant à ``select_autoescape`` + marquage ``|safe`` explicite des
# blocs JSON/SVG.
# ──────────────────────────────────────────────────────────────────
[tool.bandit]
exclude_dirs = ["tests", "picarones/report/vendor"]
skips = ["B101", "B105", "B106", "B310", "B608", "B615", "B701"]
[tool.ruff]
# Configuration centralisée pour que `ruff check`, `make lint` et le job CI
# produisent exactement les mêmes résultats sans flags en ligne de commande.
line-length = 100
target-version = "py311"
[tool.ruff.lint]
# E/W = pycodestyle, F = pyflakes. On conserve les mêmes règles que le CI
# d'origine (avant Sprint 22), qui excluait les lignes longues (E501) et les
# imports non-top (E402, parfois utiles pour imports conditionnels).
select = ["E", "W", "F"]
ignore = ["E501", "E402"]
|