Skip to main content

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.

  1. Deterministic core (ast) — base and head versions of every changed .py file are compared as public API surfaces: removed symbols, added/removed/renamed/reordered required params, changed defaults, changed type annotations, sync↔async flips, module constants, enum members, dataclass / pydantic-model fields, @property accessors, and __init__.py re-exports. A public symbol that simply moves between modules is reported as a relocation, not a false remove + add. If an openapi.yaml/openapi.json changed, its paths/methods/responses are diffed structurally too. Internal callers of every changed symbol are found by a deterministic, usage-aware repo scan (calls, attribute access, and imports — not every line that happens to contain the name) and listed with file:line.
  2. 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|sarif Human, machine, or SARIF 2.1.0 for GitHub code scanning / dashboards.
--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.

Waivers

Approve a specific break without opening the gate for everything. Drop a .oneport/apidiff-waivers.yml in the repo:

waivers:
  - symbol: core.Client.fetch     # exact qualname (or OpenAPI endpoint)
    reason: consumers migrated in v3
    expires: "2026-12-31"         # optional; after it, the gate closes again
    kind: symbol_removed          # optional; narrow to one change kind

A waived change still appears in the report (marked [WAIVED], and suppressed in SARIF) but no longer fails CI. An expired waiver is ignored automatically, so a temporary exception can't silently hide a break forever.

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.

Metadata

Release files for oneport-apidiff 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for oneport-apidiff 0.2.1
File Size Uploaded Provenance Actions
oneport_apidiff-0.2.1.tar.gz 42.0 kB Without provenance Details for oneport_apidiff-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for oneport-apidiff 0.2.1
File Interpreter ABI Platform Actions
oneport_apidiff-0.2.1-py3-none-any.whl Python 3 none any Details for oneport_apidiff-0.2.1-py3-none-any.whl

Total release size: 89.4 kB

Release files / oneport_apidiff-0.2.1.tar.gz

Download URL oneport_apidiff-0.2.1.tar.gz
Size 42.0 kB
Tags Source
SHA-256 checksum
How to use checksums
66bd716cb23ce4a8a9ae42f54e763bacc6082992a41e19f91dba9b50cbfcb726
BLAKE2b-256 checksum
How to use checksums
fddd8f604cafc105470b9c12aa4e21f77c5640fac5b3b6102daf0dc4cc354dc9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release files / oneport_apidiff-0.2.1-py3-none-any.whl

Download URL oneport_apidiff-0.2.1-py3-none-any.whl
Size 47.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
67e225c064c231354f6e25090dc43b8a1ba106c6da7b54587792b32e3756b60d
BLAKE2b-256 checksum
How to use checksums
909571c67af9072c5b3f0e4453b16f3b0bec5a9ef07eff78213b434d91c04ba0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Security for oneport-apidiff 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page