Spaces:
Sleeping
Sleeping
File size: 12,799 Bytes
49cc409 628d92a 49cc409 628d92a bff1348 49cc409 cecde1f bff1348 49cc409 b283975 49cc409 89d5b21 49cc409 bff1348 49cc409 bff1348 49cc409 a0dc23e de46be0 49cc409 bff1348 cecde1f bff1348 49cc409 89d5b21 bff1348 7afb12e 35001ff d733b48 bff1348 b4cc1c5 bff1348 001e605 bff1348 628d92a bff1348 628d92a bff1348 49cc409 628d92a 49cc409 8c509eb a0dc23e b914841 a0dc23e 89d5b21 b6bdecc 76e79a0 a0dc23e 8c509eb 49cc409 4255304 563a0f0 89d5b21 563a0f0 89d5b21 563a0f0 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 | [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/INSTALL.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/core/ + 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.core.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"]
# 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.cli: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",
"web/static/*.css",
"web/static/*.js",
"web/templates/*.j2",
"web/templates/*.html",
"report/templates/*.j2",
"report/templates/*.html",
"report/templates/*.css",
"report/templates/*.js",
"report/i18n/*.json",
"measurements/narrative/templates/*.yaml",
"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 : marker network non sélectionné. Override via
# ``pytest -m network`` (CI réseau-friendly) ou ``pytest -m ""``.
addopts = "-v --tb=short -m 'not network'"
# 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",
]
# ──────────────────────────────────────────────────────────────────
# 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.core`` est en mode ``strict`` car c'est la
# couche la plus stable et l'API publique. Les autres cercles 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`).
# ──────────────────────────────────────────────────────────────────
[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.core.*"
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"]
|