Spaces:
Running
Running
Claude
chore(quality): ratchet ruff (ISC/FLY/G) + mypy strict formats/ + Protocol NER (P1.3/P1.4)
9efdce8 unverified | [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 = [ | |
| # Sprint S6.3 — toutes les dépendances core ont une borne | |
| # supérieure (caplock major) pour éviter qu'un ``pip install | |
| # picarones`` futur ramène une version 2 d'un paquet et casse | |
| # silencieusement l'API. Les majeures suivantes sont validées | |
| # sur le repo ; passage à une majeure supérieure = sprint | |
| # explicite (mise à jour des bornes ICI + tests d'équivalence). | |
| "click>=8.1.0,<9.0", | |
| "jiwer>=3.0.0,<5.0", | |
| "Pillow>=10.0.0,<13.0", | |
| "pyyaml>=6.0.0,<7.0", | |
| "pytesseract>=0.3.10,<0.4", | |
| "tqdm>=4.66.0,<5.0", | |
| "numpy>=1.24.0,<3.0", | |
| "jinja2>=3.1.0,<4.0", | |
| # XML parsing sécurisé contre les attaques XXE / Billion Laughs. | |
| # Utilisé par ``picarones.interfaces.web.corpus_utils`` pour le parsing | |
| # ALTO/PAGE quand un utilisateur uploade un corpus XML. | |
| "defusedxml>=0.7.1,<0.8", | |
| # Pydantic — types immutables de la couche 1 (``picarones.domain``). | |
| # Déclaré explicitement pour que les installs minimaux (sans | |
| # ``[web]`` qui le ramènerait via FastAPI) bénéficient des modèles | |
| # validés au moment de la déserialisation YAML. | |
| "pydantic>=2.0,<3.0", | |
| ] | |
| [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"] | |
| calamari = ["calamari-ocr>=2.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 **vraiment complète** : tous les extras déclarés | |
| # ci-dessus, OCR cloud et docs inclus. Le nom ``all`` ne doit pas | |
| # tromper le contributeur — si un extra apparaît plus haut, il doit | |
| # apparaître ici. Phase 2.6 de l'audit code-quality (2026-05). | |
| all = [ | |
| "picarones[dev,docs,web,stats,ner,hf,pero,kraken,calamari,llm,ocr-cloud]", | |
| ] | |
| [project.scripts] | |
| picarones = "picarones.interfaces.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", | |
| "data/*.yaml", | |
| "interfaces/web/static/*.css", | |
| "interfaces/web/static/*.js", | |
| "interfaces/web/templates/*.html", | |
| "interfaces/web/templates/*.j2", | |
| "reports/_helpers/vendor/*.js", | |
| "reports/glossary/*.yaml", | |
| "reports/html/templates/*.css", | |
| "reports/html/templates/*.html", | |
| "reports/html/templates/*.j2", | |
| "reports/html/templates/*.js", | |
| "reports/i18n/*.json", | |
| "reports/narrative/templates/*.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`` et ``live`` non | |
| # sélectionnés. Override en local via ``pytest -m network`` ou | |
| # ``pytest -m live`` (avec env vars / binaires correctement | |
| # configurés). ``-m ""`` pour tout exécuter. | |
| addopts = "-v --tb=short -m 'not network and not live'" | |
| # 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'", | |
| ] | |
| # ────────────────────────────────────────────────────────────────── | |
| # 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 (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). | |
| # ────────────────────────────────────────────────────────────────── | |
| [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 couches sont suivis silencieusement | |
| # pour éviter de propager les erreurs des couches non encore typées. | |
| follow_imports = "silent" | |
| # Sprint S3.6 — plugin Pydantic obligatoire pour que ``mypy --strict`` | |
| # sur ``picarones.domain`` reconnaisse les ``BaseModel`` correctement. | |
| # Sans plugin, ``BaseModel`` est traité comme ``Any`` et chaque | |
| # ``class X(BaseModel)`` génère un faux positif | |
| # ``Class cannot subclass "BaseModel"``. | |
| plugins = ["pydantic.mypy"] | |
| [[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 | |
| # Ratchet audit prod P1.3 — ``picarones.formats`` promu strict COMPLET | |
| # (couche pure parsing/sérialisation, petite et sans état). Les 9 | |
| # erreurs (args génériques + retours ``Any``) ont été corrigées, donc | |
| # ``disallow_any_generics``/``warn_return_any`` restent actifs ici | |
| # (contrairement à domain qui les relâche encore). Prochaine cible du | |
| # ratchet : ``pipeline`` puis ``evaluation``. | |
| [[tool.mypy.overrides]] | |
| module = "picarones.formats.*" | |
| strict = true | |
| # ────────────────────────────────────────────────────────────────── | |
| # 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). | |
| # | |
| # Ratchet audit prod P1.4 — durcissement par famille, JAMAIS en bloc. | |
| # Famille activée uniquement si elle est déjà à ZÉRO violation (lock | |
| # sans churn) : | |
| # - ISC : implicit-str-concat — attrape le bug « virgule oubliée | |
| # dans une liste de chaînes → concaténation silencieuse ». | |
| # - FLY : static-join-to-fstring. | |
| # - G : logging-format (pas de f-string/format dans logging.*). | |
| # Backlog ratchet (NON activé — churn massif, à faire 1 famille/sprint | |
| # dédié, avec baseline) : UP≈736, RUF≈498, TRY≈368, I≈95, PERF≈47, | |
| # B≈34, ARG≈32, SIM≈25, C4≈15, RET≈11, PTH≈9. | |
| select = ["E", "W", "F", "ISC", "FLY", "G"] | |
| ignore = ["E501", "E402"] | |