Spaces:
Sleeping
Sleeping
| # .github/workflows/ci.yml β Picarones CI/CD | |
| # | |
| # Pipeline GitHub Actions (mis Γ jour Sprint A1 β Hardening CI) : | |
| # - Tests sur Python 3.11 / 3.12 / 3.13 (3.13 informationnel, 6 mois) | |
| # - Linux, macOS, Windows | |
| # - Couverture exigΓ©e >= 85 % (--cov-fail-under, plancher 2 pts sous baseline 87 %) | |
| # - Timeout pytest 5 min par test individuel (pytest-timeout, mode thread) | |
| # - Type-check mypy (strict sur picarones/domain/, lax ailleurs β durci en A11) | |
| # - Scanners sΓ©curitΓ© : bandit (statique) + pip-audit (CVE deps) + trivy (image) | |
| # - Build de la distribution Python | |
| # - VΓ©rification de l'exΓ©cutable demo | |
| name: CI | |
| on: | |
| push: | |
| branches: [main, develop, "feature/**", "claude/**"] | |
| pull_request: | |
| branches: [main, develop] | |
| workflow_dispatch: # DΓ©clenchement manuel | |
| permissions: | |
| contents: read | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 1 : Tests unitaires et d'intΓ©gration | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| jobs: | |
| tests: | |
| name: Tests Python ${{ matrix.python-version }} / ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| # ``CODECOV_TOKEN`` au niveau JOB plutΓ΄t que step : nΓ©cessaire | |
| # pour que ``env.CODECOV_TOKEN`` soit visible dans le ``if:`` de | |
| # l'Γ©tape Codecov (le ``env`` d'un step n'est PAS rΓ©solu avant | |
| # l'Γ©valuation du ``if`` de ce mΓͺme step). | |
| env: | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| # 3.13 ajoutΓ© Sprint A1 (item m-8). Reste informationnel | |
| # (continue-on-error sur Linux uniquement) pendant 6 mois pour | |
| # tracker la compat sans bloquer. | |
| python-version: ["3.11", "3.12", "3.13"] | |
| include: | |
| - python-version: "3.13" | |
| os: ubuntu-latest | |
| experimental: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| # ββ Tesseract ββββββββββββββββββββββββββββββββββββββββββββββ | |
| - name: Install Tesseract (Ubuntu) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y tesseract-ocr tesseract-ocr-fra tesseract-ocr-lat | |
| - name: Install Tesseract (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| brew install tesseract tesseract-lang | |
| env: | |
| HOMEBREW_NO_AUTO_UPDATE: "1" | |
| - name: Install Tesseract (Windows) | |
| if: runner.os == 'Windows' | |
| run: | | |
| choco install tesseract -y | |
| echo "C:\Program Files\Tesseract-OCR" >> $env:GITHUB_PATH | |
| shell: pwsh | |
| # ββ DΓ©pendances Python ββββββββββββββββββββββββββββββββββββββ | |
| # Mise Γ jour pip/setuptools/wheel en dΓ©but de job (Sprint A1) pour | |
| # Γ©viter les CVE qui dorment dans les images runner GitHub. | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip setuptools wheel | |
| pip install -e ".[dev,web]" | |
| # ββ Tests βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Sprint A1 : --cov-fail-under=85 (baseline mesurΓ© 87 %, marge 2 pts). | |
| # pytest-timeout est configurΓ© dans pyproject.toml [tool.pytest.ini_options]. | |
| # | |
| # Garde-fous anti-hang : | |
| # | |
| # 1. ``timeout-minutes: 18`` au niveau step : cap dur GitHub si | |
| # tout le reste Γ©choue. DimensionnΓ© pour Windows latest qui | |
| # tournait Γ ~10 min de wall-clock contre ~3 min sur Linux | |
| # (filesystem 3-5x plus lent, pas de timeout intermΓ©diaire | |
| # disponible cΓ΄tΓ© Git-Bash). | |
| # 2. ``timeout`` GNU autour de pytest : SIGTERM Γ 12 minutes, | |
| # SIGKILL 30s après si Python n'a pas obéi. Couvre | |
| # spΓ©cifiquement le cas d'un hang de SHUTDOWN de | |
| # l'interprΓ©teur Python 3.12+ (threads non-daemon, connexions | |
| # sqlite non fermΓ©es, ResourceWarnings β observΓ© sur ubuntu | |
| # 3.12 oΓΉ pytest finit en 3:21 et l'interprΓ©teur reste 12 min | |
| # avant de rendre la main). | |
| # 3. ``-X faulthandler`` : si le hang revient, on aura les stack | |
| # traces de tous les threads dans le log avant le SIGKILL. | |
| # 4. ``PYTHONFAULTHANDLER=1`` redondance ceinture-bretelles. | |
| # | |
| # Le code de retour 124 (SIGTERM par GNU timeout) ou 137 (SIGKILL) | |
| # est traitΓ© comme un Γ©chec normal β on perd l'info pytest mais | |
| # on prΓ©serve la latence de la CI. | |
| # | |
| # Γvolution du budget : `540s` -> `720s` GNU timeout + | |
| # `12 min` -> `18 min` step cap (mai 2026) après que Windows | |
| # latest a hit le cap 12 min et qu'Ubuntu 3.11 a hit les 9 min | |
| # GNU sur la mΓͺme semaine. La cible reste Β« tests passent en | |
| # 3-10 min selon plateforme Β» ; le cap +30s SIGKILL garde la | |
| # CI faillible plutΓ΄t que de coller indΓ©finiment sur un hang. | |
| - name: Run tests | |
| # Sur Python 3.13, on continue malgrΓ© une erreur pour ne pas bloquer | |
| # le merge pendant la fenΓͺtre informationnelle de 6 mois (m-8). | |
| continue-on-error: ${{ matrix.python-version == '3.13' }} | |
| timeout-minutes: 18 | |
| shell: bash | |
| run: | | |
| # ``timeout`` n'est pas standard sur macOS (BSD vs GNU) β on | |
| # dΓ©tecte et on adapte. Sur Windows, le shell bash de | |
| # Git-Bash n'a pas timeout : on retombe sur python direct. | |
| if command -v timeout >/dev/null 2>&1; then | |
| timeout --signal=SIGTERM --kill-after=30 720 \ | |
| python -X faulthandler -m pytest tests/ -q --tb=short --no-header \ | |
| --cov=picarones --cov-report=xml --cov-report=term-missing \ | |
| --cov-fail-under=85 | |
| elif command -v gtimeout >/dev/null 2>&1; then | |
| # macOS Homebrew coreutils. | |
| gtimeout --signal=SIGTERM --kill-after=30 720 \ | |
| python -X faulthandler -m pytest tests/ -q --tb=short --no-header \ | |
| --cov=picarones --cov-report=xml --cov-report=term-missing \ | |
| --cov-fail-under=85 | |
| else | |
| python -X faulthandler -m pytest tests/ -q --tb=short --no-header \ | |
| --cov=picarones --cov-report=xml --cov-report=term-missing \ | |
| --cov-fail-under=85 | |
| fi | |
| env: | |
| PYTHONIOENCODING: utf-8 | |
| PYTHONUTF8: "1" | |
| PYTHONFAULTHANDLER: "1" | |
| # ββ Couverture ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Conditions : | |
| # - ``always()`` : on remonte la couverture MΓME quand pytest a | |
| # Γ©chouΓ© (utile pour suivre la dΓ©rive sur un build cassΓ©). | |
| # - ``runner.os == 'Linux' && python-version == '3.11'`` : un seul | |
| # upload par run pour ne pas saturer le rate limit Codecov. | |
| # - ``env.CODECOV_TOKEN != ''`` : skip si le secret n'est pas | |
| # dΓ©fini (fork PR, environnement de dev local). | |
| # | |
| # Garde-fous : | |
| # - ``timeout-minutes: 5`` : codecov-action v4 a dΓ©jΓ bloquΓ© la CI | |
| # 50+ min en attendant un upload qui n'aboutissait pas. | |
| # - ``fail_ci_if_error: false`` : un Γ©chec d'upload n'invalide | |
| # pas un run de tests valide. | |
| - name: Upload coverage to Codecov | |
| if: always() && runner.os == 'Linux' && matrix.python-version == '3.11' && env.CODECOV_TOKEN != '' | |
| timeout-minutes: 5 | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| token: ${{ env.CODECOV_TOKEN }} | |
| files: coverage.xml | |
| flags: unittests | |
| name: picarones-coverage | |
| fail_ci_if_error: false | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 2 : VΓ©rification du rapport demo | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| demo: | |
| name: Demo end-to-end | |
| runs-on: ubuntu-latest | |
| needs: tests | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Install Tesseract | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y tesseract-ocr tesseract-ocr-fra | |
| - name: Install Picarones | |
| run: pip install -e ".[web]" | |
| - name: Run demo | |
| run: | | |
| picarones demo --docs 12 --output rapport_demo_ci.html \ | |
| --with-history --with-robustness | |
| ls -lh rapport_demo_ci.html | |
| # VΓ©rifier que le fichier est valide et contient les sections attendues | |
| python -c " | |
| content = open('rapport_demo_ci.html').read() | |
| assert 'Picarones' in content, 'Picarones non trouvΓ© dans le rapport' | |
| assert 'CER' in content, 'CER non trouvΓ© dans le rapport' | |
| assert len(content) > 50000, f'Rapport trop petit : {len(content)} octets' | |
| print(f'Rapport OK : {len(content):,} octets') | |
| " | |
| - name: Upload demo report as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: rapport-demo | |
| path: rapport_demo_ci.html | |
| retention-days: 7 | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 3 : Build de la distribution Python | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| build: | |
| name: Build distribution | |
| runs-on: ubuntu-latest | |
| needs: tests | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Install build tools | |
| run: pip install --upgrade build twine | |
| - name: Build wheel and sdist | |
| run: python -m build | |
| - name: Check distribution | |
| run: twine check dist/* | |
| - name: Upload distribution as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-packages | |
| path: dist/ | |
| retention-days: 30 | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 4 : VΓ©rification de la qualitΓ© du code | |
| # La config ruff est centralisΓ©e dans pyproject.toml ([tool.ruff]), | |
| # donc CI, Makefile et invocations locales produisent exactement les | |
| # mΓͺmes rΓ©sultats β pas de divergence possible entre flags. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| lint: | |
| name: Code quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Install ruff | |
| run: pip install ruff | |
| - name: Run ruff | |
| run: ruff check picarones/ tests/ | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 4-ter : Validation Docker compose | |
| # | |
| # Sans ce check, une incohΓ©rence entre ``docker-compose.yml`` et | |
| # ``docker-compose.prod.yml`` (variable non hΓ©ritΓ©e, override de | |
| # port mal formΓ©, conflit de services) ne se voit qu'au | |
| # dΓ©ploiement. ``docker compose config`` rΓ©sout les merges, | |
| # substitue les variables d'env et Γ©choue si la composition est | |
| # invalide β c'est exactement le linter manquant. | |
| # | |
| # Le secret CSRF est forcΓ© sur une valeur factice : ``prod.yml`` | |
| # exige ``${PICARONES_CSRF_SECRET:?...}`` qui ferait Γ©chouer | |
| # ``config`` sans valeur. La valeur n'est jamais utilisΓ©e β on ne | |
| # dΓ©marre aucun container, on valide uniquement le YAML. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| compose-check: | |
| name: Docker compose validation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Validate local compose | |
| run: docker compose -f docker-compose.yml config > /dev/null | |
| - name: Validate local + prod merge | |
| env: | |
| PICARONES_CSRF_SECRET: compose-check-not-a-real-secret | |
| run: | | |
| docker compose \ | |
| -f docker-compose.yml \ | |
| -f docker-compose.prod.yml \ | |
| config > /dev/null | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 4-bis : Sync compteurs README/CLAUDE.md β Phase 2.1 audit | |
| # code-quality (2026-05). Le script gen_readme_tables.py reflète | |
| # le code rΓ©el dans la prose des docs (tableaux Engines/CLI/API + | |
| # compteur de tests). En CI : exit 1 si la doc dΓ©rive. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| sync-counters: | |
| name: Doc counters sync | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Install Picarones (web extras pour ouvrir l'app FastAPI) | |
| run: pip install -e ".[dev,web]" | |
| - name: Vérifier que README.md / CLAUDE.md reflètent le code | |
| run: python scripts/gen_readme_tables.py --check | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 5 : Type-checking β Sprint A1 (item M-4) | |
| # | |
| # mypy est configurΓ© dans pyproject.toml [tool.mypy] : | |
| # - strict sur picarones.domain.* (couche 1 du rewrite, ex-picarones.core) | |
| # - lax ailleurs (follow_imports=silent) | |
| # Deux checks prΓ©-existants dΓ©sactivΓ©s (disallow_any_generics et | |
| # warn_return_any), à ré-activer en Sprint A11 après fix des | |
| # ~100 erreurs baseline. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| typecheck: | |
| name: Type checking (mypy) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip setuptools wheel | |
| pip install -e ".[dev,web,stats]" | |
| - name: Run mypy on picarones/domain (strict) | |
| run: python -m mypy picarones/domain/ | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 6 : SΓ©curitΓ© β Sprint A1 (item B-7) | |
| # | |
| # bandit : scan statique du code Python (HIGH/MEDIUM bloquants). | |
| # pip-audit : CVEs des dΓ©pendances installΓ©es. | |
| # trivy : scan du Dockerfile + image rΓ©sultante (HIGH/CRITICAL bloquants). | |
| # | |
| # Configuration bandit dans pyproject.toml [tool.bandit] avec exclusions | |
| # documentΓ©es (B310, B608, B615, B701 β chacune avec sprint cible). | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| security: | |
| name: Security scanners | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Install scanners | |
| run: | | |
| python -m pip install --upgrade pip setuptools wheel | |
| pip install -e ".[dev,web]" | |
| # bandit : -ll = LOW + above. La config pyproject.toml exclut les | |
| # informationnels acceptΓ©s ; tout HIGH/MEDIUM nouveau bloque. | |
| - name: Run bandit | |
| run: python -m bandit -r picarones/ -ll -c pyproject.toml | |
| # pip-audit : --skip-editable car picarones n'est pas sur PyPI. | |
| # En CI, l'env est frais (pip Γ jour grΓ’ce au step prΓ©cΓ©dent) donc | |
| # les CVEs trouvΓ©es sont des vraies vulnΓ©rabilitΓ©s dΓ©clarΓ©es. | |
| # NB : ``--strict`` + ``--skip-editable`` combinΓ©s transforment le | |
| # skip en erreur (incompatibilitΓ© documentΓ©e du tool). On utilise | |
| # uniquement ``--skip-editable`` ; pip-audit retourne dΓ©jΓ exit 1 | |
| # sur CVE dΓ©tectΓ©e β le job Γ©chouera donc en cas de vraie vulnΓ©rabilitΓ©. | |
| - name: Run pip-audit | |
| run: python -m pip_audit --skip-editable | |
| # trivy : scan du Dockerfile + image rΓ©sultante. Build local pour | |
| # auditer ce qui sera rΓ©ellement dΓ©ployΓ© sur HuggingFace Space. | |
| - name: Build Docker image for scan | |
| run: docker build -t picarones:ci-scan . | |
| - name: Run Trivy vulnerability scanner (image) | |
| uses: aquasecurity/trivy-action@v0.36.0 | |
| with: | |
| image-ref: 'picarones:ci-scan' | |
| format: 'table' | |
| exit-code: '1' | |
| ignore-unfixed: true | |
| severity: 'HIGH,CRITICAL' | |
| vuln-type: 'os,library' | |
| - name: Run Trivy on Dockerfile (config scan) | |
| uses: aquasecurity/trivy-action@v0.36.0 | |
| with: | |
| scan-type: 'config' | |
| scan-ref: 'Dockerfile' | |
| exit-code: '1' | |
| severity: 'HIGH,CRITICAL' | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Job 7 : CI/CD β DΓ©tection de rΓ©gression CER (optionnel) | |
| # CommentΓ© par dΓ©faut β activer si vous avez un corpus de rΓ©fΓ©rence | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # regression-check: | |
| # name: Regression check | |
| # runs-on: ubuntu-latest | |
| # needs: tests | |
| # if: github.event_name == 'pull_request' | |
| # | |
| # steps: | |
| # - name: Checkout | |
| # uses: actions/checkout@v4 | |
| # | |
| # - name: Install | |
| # run: pip install -e . | |
| # | |
| # - name: Run benchmark on reference corpus | |
| # run: | | |
| # picarones run \ | |
| # --corpus ./tests/fixtures/reference_corpus/ \ | |
| # --engines tesseract \ | |
| # --output results_pr.json \ | |
| # --fail-if-cer-above 0.15 # fraction (0.15 = 15 %) | |