Picarones / pyproject.toml
Claude
feat(sprint-A9): release pipeline PyPI + ghcr.io + GitHub Release
628d92a unverified
Raw
History Blame
12.5 kB
[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"]
# 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"]