Linting and CI
Code quality tools
Ruff
Ruff is the linter and formatter. It is
configured in pyproject.toml:
[tool.ruff]
target-version = "py312"
line-length = 80
src = ["src"]
[tool.ruff.lint]
select = [
"B", # flake8-bugbear
"E", # pycodestyle errors
"F", # pyflakes
"N", # pep8-naming
"PTH", # flake8-use-pathlib
"RUF", # Ruff-specific rules
"S", # flake8-bandit (security)
"SIM", # flake8-simplify
"UP", # pyupgrade
"W", # pycodestyle warnings
]
The rule set was chosen to catch common bugs (B), security issues (S),
outdated patterns (UP) and unnecessary complexity (SIM) without being
overly noisy. Per-file ignores are applied where rules conflict with intentional
patterns:
lang.py- Cyrillic characters triggerRUF001.pdfopt.py- broadexcept: passis intentional for best-effort optimization (S110,S112).providers/*-urlopencalls are expected (S310).tests/*-assertis the standard way to write checks (S101).test_pdfopt.py- late imports afterpytest.importorskip(E402).
Run ruff manually:
ruff check .
Mypy
Mypy runs in strict mode:
[tool.mypy]
python_version = "3.12"
strict = true
Two overrides relax strictness where full typing is impractical:
livereload.*- no type stubs available (ignore_missing_imports).tests.*- fixtures and mocks are hard to type fully (disallow_untyped_defs = false).
Run mypy manually:
mypy src/
Pre-commit hooks
The .pre-commit-config.yaml runs three hook sets before every commit:
pre-commit-hooks (v6.0.0) - trailing whitespace, end-of-file fixer, YAML check.
ruff (v0.15.5) - linting.
mypy (v1.19.0) - type checking with
types-tqdmstubs.
Install the hooks (one-time setup):
pre-commit install
Run all hooks against every file manually:
pre-commit run --all-files
CI pipelines
Three GitHub Actions workflows automate quality checks and artifact generation.
test.yml
Triggered on every push to main and every pull request.
lint job:
Install
pip install -e .[dev]ruff check .mypy src/Generate and upload a lint badge.
test job:
Install
pip install -e .[dev]pytest --cov --cov-report=term --cov-report=xml:coverage.xmlGenerate and upload a coverage badge.
publish job (main branch only):
Pushes the lint and coverage badge SVGs to the _artifacts orphan branch so
they are available at stable raw URLs for the README badges.
build-pdf.yml
Triggered on every push to main and every pull request. Builds PDFs for
multiple countries using a matrix strategy (pl, by, us, kr). On
the main branch, the resulting PDFs are pushed to the _artifacts branch.
build-sphinx-docs.yml
Triggered on push to main. Builds the Sphinx HTML documentation and deploys
it to GitHub Pages.
The _artifacts branch
Instead of GitHub Releases with moving tags, badge SVGs and pre-built PDFs are
pushed to an orphan branch called _artifacts. This gives stable raw URLs
like:
https://github.com/stamerlan/feather-flow/raw/_artifacts/coverage-badge.svg
The release-based approach had problems:
Moving tags (e.g.
latest) cause confusion withgit fetch.Release assets require authentication for private repos.
The release page becomes cluttered with auto-generated entries.
The _artifacts branch provides stable raw URLs, works without
authentication, and keeps the release history clean for actual version releases.