{"id":28069581,"url":"https://github.com/promptromp/pytest-impacted","last_synced_at":"2026-04-05T04:03:31.699Z","repository":{"id":292852492,"uuid":"981866594","full_name":"promptromp/pytest-impacted","owner":"promptromp","description":"A pytest plugin that selectively runs tests affected by codechanges via git introspection, ASL parsing, and dependency graph analysis.","archived":false,"fork":false,"pushed_at":"2025-05-12T14:10:31.000Z","size":1367,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-12T19:10:06.702Z","etag":null,"topics":["ci","pytest","python","python3","unit-testing"],"latest_commit_sha":null,"homepage":"https://promptromp.github.io/pytest-impacted/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/promptromp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-12T02:50:48.000Z","updated_at":"2025-05-12T14:09:55.000Z","dependencies_parsed_at":"2025-05-12T14:15:22.766Z","dependency_job_id":"d2a5e0b7-6e21-4f07-8527-3dcdf73bb1cc","html_url":"https://github.com/promptromp/pytest-impacted","commit_stats":null,"previous_names":["promptromp/pytest-impacted"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promptromp%2Fpytest-impacted","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promptromp%2Fpytest-impacted/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promptromp%2Fpytest-impacted/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/promptromp%2Fpytest-impacted/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/promptromp","download_url":"https://codeload.github.com/promptromp/pytest-impacted/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253805860,"owners_count":21967053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","pytest","python","python3","unit-testing"],"created_at":"2025-05-12T19:10:17.063Z","updated_at":"2026-04-05T04:03:31.694Z","avatar_url":"https://github.com/promptromp.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pytest-impacted\n\n[![CI](https://github.com/promptromp/pytest-impacted/actions/workflows/ci.yml/badge.svg)](https://github.com/promptromp/pytest-impacted/actions/workflows/ci.yml)\n[![GitHub License](https://img.shields.io/github/license/promptromp/pytest-impacted)](https://github.com/promptromp/pytest-impacted/blob/main/LICENSE)\n[![PyPI - Version](https://img.shields.io/pypi/v/pytest-impacted)](https://pypi.org/project/pytest-impacted/)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytest-impacted)](https://pypi.org/project/pytest-impacted/)\n\n**Run only the tests that matter.** A pytest plugin that uses git diff, AST parsing, and dependency graph analysis to selectively run tests impacted by your code changes.\n\n```bash\npytest --impacted --impacted-module=my_package     # unstaged changes\npytest --impacted --impacted-module=my_package \\\n       --impacted-git-mode=branch \\\n       --impacted-base-branch=main                 # branch changes vs main\n```\n\n---\n\n### Key Features\n\n| | Feature | Details |\n|---|---|---|\n| :zap: | **Fast feedback** | Only runs tests affected by your changes — skip the rest |\n| :deciduous_tree: | **Dependency-aware** | Follows import chains transitively, not just direct file changes |\n| :gear: | **No imports at analysis time** | Filesystem discovery + AST parsing — no module-level side effects |\n| :test_tube: | **pytest-native** | Works as a standard pytest plugin with familiar CLI options |\n| :wrench: | **conftest.py aware** | Changes to `conftest.py` automatically impact all tests in scope |\n| :package: | **Dependency-file aware** | Changes to `uv.lock`, `requirements.txt`, `pyproject.toml` etc. trigger all tests |\n| :building_construction: | **CI-friendly** | Standalone `impacted-tests` CLI for two-stage CI pipelines |\n| :rocket: | **Rust-accelerated** | Optional Rust extension for 37-65x faster import parsing on large codebases |\n| :shield: | **Helpful errors** | Validates config early with clear messages and suggestions |\n\n\u003e [!CAUTION]\n\u003e This project is currently in beta. Please report bugs via the [Issues](https://github.com/promptromp/pytest-impacted/issues) tab.\n\n---\n\n## Installation\n\n```bash\npip install pytest-impacted\n```\n\nOr with [uv](https://docs.astral.sh/uv/):\n\n```bash\nuv add pytest-impacted\n```\n\nFor **37-65x faster** import parsing on large codebases, install with the optional Rust extension:\n\n```bash\npip install pytest-impacted[fast]\n```\n\nRequires **Python 3.11+**.\n\n---\n\n## Quick Start\n\n**1. Run tests impacted by uncommitted changes:**\n\n```bash\npytest --impacted --impacted-module=my_package\n```\n\n**2. Run tests impacted by branch changes (vs `main`):**\n\n```bash\npytest --impacted \\\n       --impacted-module=my_package \\\n       --impacted-git-mode=branch \\\n       --impacted-base-branch=main\n```\n\n**3. Include tests outside the package directory:**\n\n```bash\npytest --impacted \\\n       --impacted-module=my_package \\\n       --impacted-tests-dir=tests\n```\n\nThat's it. Unaffected tests are automatically skipped.\n\n---\n\n## How It Works\n\n```\nGit diff → Changed files → Module resolution → AST import parsing → Dependency graph → Impacted tests\n                         ↘ Dependency file detection → All tests (if dep files changed)\n```\n\n1. **Git introspection** identifies which files changed (unstaged edits or branch diff)\n2. **Filesystem discovery** maps file paths to Python module names — without importing anything\n3. **AST parsing** (via [astroid](https://pylint.pycqa.org/projects/astroid/en/latest/), or the optional Rust extension using [ruff's parser](https://github.com/astral-sh/ruff)) extracts import relationships from source files\n4. **Dependency graph** (via [NetworkX](https://networkx.org/)) traces transitive dependencies from changed modules to test modules\n5. **Dependency file detection** — if files like `uv.lock`, `requirements.txt`, or `pyproject.toml` changed, all tests are marked as impacted regardless of import analysis\n6. **Test filtering** skips tests whose modules are not in the impact set\n\nThe philosophy is to **err on the side of caution**: we favor false positives (running a test that didn't need to run) over false negatives (missing a test that should have run).\n\n### Strategy-Based Architecture\n\nImpact analysis is pluggable via a strategy pattern. The default pipeline combines three strategies:\n\n| Strategy | What it does |\n|----------|-------------|\n| **ASTImpactStrategy** | Traces transitive import dependencies through the dependency graph |\n| **PytestImpactStrategy** | Extends AST analysis with pytest-specific knowledge — when a `conftest.py` file changes, **all tests in its directory and subdirectories** are marked as impacted |\n| **DependencyFileImpactStrategy** | When dependency files change (`uv.lock`, `requirements.txt`, `pyproject.toml`, etc.), **all tests** are marked as impacted |\n\nAll strategies are combined via `CompositeImpactStrategy`, which deduplicates and merges their results. Dependency file detection is enabled by default and can be disabled with `--no-impacted-dep-files`.\n\nYou can also supply a custom strategy via the `get_impacted_tests()` API:\n\n```python\nfrom pytest_impacted.api import get_impacted_tests\nfrom pytest_impacted.strategies import ImpactStrategy\n\nclass MyCustomStrategy(ImpactStrategy):\n    def find_impacted_tests(self, changed_files, impacted_modules, ns_module, **kwargs):\n        # your logic here\n        ...\n\nimpacted = get_impacted_tests(\n    impacted_git_mode=\"branch\",\n    impacted_base_branch=\"main\",\n    root_dir=Path(\".\"),\n    ns_module=\"my_package\",\n    strategy=MyCustomStrategy(),\n)\n```\n\n---\n\n## Usage\n\n### Git Modes\n\n| Mode | Flag | What it compares |\n|------|------|-----------------|\n| **unstaged** (default) | `--impacted-git-mode=unstaged` | Working directory changes + untracked files |\n| **branch** | `--impacted-git-mode=branch` | All commits on current branch vs base branch |\n\nThe `--impacted-base-branch` flag accepts any valid git ref, including expressions like `HEAD~4`.\n\n### External Tests Directory\n\nWhen your tests live outside the namespace package (a common layout), use `--impacted-tests-dir` so the dependency graph includes them:\n\n```bash\npytest --impacted \\\n       --impacted-module=my_package \\\n       --impacted-tests-dir=tests\n```\n\n### Monorepo / src-Layout Support\n\nThe plugin works in monorepos where the Python project is nested in a subdirectory (the `.git` directory doesn't need to be in the working directory — parent directories are searched automatically).\n\nFor **src-layout** projects (e.g. `src/my_package/`), point `--impacted-module` at the full path including the `src/` prefix:\n\n```bash\n# From the project directory (e.g. monorepo/backend/)\npytest --impacted \\\n       --impacted-module=src/my_package \\\n       --impacted-tests-dir=tests\n```\n\nThe plugin automatically detects that `src/` is not a Python package and uses the correct importable module name (`my_package`) for dependency analysis.\n\n### CI Integration\n\nFor CI pipelines where git access and test execution happen in separate stages, use the `impacted-tests` CLI to generate the test file list:\n\n```bash\n# Stage 1: identify impacted tests\nimpacted-tests --module=my_package --git-mode=branch --base-branch=main \u003e impacted_tests.txt\n\n# Stage 2: run only those tests\npytest $(cat impacted_tests.txt)\n```\n\n### Configuration via `pyproject.toml`\n\nAll CLI options can be set as defaults in your `pyproject.toml` (or `pytest.ini`):\n\n```toml\n[tool.pytest.ini_options]\nimpacted = true\nimpacted_module = \"my_package\"\nimpacted_git_mode = \"branch\"\nimpacted_base_branch = \"main\"\nimpacted_tests_dir = \"tests\"\n# no_impacted_dep_files = true  # uncomment to disable dep file detection\n```\n\nCLI flags override these defaults.\n\n### All Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--impacted` | `false` | Enable the plugin |\n| `--impacted-module` | *(required)* | Top-level Python package to analyze |\n| `--impacted-git-mode` | `unstaged` | Git comparison mode: `unstaged` or `branch` |\n| `--impacted-base-branch` | *(required for branch mode)* | Base branch/ref for branch-mode comparison |\n| `--impacted-tests-dir` | `None` | Directory containing tests outside the package |\n| `--no-impacted-dep-files` | `false` | Disable dependency file change detection |\n\n---\n\n## Alternatives\n\n| Project | Notes |\n|---------|-------|\n| [pytest-testmon](https://testmon.org/) | Most popular option. Uses coverage-based granular change tracking. More precise but heavier; may conflict with other plugins. |\n| [pytest-picked](https://github.com/anapaulagomes/pytest-picked) | Runs tests from directly modified files only — no transitive dependency analysis. |\n| [pytest-affected](https://pypi.org/project/pytest-affected/0.1.6/) | Appears unmaintained, no source repository. |\n\n---\n\n## Performance: Optional Rust Acceleration\n\nFor large codebases, install the optional Rust extension to accelerate import parsing by **37-65x**:\n\n```bash\npip install pytest-impacted[fast]\n```\n\nThis installs `pytest-impacted-rs`, a pre-built Rust extension using [ruff's parser](https://github.com/astral-sh/ruff) and [rayon](https://github.com/rayon-rs/rayon) for parallel file processing. The extension is automatically detected at runtime — no configuration needed. When unavailable, the pure-Python (astroid) implementation is used.\n\n---\n\n## Development\n\nThis project uses [uv](https://docs.astral.sh/uv/) for dependency management.\n\n```bash\n# Setup\nuv sync --all-extras --dev\n\n# Run tests\nuv run python -m pytest\n\n# Run tests with coverage\nuv run python -m pytest --cov=pytest_impacted --cov-branch tests\n\n# Lint + format + type check\npre-commit run --all-files\n\n# Install with Rust acceleration (pre-built wheels, no Rust toolchain needed)\npip install pytest-impacted[fast]\n\n# Or build from source (requires Rust toolchain)\npip install maturin\ncd rust \u0026\u0026 maturin develop --release\n\n# Run parsing benchmarks\npython -m benchmarks.bench_parsing\n```\n\n---\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpromptromp%2Fpytest-impacted","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpromptromp%2Fpytest-impacted","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpromptromp%2Fpytest-impacted/lists"}