Breaking-change gate for PRs — detects API breaks straight from the Python code diff, no OpenAPI spec required
Project description
Oneport ApiDiff
Breaking-change gate for PRs — straight from the Python code diff. No OpenAPI spec required.
oasdiff needs an OpenAPI spec. buf breaking needs protobuf. Most Python codebases have neither — their public API is the code. Oneport ApiDiff diffs the actual public surface of your changed .py files (via the ast module, deterministically) and gates the merge when a change would break consumers.
$ oneport-apidiff check --staged
oneport-apidiff — --staged
==========================
Verdict: BREAKING (2 breaking, 0 risky, 1 compatible; 1 file(s) checked)
[BREAKING] charge (pkg/payments.py:6)
param `timeout` was removed
- def charge(amount, currency='usd', timeout=30)
+ def charge(amount, currency='usd')
impact: callers passing timeout positionally or by keyword now break
migration: drop the timeout argument; configure timeouts on the client instead
internal callers (2):
app/billing.py:1 from pkg.payments import charge
app/billing.py:14 charge(total, timeout=5)
Exit code 1 on BREAKING — wire it into CI and the merge is blocked until someone consciously overrides.
How it works
Deterministic detection, LLM classification. The model never decides what changed — only what it means.
- Deterministic core (
ast) — base and head versions of every changed.pyfile are compared as public API surfaces: removed symbols, added/removed/renamed/reordered required params, changed defaults, changed type annotations, sync↔async flips. If anopenapi.yaml/openapi.jsonchanged, its paths/methods/responses are diffed structurally too. Internal callers of every changed symbol are found by a deterministic repo scan and listed withfile:line. - LLM layer (Claude or Gemini) — each detected change is classified BREAKING / RISKY / COMPATIBLE with a one-line consumer-impact narrative and a migration note. Every change carries a deterministic default verdict, so if the model is down, wrong, or skipped (
--no-llm), the gate still works.
Install
pip install oneport-apidiff
export GEMINI_API_KEY=... # free at https://aistudio.google.com/apikey
# or: export ANTHROPIC_API_KEY=sk-ant-...
Usage
oneport-apidiff check --staged # gate what you're about to commit (default)
oneport-apidiff check --head # gate the last commit
oneport-apidiff check --base main # gate your branch against main
oneport-apidiff check https://github.com/org/repo/pull/42 # gate a PR
oneport-apidiff check https://github.com/org/repo/pull/42 --post # ...and post the verdict
Flags:
| Flag | Effect |
|---|---|
--post |
Post a sticky verdict table + inline comments on the exact changed signatures. Re-runs update in place (hidden marker) instead of duplicating. Requires GITHUB_TOKEN. |
--format inline|json |
Human or machine output. |
--allow-breaking |
Report but exit 0 (adoption mode). |
--no-llm |
Deterministic verdicts only — no API key needed at all. |
Exit codes: 0 clean/compatible · 1 breaking changes · 2 usage/config/auth error.
Team guidelines
ApiDiff reads the same .oneport/guidelines.md file as oneport-review. Verdicts respect your policy:
- internal package, private API — only changes to __all__ symbols are breaking
- CLI flag names are public API too
Detection stays deterministic either way; guidelines only shape what a change means.
CI
See examples/workflows/apidiff.yml. Minimal version:
- run: pip install oneport-apidiff
- run: oneport-apidiff check "${{ github.event.pull_request.html_url }}" --post
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Privacy
Serverless by design: your code goes from your machine (or your CI runner) directly to the model API with your own key. No Oneport servers, no telemetry, no storage. See PRIVACY.md.
vs. the alternatives
| oneport-apidiff | oasdiff | buf breaking | |
|---|---|---|---|
| Works without a spec | yes — diffs the code itself | no (OpenAPI only) | no (protobuf only) |
| Python public API (functions/classes/params) | yes | no | no |
| OpenAPI paths/methods/responses | yes (when a spec is present) | yes (deeper) | no |
| Consumer-impact narrative + migration notes | yes (LLM) | no | no |
| Internal caller list ("these files break") | yes | no | no |
Part of the Oneport pre-ship gate suite, alongside oneport-review.
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 oneport_apidiff-0.1.0.tar.gz.
File metadata
- Download URL: oneport_apidiff-0.1.0.tar.gz
- Upload date:
- Size: 35.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c9ce2551cdd926142f47c6b610da65c5d74b235e72b8b6dcd6ac9b649d972a7
|
|
| MD5 |
be3742b0538b1d4dfa1b65ce84c2fa98
|
|
| BLAKE2b-256 |
7f666d809518dc5664b09fd2c9f7411fd23f01129ecb6301292301457b48c5cd
|
File details
Details for the file oneport_apidiff-0.1.0-py3-none-any.whl.
File metadata
- Download URL: oneport_apidiff-0.1.0-py3-none-any.whl
- Upload date:
- Size: 40.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f13652a746dc791d08aec537c7736cdc3dfc7d7e337d2904c758adee13c5004
|
|
| MD5 |
9860c8d9941c73a3b69e2bd0145be119
|
|
| BLAKE2b-256 |
5907ec24ee29e7473aa17c039d8ca71f1febb5b84cc697216ed9b24bcf6bf8cf
|