Run pytest for tests related to staged Python files in pre-commit.
Project description
pytest-changed
Run pytest for tests related to staged Python files in pre-commit.
pytest-changed is a small, deterministic selector for commit-time feedback. It takes the Python filenames passed in by pre-commit, turns source-file changes into test-file paths using simple conventions, applies optional explicit overrides, and runs pytest on the resulting test files.
If src/foo/bar.py changes, pytest-changed looks for:
tests/foo/test_bar.pytests/test_bar.py
Any changed test file under your configured test roots is always run directly.
What it is for
Use pytest-changed when you want a staged-file-aware pre-commit hook that:
- runs fast enough to sit on the commit path
- behaves predictably from filenames alone
- is easy to debug when selection looks wrong
- does not rely on stored state, import graphs, or runtime tracing
This tool is intentionally file-level and convention-driven. It is for commit-time test selection, not whole-project impact analysis.
Install
pip install pytest-changed
pytest is installed as a runtime dependency because pytest-changed invokes pytest directly.
Quick start
The default convention assumes source files live under src/ and tests live under tests/.
[tool.pytest-changed]
pytest_args = ["-q"]
With that config:
src/foo/bar.py -> tests/foo/test_bar.py
src/foo/bar.py -> tests/test_bar.py
Run manually:
pytest-changed src/foo/bar.py tests/test_other.py
If src/foo/bar.py has no matching tests, pytest-changed exits 0 but warns on stderr by default.
Pre-commit usage
Use this repository as a normal pre-commit hook:
repos:
- repo: https://github.com/mishmishb/pytest-changed
rev: v0.1.0
hooks:
- id: pytest-changed
For local development before the first release:
repos:
- repo: local
hooks:
- id: pytest-changed
name: pytest-changed
entry: pytest-changed
language: system
pass_filenames: true
always_run: false
stages: [pre-commit]
Configuration
All configuration lives in pyproject.toml under [tool.pytest-changed]:
| Option | Default | Description |
|---|---|---|
pytest_args |
["-q"] |
Extra args passed to pytest |
source_roots |
["src"] |
Source roots stripped before convention matching |
test_roots |
["tests"] |
Test roots searched for matching tests |
warn_on_missing |
true |
Warn on stderr when a changed source file has no match |
mapping |
{} |
Optional explicit overrides for individual source files |
Convention matching
For each changed Python source file, pytest-changed tries these paths under each configured test_root:
<test_root>/<relative_dir>/test_<module>.py
<test_root>/test_<module>.py
Example with defaults:
src/core.py -> tests/test_core.py
src/utils/helpers.py -> tests/utils/test_helpers.py
src/utils/helpers.py -> tests/test_helpers.py
If both convention matches exist, both run.
Explicit overrides
Use [tool.pytest-changed.mapping] when a file needs something more specific than the naming convention. Explicit mappings override convention discovery for that source file.
[tool.pytest-changed]
pytest_args = ["-q"]
warn_on_missing = true
[tool.pytest-changed.mapping]
"src/db.py" = ["tests/test_db.py", "tests/test_db_integration.py"]
"src/core.py" = ["tests/custom/test_special_core.py"]
Custom roots
If your project uses different layout conventions, configure them explicitly:
[tool.pytest-changed]
source_roots = ["lib", "pkg"]
test_roots = ["spec", "tests"]
warn_on_missing = true
Environment override
Set PYTEST_CHANGED_CONFIG to point at a TOML config file. This is useful when you cannot or do not want to put configuration in the project pyproject.toml.
PYTEST_CHANGED_CONFIG=pytest-changed.toml pytest-changed src/core.py
The override file uses the same table structure:
[tool.pytest-changed]
pytest_args = ["-q", "--tb=short"]
source_roots = ["src"]
test_roots = ["tests"]
warn_on_missing = true
[tool.pytest-changed.mapping]
"src/core.py" = ["tests/custom/test_special_core.py"]
The canonical table name is hyphenated (pytest-changed) because it matches the package/distribution name. The Python import package remains underscored (pytest_changed), as normal for Python modules. The early underscore table ([tool.pytest_changed]) is accepted as a compatibility alias, but new projects should use [tool.pytest-changed].
How it works
- Receives changed filenames from pre-commit or CLI arguments
- Filters to Python files only
- Auto-includes changed files under configured test roots
- Applies an explicit mapping override if one exists for a changed source file
- Otherwise tries nested and flat convention matches
- Warns about unmatched source files unless disabled
- Deduplicates selected tests
- Runs
python -m pytest <selected tests> <pytest_args> - Returns exit code
0if no tests match, or if pytest exits with code5(no tests collected)
Scope and limits
These are current scope boundaries, not laws of nature:
- Staged filenames in, test files out — the current contract is commit-time file selection
- File-level selection — it selects test files, not individual test functions or classes
- No hidden state — no database, baseline run, or runtime trace cache
- No dependency graph inference — selection is based on configured conventions and explicit overrides
- CI still matters — this speeds up local commit feedback; it does not replace the full suite
Design principles
- Pre-commit first — designed around staged filenames passed by the hook
- Convention first — useful with minimal config
- Explicit overrides when needed — config is the escape hatch, not the baseline
- Changed tests always run — edited tests do not need source mapping
- Warnings over silent misses — unmatched sources should be visible
- Deterministic local feedback — selection should be understandable from paths alone
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_changed-0.1.0.tar.gz.
File metadata
- Download URL: pytest_changed-0.1.0.tar.gz
- Upload date:
- Size: 54.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
995a2945f1578322b224e4c8ea079f279c9565a5a0de982b916bb4b0b75c22d4
|
|
| MD5 |
bc7728f19c391b6c0d155d6e9035aefb
|
|
| BLAKE2b-256 |
b86f157d79becbe9e2b58dac6b9b2e42943510f5bbd66c2be36aa766d8d505ca
|
Provenance
The following attestation bundles were made for pytest_changed-0.1.0.tar.gz:
Publisher:
release.yml on mishmishb/pytest-changed
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_changed-0.1.0.tar.gz -
Subject digest:
995a2945f1578322b224e4c8ea079f279c9565a5a0de982b916bb4b0b75c22d4 - Sigstore transparency entry: 1625590020
- Sigstore integration time:
-
Permalink:
mishmishb/pytest-changed@53fe7e4e048c785bd4f66def8ff2b41f3ab559ac -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mishmishb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@53fe7e4e048c785bd4f66def8ff2b41f3ab559ac -
Trigger Event:
push
-
Statement type:
File details
Details for the file pytest_changed-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pytest_changed-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82880d5ecee0dfab90942c6e246b3a00b666e1509f50b43f8860d9d7ec9e23e1
|
|
| MD5 |
300200046f1bc1a8b7b398f1348968bc
|
|
| BLAKE2b-256 |
d6c596f070ee92869aa5c99e214fc1f81ec203ae1fd7edd7f720958783bb571c
|
Provenance
The following attestation bundles were made for pytest_changed-0.1.0-py3-none-any.whl:
Publisher:
release.yml on mishmishb/pytest-changed
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_changed-0.1.0-py3-none-any.whl -
Subject digest:
82880d5ecee0dfab90942c6e246b3a00b666e1509f50b43f8860d9d7ec9e23e1 - Sigstore transparency entry: 1625590038
- Sigstore integration time:
-
Permalink:
mishmishb/pytest-changed@53fe7e4e048c785bd4f66def8ff2b41f3ab559ac -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mishmishb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@53fe7e4e048c785bd4f66def8ff2b41f3ab559ac -
Trigger Event:
push
-
Statement type: