Detect Python refactorings across Git revisions with functional-change reporting
Project description
PyRef2
PyRef2 is based on the core idea behind PyRef: automatically detecting refactorings in Python code. It extends that idea with a typed Python 3.13 codebase, direct Git revision analysis, structured JSON and Markdown reporting, hierarchical change summaries, and explicit functional-change detection for both refactoring-related and standalone behavior changes.
Use it when you need a fast, review-friendly report for questions like:
- What was renamed, moved, extracted, inlined, or signature-changed between two revisions?
- Did any of those findings also change method/class behavior?
- Were there behavior changes that are not simple rename/move operations?
It is designed for CI checks, release reviews, and repository archaeology.
Install and run in 60 seconds
Install from PyPI:
python -m pip install pyref2
Or with uv:
uv tool install pyref2
Then run:
pyref2 analyze-revisions --repo path/to/repo origin/main..HEAD --format markdown
What you get:
- machine-readable JSON findings (default)
- optional Markdown report grouped by change type and scope
- per-finding functional-change status
- condensed method-level code diffs when a functional change is detected
How to read the output
No Functional Changemeans PyRef2 did not find structural behavior signals for the compared entity.Functional Change Detectedmeans at least one behavior signal changed and the report includes reasons.- Markdown reports group findings into module-level changes, class-wise changes, mixed-scope method changes, and other refactorings.
Architecture overview
PyRef2 uses a small layered pipeline so each stage stays testable and replaceable:
- Parsing layer (
core/ast_analysis.py): converts Python source into typed entities (ModuleEntity,ClassEntity,MethodEntity). - Diff layer (
core/diff_engine.py): matches entities between revisions and produces a structuralModuleDiff. - Detection layer (
core/detectors/): runs focused heuristic detectors (rename/extract/inline/move/signature changes). - Service layer (
service.py): orchestrates parse → diff → detect and returns sorted findings. - Interface layer (
cli/commands.py): exposesanalyze-filesand emits schema-versioned JSON output.
How functional changes are detected
PyRef2 reports functional-change status using static structural checks for move-related and non-move findings.
Method-level checks
For method comparisons, PyRef2 marks Functional Change Detected when any of these differ between before/after revisions:
body_signature: normalized AST statement signatures for the method bodyparams: method parameter tuplecalled_names: set of called symbol names detected in the method body
If none of those differ, status is No Functional Change.
These method-level checks are used by:
- Move Method
- Rename Method
- Modify Method (same name, same scope, same module, but behavior changed)
- Change Method Signature
- Extract Method (assesses whether the source caller changed)
- Inline Method (assesses whether the destination caller changed)
Class-level checks
For class moves, PyRef2 combines class-level and member-level signals:
- class bases changed
- class method-name set changed
- any contained matched method is marked
Functional Change Detected
If none of the above is true, class status is No Functional Change.
For class signature changes (for example, base-class changes), PyRef2 reports Functional Change Detected with reasons when the class signature differs.
Reporting behavior
- Move-related and non-move behavior findings include a functional-change status in JSON and Markdown.
- In Markdown, same-name method entries are suppressed unless status is
Functional Change Detected. - Class entries can include child method changes used to justify class-level status.
- When status is
Functional Change Detected, Markdown includes a condensed unified code diff scoped to the relevant method.
Current limitations
This is a static heuristic, not dynamic execution equivalence. It does not guarantee runtime equivalence in all cases. In particular, behavior can still change through effects not captured by the current signals (for example, external state interactions or semantics-preserving AST rewrites that alter call-name sets).
Quickstart
uv sync --extra dev
uv run pyref2 analyze-files --before path/to/old.py --after path/to/new.py
uv run pyref2 analyze-tree --before-root path/to/revision-A --after-root path/to/revision-B
uv run pyref2 analyze-revisions --repo path/to/repo origin/main..HEAD
uv run pyref2 analyze-revisions --format markdown --repo path/to/repo origin/main..HEAD
Git revision analysis
Use analyze-revisions to compare repository states directly from Git without exporting trees by hand.
- Pass a standard Git double-dot range:
uv run pyref2 analyze-revisions --repo path/to/repo origin/main..HEAD main..featuremeans: analyze the total effect between the tree atmainand the tree atfeature, which matches the common feature-branch review workflow.- Add
--format markdownto get a developer-oriented report grouped by refactoring type.
Tree test fixtures
Whole-tree regression tests live under tests/source_trees/ and use this layout:
<test-name>/revision-A/<test-name>/revision-B/
This keeps it easy to add a new before/after source tree pair whenever a bug needs a permanent fixture.
Documentation conventions
- Use Google-style docstrings for public Python modules, classes, and functions.
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 pyref2-0.1.1.tar.gz.
File metadata
- Download URL: pyref2-0.1.1.tar.gz
- Upload date:
- Size: 14.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f05bd6b35c95cdc13b11654760c81494fa89e7738a2289c3b0680bde2418e1aa
|
|
| MD5 |
5b5010c1b921e9d2f11df639aee302ff
|
|
| BLAKE2b-256 |
aee179d7bfbbb704e8ef58025f6a772214681ed7971633ff7f53a4dfc116a09a
|
File details
Details for the file pyref2-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pyref2-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b2d37493986db8c579f719eb3be05496759dfcf8e794716efe761472f38db05
|
|
| MD5 |
dd49679f940fb3ed0ffbe0134c9fc717
|
|
| BLAKE2b-256 |
bf179bbed00c62d3445ea0573ea5dd697685f8ace03964cdcd703f8011e74ef9
|