pyrift
Detect silent Python behaviour differences across CPython versions and CPython vs PyPy.
What is pyrift?
Python upgrades and cross-runtime compatibility (CPython vs PyPy) introduce silent behaviour differences — code that runs without errors but produces wrong results, leaks resources, or crashes only in certain environments.
These are not syntax errors. Linters won't catch them. They are not CVEs. Security scanners won't catch them. They only appear at runtime, often in production.
pyrift statically detects these patterns before they become bugs.
Install
pip install pyrift
Zero external dependencies. Pure Python. Works on Python 3.10+.
Quick start — CLI
# Scan current directory
pyrift scan .
# Scan a specific path
pyrift scan ./src
# JSON output (for CI integration)
pyrift scan . --format json
# Markdown report saved to file
pyrift scan . --format markdown --output report.md
# Check version
pyrift --version
Example output
[ERROR] src/server.py:42 CPY002: Exception.add_note() requires Python 3.11+
→ Guard with: if sys.version_info >= (3, 11): e.add_note(...)
[ERROR] src/compat.py:17 CPY007: Module removed in Python 3.13
→ Find a third-party replacement for 'cgi' on PyPI
[WARNING] src/resource.py:88 PPY001: Relying on del for resource cleanup breaks on PyPy
→ Use context managers (with statement) or try/finally blocks
Scanned 23 file(s). Found 2 error(s), 1 warning(s). Score: 77/100
Quick start — Python API
import pyrift
# Scan a directory
result = pyrift.scan("./src")
print(result)
# ScanResult(files=23, errors=2, warnings=1, score=77)
# Iterate findings
for finding in result.findings:
print(finding)
# Filter by severity
for error in result.errors:
print(f"{error.file}:{error.line} — {error.title}")
# Export formats
json_output = pyrift.to_json(result)
markdown_output = pyrift.to_markdown(result)
text_output = pyrift.to_text(result)
# Scan a single file
findings = pyrift.scan_file("./src/utils.py")
Rules
CPython rules — version compatibility
| ID | Title | Severity | Affects |
|---|---|---|---|
| CPY001 | Dict ordering assumption | Warning | CPython < 3.7 |
| CPY002 | Exception.add_note() requires Python 3.11+ |
Error | CPython ≤ 3.10 |
| CPY003 | X | Y union type syntax requires Python 3.10+ |
Error | CPython ≤ 3.9 |
| CPY004 | tomllib requires Python 3.11+ |
Error | CPython ≤ 3.10 |
| CPY005 | match/case requires Python 3.10+ |
Error | CPython ≤ 3.9 |
| CPY006 | asyncio.timeout() / TaskGroup requires Python 3.11+ |
Error | CPython ≤ 3.10 |
| CPY007 | Module removed in Python 3.13 (21 modules) | Error | CPython ≥ 3.13 |
| CPY008 | __slots__ may not prevent __dict__ with base classes |
Info | All versions |
| CPY009 | ExceptionGroup requires Python 3.11+ |
Error | CPython ≤ 3.10 |
| CPY010 | @dataclass(slots=True) requires Python 3.10+ |
Error | CPython ≤ 3.9 |
PyPy rules — runtime differences
| ID | Title | Severity | Affects |
|---|---|---|---|
| PPY001 | Relying on __del__ for resource cleanup |
Error | PyPy all versions |
| PPY002 | ctypes usage may silently fail |
Warning | PyPy all versions |
| PPY003 | sys.getrefcount() is meaningless on PyPy |
Error | PyPy all versions |
| PPY004 | weakref.proxy() raises ReferenceError unpredictably |
Warning | PyPy all versions |
| PPY005 | File write without explicit flush may lose data | Warning | PyPy all versions |
| PPY006 | Monkey-patching built-in types behaves differently | Warning | PyPy all versions |
| PPY007 | sys.intern() identity guarantees differ on PyPy |
Warning | PyPy all versions |
Full rule documentation: docs/rules.md
Use in CI
Add pyrift to your GitHub Actions workflow:
- name: Run pyrift
run: |
pip install pyrift
pyrift scan . --format json --output pyrift-report.json
pyrift scan .
Exit code is 1 when errors are found — fails the CI build automatically.
Use --exit-zero to report without failing.
Use with pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/BHUVANSH855/pyrift
rev: v0.3.1
hooks:
- id: pyrift
Why pyrift?
| Tool | What it catches | What it misses |
|---|---|---|
pylint / ruff |
Style, common bugs | Runtime behaviour differences |
mypy / pyright |
Type errors | Runtime behaviour differences |
pip-audit |
Known CVEs | Behaviour differences |
bandit |
Security patterns | Behaviour differences |
pyrift |
Silent runtime behaviour differences | (that's the whole point) |
pyrift does not replace any of these tools. It catches what they all miss.
Roadmap
Planned for upcoming versions — contributions welcome:
CPY011—typing.Selfrequires Python 3.11+CPY012—typing.LiteralStringrequires Python 3.11+CPY013—@overridedecorator requires Python 3.12+PPY008—array.arraybehaviour differences on PyPyPPY009—decimalmodule precision differences on PyPy- Pre-commit hook native support
- VS Code extension
- GitHub Action marketplace listing
See CONTRIBUTING.md to add a rule yourself — rule IDs
CPY011+ and PPY008+ are open for community contributions.
Contributing
Contributions are very welcome — especially new rules for behaviour differences you have personally encountered.
See CONTRIBUTING.md for the full guide.
Project status
- Version: 0.3.1
- Rules: 17 (10 CPython + 7 PyPy)
- Tests: 70 passing
- Dependencies: zero
- Python: 3.10+
Author
Built by Bhuvansh Kataria — CPython contributor and PyPy toolkit author.
License
MIT — see LICENSE
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 pyrift-0.3.1.tar.gz.
File metadata
- Download URL: pyrift-0.3.1.tar.gz
- Upload date:
- Size: 26.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7cbf00830ad0358902806ebd1fcef036cd1fcb4a71bbf37f9b2268e110d12084
|
|
| MD5 |
0ff72050ca897b04be47a2636390c8ab
|
|
| BLAKE2b-256 |
f20eb98a61c427c4ba0ed9adb69599e4052315ae0570da81199a87079218f137
|
File details
Details for the file pyrift-0.3.1-py3-none-any.whl.
File metadata
- Download URL: pyrift-0.3.1-py3-none-any.whl
- Upload date:
- Size: 30.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f176a4e6436c3050d11490cbe8c83d98627ffd1dc316c82f6b1e5f90c140fcb
|
|
| MD5 |
ba723bce7a50bc9e3ee7947e6346ddd1
|
|
| BLAKE2b-256 |
473d078712487c7c78e022c5160355b7e0534750b011de46514ad1b171c2cc1f
|