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.0

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.0
File Size Uploaded Provenance Actions
oneport_apidiff-0.2.0.tar.gz 41.9 kB Without provenance Details for oneport_apidiff-0.2.0.tar.gz

Built distribution (wheel)

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

Total release size: 89.3 kB

Release files / oneport_apidiff-0.2.0.tar.gz

Download URL oneport_apidiff-0.2.0.tar.gz
Size 41.9 kB
Tags Source
SHA-256 checksum
How to use checksums
290b70b8dc5d628014cf005962e3d2eb9573e06f54386c36e42597c648c07f97
BLAKE2b-256 checksum
How to use checksums
42cb5aac7b3f8277fe17475488cc1cb2eabd0475329958c475add8b646970604
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.0-py3-none-any.whl

Download URL oneport_apidiff-0.2.0-py3-none-any.whl
Size 47.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
26e052adbee310ef018ad6e3af7eb2b55edeaec4313e7e05a6578e3ca7668a5c
BLAKE2b-256 checksum
How to use checksums
7428b18ce6e645d7ca81e50d0a6e9323201366fa4b301a07aa007e2e8f123042
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.0

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