Files
pyWebLayout/.gitea/workflows/ci.yml
T
dtourolleandClaude Opus 5 767e4c135c build: consolidate packaging on pyproject.toml and correct the dependency set
The project carried three sets of packaging metadata — pyproject.toml
[project], setup.cfg [options] and setup.py kwargs — declaring different
dependencies. pyproject.toml wins under any modern backend, so the wheel
was correct, but the other two said "Pillow, numpy" and reading either
gave the wrong answer about what the library needs.

Reduce setup.cfg to its [flake8] section and setup.py to a setup() shim,
each pointing at pyproject.toml.

Correct the authoritative list while consolidating:

- flask was a runtime dependency but is imported only by the fixture HTTP
  server in tests. Every user was installing Flask, Jinja2, Werkzeug,
  click, itsdangerous and blinker for nothing.
- ebooklib was a runtime dependency and is never imported by the library;
  epub_reader.py uses zipfile + xml.etree directly. Only the tests use it,
  to build EPUB fixtures.
- requests was declared required but concrete/image.py imports it lazily
  and degrades gracefully when absent, so it belongs in an extra.
- requires-python said >=3.6, which cannot be true: the package uses
  dataclasses and `from __future__ import annotations`, both 3.7+, and CI
  tests 3.10/3.12/3.13.

Runtime install drops from 7 direct dependencies to 4. Adds test,
remote-images and dev extras, and a CI step that installs into an empty
venv with only the runtime deps and imports every subpackage — this class
of defect is only caught by installing what you ship.

Verified: runtime-only install imports all subpackages; `.[test]` runs the
full suite, 853 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 12:30:08 +02:00

192 lines
7.4 KiB
YAML

name: Python CI
on:
push:
branches: [ main, master, develop ]
paths-ignore:
- 'coverage*.svg'
- 'README.md'
pull_request:
branches: [ main, master, develop ]
jobs:
test:
runs-on: self-hosted
strategy:
matrix:
python-version: ['3.10', '3.12', '3.13']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Verify declared dependencies are sufficient
run: |
# A clean venv with ONLY the declared runtime deps. If an import here
# fails, install_requires is incomplete and a real `pip install
# pyWebLayout` would fail the same way for a user.
python -m venv /tmp/clean-install
/tmp/clean-install/bin/pip install --upgrade pip
/tmp/clean-install/bin/pip install .
/tmp/clean-install/bin/python -c "
import pyWebLayout.concrete, pyWebLayout.abstract
import pyWebLayout.io.readers.epub_reader
import pyWebLayout.io.readers.html_extraction
import pyWebLayout.layout.ereader_manager
print('clean install imports OK')
"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# Install package in development mode, with the declared dev extra.
# Test dependencies belong in setup.cfg, not in an ad-hoc pip line.
pip install -e '.[dev]'
- name: Download initial failed badges
run: |
echo "Downloading initial failed badges..."
# Create cov_info directory first
mkdir -p cov_info
# Download failed badges as defaults
curl -o cov_info/coverage.svg "https://img.shields.io/badge/coverage-failed-red.svg"
curl -o cov_info/coverage-docs.svg "https://img.shields.io/badge/docs-failed-red.svg"
echo "Initial failed badges created:"
ls -la cov_info/coverage*.svg
- name: Run tests with pytest
id: pytest
continue-on-error: true
run: |
# Run tests with coverage
python -m pytest tests/ -v --cov=pyWebLayout --cov-report=term-missing --cov-report=json --cov-report=html --cov-report=xml
- name: Check documentation coverage
id: docs
continue-on-error: true
run: |
# Generate documentation coverage report
interrogate -v --ignore-init-method --ignore-init-module --ignore-magic --ignore-private --ignore-property-decorators --ignore-semiprivate --fail-under=80 pyWebLayout/
- name: Lint with flake8
run: |
# Stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# Exit-zero treats all errors as warnings
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Create coverage info directory
if: always()
run: |
mkdir -p cov_info
echo "Created cov_info directory for coverage data"
- name: Update test coverage badge on success
if: steps.pytest.outcome == 'success' && always()
run: |
echo "Tests passed! Generating successful coverage badge..."
if [ -f coverage.json ]; then
coverage-badge -o cov_info/coverage.svg -f
echo "✅ Test coverage badge updated with actual results"
else
echo "⚠️ No coverage.json found, keeping failed badge"
fi
- name: Update docs coverage badge on success
if: steps.docs.outcome == 'success' && always()
run: |
echo "Docs check passed! Generating successful docs badge..."
# Remove existing badge first to avoid overwrite error
rm -f cov_info/coverage-docs.svg
interrogate --generate-badge cov_info/coverage-docs.svg pyWebLayout/
echo "✅ Docs coverage badge updated with actual results"
- name: Generate coverage reports
if: steps.pytest.outcome == 'success'
run: |
# Generate coverage summary for README
python -c "
import json
import os
# Read coverage data
if os.path.exists('coverage.json'):
with open('coverage.json', 'r') as f:
coverage_data = json.load(f)
total_coverage = round(coverage_data['totals']['percent_covered'], 1)
# Create coverage summary file in cov_info directory
with open('cov_info/coverage-summary.txt', 'w') as f:
f.write(f'{total_coverage}%')
print(f'Test Coverage: {total_coverage}%')
covered_lines = coverage_data['totals']['covered_lines']
total_lines = coverage_data['totals']['num_statements']
print(f'Lines Covered: {covered_lines}/{total_lines}')
else:
print('No coverage data found')
"
# Copy other coverage files to cov_info
if [ -f coverage.json ]; then cp coverage.json cov_info/; fi
if [ -f coverage.xml ]; then cp coverage.xml cov_info/; fi
if [ -d htmlcov ]; then cp -r htmlcov cov_info/; fi
- name: Final badge status
if: always()
run: |
echo "=== FINAL BADGE STATUS ==="
echo "Test outcome: ${{ steps.pytest.outcome }}"
echo "Docs outcome: ${{ steps.docs.outcome }}"
if [ -f cov_info/coverage.svg ]; then
echo "✅ Test coverage badge: $(ls -lh cov_info/coverage.svg)"
else
echo "❌ Test coverage badge: MISSING"
fi
if [ -f cov_info/coverage-docs.svg ]; then
echo "✅ Docs coverage badge: $(ls -lh cov_info/coverage-docs.svg)"
else
echo "❌ Docs coverage badge: MISSING"
fi
echo "Coverage info directory contents:"
ls -la cov_info/ 2>/dev/null || echo "No cov_info directory found"
- name: Upload coverage artifacts
uses: actions/upload-artifact@v3
with:
name: coverage-reports
path: |
cov_info/
- name: Commit badges to badges branch
if: github.ref == 'refs/heads/master'
run: |
git config --local user.email "action@gitea.local"
git config --local user.name "Gitea Action"
# Set the remote URL to use the token
git remote set-url origin https://${{ secrets.PUSH_TOKEN }}@gitea.tourolle.paris/dtourolle/pyWebLayout.git
# Create a new orphan branch for badges (this discards any existing badges branch)
git checkout --orphan badges
# Remove all files except cov_info
find . -maxdepth 1 -not -name '.git' -not -name 'cov_info' -exec rm -rf {} + 2>/dev/null || true
# Add only the coverage info directory
git add -f cov_info/
# Always commit (force overwrite)
echo "Force updating badges branch with new coverage data..."
git commit -m "Update coverage badges [skip ci]"
git push -f origin badges