Example repository demonstrating a production-ready, diff-aware CI/CD workflow for Python projects using GitHub Actions.
Enforce high quality standards on new and modified code without being blocked by technical debt in the existing codebase.
Only new or modified lines are linted using flake8 --diff.
Only tests affected by code changes are run using pytest-testmon.
Coverage is measured only for new and modified lines using diff-cover.
Selected tests run in parallel for maximum speed using pytest-xdist.
- Gatekeeper: Fast checks on pushes to main (multi-version, parallel)
- Collaborator: Detailed PR feedback with line-by-line analysis
βββββββββββββββββββββββββββββββββββββββββββββββ
β Push to main/develop β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Gatekeeper Workflow β
β (Fast & Parallel) β
βββββββββββββββββββββββ
β
βββββββββββ΄ββββββββββ
βΌ βΌ
ββββββββββββ ββββββββββββ
β Python β ... β Python β
β 3.8 β β 3.12 β
ββββββββββββ ββββββββββββ
β β
βββββββββββ¬ββββββββββ
βΌ
Run affected tests
with pytest-testmon
βββββββββββββββββββββββββββββββββββββββββββββββ
β Open Pull Request β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββ
β Pull Request Feedback β
β (Detailed Analysis) β
ββββββββββββββββββββββββββββββ
β
βββββββββββ΄ββββββββββ
βΌ βΌ
ββββββββββββ ββββββββββββββββββ
β Diff β β Diff Coverage β
β Linting β β Report β
ββββββββββββ ββββββββββββββββββ
β β
βββββββββββ¬ββββββββββ
βΌ
Post results as PR comment
- Testing:
pytest,pytest-cov,pytest-testmon,pytest-xdist - Linting:
flake8 - Coverage:
diff-cover - Build:
hatchling - CI/CD: GitHub Actions
- Python 3.8 or higher
- Git
# Clone the repository
git clone https://github.com/yourusername/diff-aware-quality-workflow-py.git
cd diff-aware-quality-workflow-py
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt# Run all tests
pytest
# Run with coverage
pytest --cov
# Run only affected tests (after making changes)
pytest --testmon
# Run tests in parallel
pytest -n auto# Lint entire codebase
flake8 src/ tests/
# Lint only changed lines (requires git)
git diff -U0 main | flake8 --diffThis demonstrates how the workflow catches issues in new code.
-
Create a feature branch
git checkout -b feature/add-power-function
-
Add a new function with a linting issue
Edit
src/calculator.pyand add:def power(a, b): """Calculate a to the power of b.""" unused_var = "oops" # This will trigger F841 return a ** b
-
Commit and push
git add src/calculator.py git commit -m "Add power function" git push origin feature/add-power-function -
Open a Pull Request
The PR workflow will:
- β Catch the
F841error in the new code - β
Ignore the existing
F841insubtract()(legacy code) - π Post coverage report showing the new function
- β Catch the
-
Fix the issue
Remove the unused variable:
def power(a, b): """Calculate a to the power of b.""" return a ** b
-
Push the fix
git add src/calculator.py git commit -m "Fix linting issue" git push -
Workflow passes β
This demonstrates how existing issues don't block new work.
-
Notice the existing issue
src/calculator.pyhasunused_variable = 123insubtract() -
Verify it's ignored
Check
.flake8:per-file-ignores = src/calculator.py:F841 -
Add a new test
Edit
tests/test_calculator.py:def test_add_zero(self): """Test adding zero.""" assert add(5, 0) == 5
-
Commit and create PR
git checkout -b feature/test-add-zero git add tests/test_calculator.py git commit -m "Add test for adding zero" git push origin feature/test-add-zero -
PR passes β despite legacy code issues
This demonstrates the speed improvements from intelligent test selection.
-
Make a small change
Edit docstring in
src/calculator.py:def add(a, b): """Add two numbers together.""" # Changed return a + b
-
Run without testmon (all tests)
pytest # All 13 tests run -
Run with testmon (affected tests only)
pytest --testmon # Only 3 tests run (those testing add function) -
Observe the speedup π
.
βββ .github/
β βββ workflows/
β βββ gatekeeper-checks.yml # Fast multi-version testing
β βββ pull-request-feedback.yml # Detailed PR analysis
βββ src/
β βββ __init__.py
β βββ calculator.py # Example module (with legacy issue)
βββ tests/
β βββ __init__.py
β βββ test_calculator.py # Comprehensive test suite
βββ .flake8 # Linting configuration
βββ .gitignore # Git ignore rules
βββ pyproject.toml # Project metadata & tool configs
βββ requirements.txt # Python dependencies
βββ LICENSE # MIT License
βββ README.md # This file
Triggers: Push to main or develop
Strategy:
- Matrix build across Python 3.8-3.12
- Parallel execution with
pytest-xdist - Intelligent test selection with
pytest-testmon - Caching of
.testmondatafor speed
Benefits:
- Fast feedback (only affected tests)
- Multi-version compatibility check
- Efficient use of CI minutes
Triggers: PR opened, synchronized, or reopened
Features:
- Diff-aware linting (
flake8 --diff) - Coverage only on changed lines (
diff-cover) - Automatic PR comments with results
- Fails PR if new linting issues found
Benefits:
- Clear, actionable feedback
- No noise from legacy code
- Encourages quality in new code
The .flake8 configuration uses per-file-ignores to suppress known issues:
per-file-ignores =
src/calculator.py:F841This allows you to:
- β Enforce standards on new code
- β Gradually fix legacy issues
- β Avoid "big bang" refactoring
- β Ship features without tech debt blockers
Edit .github/workflows/gatekeeper-checks.yml:
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']Edit .flake8:
[flake8]
max-line-length = 100 # Increase line length
extend-ignore = E203, W503, E402 # Add more ignoresEdit pyproject.toml:
[tool.coverage.report]
fail_under = 80 # Require 80% coverageEdit pyproject.toml:
[tool.pytest.ini_options]
addopts = "-v --tb=short --strict-markers"Problem: pytest --testmon runs all tests
Solution: Delete .testmondata and rebuild:
rm -rf .testmondata
pytest --testmonProblem: git diff | flake8 --diff shows no output
Solution: Ensure you have uncommitted changes:
# Check diff
git diff
# If empty, make changes or use:
git diff HEAD~1 | flake8 --diffProblem: No comment appears on PR
Check:
- Workflow has
pull-requests: writepermission - GitHub token has correct scopes
- Check workflow logs for errors
- pytest-testmon documentation
- diff-cover documentation
- flake8 documentation
- GitHub Actions documentation
This is a demo repository, but feel free to:
- Open issues for questions
- Submit PRs with improvements
- Fork and adapt for your projects
MIT License - see LICENSE file for details.
Built with excellent tools from the Python community:
- pytest team
- flake8 maintainers
- diff-cover contributors
- GitHub Actions team
Questions? Open an issue or check the workflows for inline comments explaining each step.