Contributing to PhilanthroPy
September 10, 2026 · View on GitHub
Thanks for helping improve PhilanthroPy! This guide covers the local checks every change must pass before it reaches CI. By participating you agree to abide by our Code of Conduct.
New here? Start with a good first issue each one names the files to touch and the single command that proves it is done. A first PR does not need to be big; docs fixes and missing tests are genuinely wanted. Comment on the issue to claim it, and ask there if anything in this guide does not work; a question is a valid contribution too.
Claiming an issue
Comment on the issue before you open a pull request, and wait for a maintainer
to assign it to you; we assign within 24 hours. This is not bureaucracy:
issues #175 and #176 were two people solving the same four-line problem on the
same evening, and one of them wasted their time. If an issue already has an
assignee, pick another one; the good first issue label always has open ones.
Setup
Fork the repository on GitHub first; you will not have push access to
PhilanthroPy-Project/PhilanthroPy.
git clone https://github.com/<your-username>/PhilanthroPy.git
cd PhilanthroPy
git remote add upstream https://github.com/PhilanthroPy-Project/PhilanthroPy.git
git switch -c my-change # never work on main
pip install -e ".[dev]" # editable install so the working tree is what's tested
sh scripts/install_hooks.sh # pre-push hook: runs the suite before every push
Windows contributors:
makeis not available in PowerShell by default, somake ciandmake riskcovbelow will not run as written. See Testing on Windows for the equivalent commands.sh scripts/install_hooks.shalso requires Git Bash; the installed pre-push hook does not fire when pushing from plain PowerShell, only from a POSIX-compatible shell (confirmed by testing).
Install editable: a non-editable copy in site-packages will shadow your edits under pytest and silently run stale code.
Expect the install plus a first green make ci to take about eight minutes.
Before pushing any commit
Always run the full local gate first:
make ci
This runs, in order:
- Lint (flake8, real defects only)
- Type check (mypy)
- Collection check, catches missing imports immediately
- Docstring examples (
--doctest-modules) - Full test suite
- Coverage gate (branch coverage ≥ 92% overall)
If make ci passes, the lint, type, test, and overall-coverage jobs will pass
CI. CI checks four more things make ci does not:
- a risk-tier coverage floor over
preprocessing/,models/,ingest/,cli.py, andutils/_persistence.py; reproduce it locally withmake riskcov(run it aftermake ci, which produces the coverage data). The include list and the floor are defined once in theMakefile; CI runs the same target, so the two cannot disagree. - the declared dependency floors on Python 3.9 (
uv pip install --resolution lowest-direct) python -m buildplustwine checkon the built distributions- a minimal install (
pip install .) that must import without matplotlib
Never use git push --no-verify.
When adding a new test file that imports a new class:
- Implement the class FIRST
- Add the export to
__init__.pyFIRST - Verify:
python -c "from philanthropy.X import Y; print('OK')" - THEN write the test file
- THEN run
make ci - THEN git add + commit
A single test file must never assert contradictory shapes or column counts for the same transformer. Before committing a test file, run:
grep -n "shape\|columns\|n_by" tests/<file>.py
and confirm all shape assertions are consistent with each other.
Opening the pull request
git push origin my-change # your fork, not upstream
Then open a pull request against PhilanthroPy-Project/PhilanthroPy main.
GitHub offers the button on your fork right after the push. Fill in the
PR template: what changed and why, and add a
CHANGELOG.md entry under [Unreleased]. Add yourself to
CONTRIBUTORS.md in the same PR: one line, name or handle and
what you did.
A body of just Resolves #N is not enough; say what changed and why even
when an issue is linked; reviewers read the PR body first, not the issue.
CI runs on pull requests from forks. If a job fails, push another commit to the same branch; the PR updates itself.
Additional checks
After cloning, run sh scripts/install_hooks.sh to install the pre-push hook.
This runs the full test suite before every push, preventing collection errors
from reaching CI.
Before committing a new test file, always verify:
python -m pytest <new_test_file.py> --collect-only -q
# Must show: X tests collected, 0 errors
Testing on Windows
make and sh are not available in PowerShell by default. Run the
equivalent commands directly instead of make ci / make riskcov:
python -m flake8 philanthropy tests examples
python -m mypy philanthropy
python -m pytest philanthropy --doctest-modules -q --no-cov
python -m pytest tests/ --collect-only -q
python -m coverage run -m pytest tests/ -q
python -m coverage report
coverage report reads the overall floor from [tool.coverage.report] in
pyproject.toml, so the bare command enforces exactly what CI does. Do not
pass --fail-under here: the number is defined once and copying it into a
second place is how the two drift apart.
For the risk-tier coverage floor, see the RISK_TIER/RISK_FLOOR values
in the Makefile and run:
python -m coverage report --include='<RISK_TIER from Makefile>' --fail-under=<RISK_FLOOR from Makefile>
If pytest --cov=... (via pytest-cov) raises
ImportError: cannot load module more than once per process on numpy
import, use coverage run -m pytest followed by coverage report instead.
Same result, different import path.
The sh scripts/install_hooks.sh pre-push hook requires Git Bash to
install, and does not fire when pushing from plain PowerShell. Run it via
Git Bash, or run the commands above manually before every push.
Versioning & deprecation
PhilanthroPy follows Semantic Versioning. Breaking changes
are called out under a Breaking heading in CHANGELOG.md, and
a deprecated public API emits a DeprecationWarning for at least one minor
release before removal. Per-symbol stability tiers are in
docs/reference/index.md.
Cutting a release is a maintainer task and needs PyPI, Zenodo, and GitHub release permissions; the runbook lives in RELEASING.md.