Skip to main content

Database-migration safety gate for Python — Django, Alembic, and raw SQL, with AI blast-radius judgment

Project description

Oneport Migrate

The database-migration safety gate for Python teams. Django migrations, Alembic revisions, and raw .sql — checked by a deterministic rule engine, judged by an LLM that knows your repo, and wired into CI as a blocking gate.

Migrations are the scariest deploy moment: squawk lints Postgres SQL only, strong_migrations is Rails only. Nothing covers the Python world's actual migration formats. Oneport Migrate does.

How it works

Two layers, strictly separated:

  1. Deterministic rule engine — parses your migrations (Django via AST, Alembic via AST, raw SQL via regex) into a normalized operation model and matches it against a YAML rule catalog (OPM001+). This layer alone decides whether an operation is dangerous. No model, no network, no flakiness — the same migration always produces the same findings.

  2. LLM blast-radius layer (Gemini or Claude) — given the findings, the migration source, and your models/schema files, it judges how dangerous in this repo ("the users table is written on every request — a 4-minute lock here is an outage"), writes a safe multi-step rewrite plan (expand → backfill in batches → contract), and a plain-English verdict. It can adjust a finding's severity only when your team guidelines explicitly justify it — never on its own judgment.

If the LLM is unavailable (no key, rate limit, bad output), the gate still works: deterministic findings and the exit code stand on their own.

Install

pip install oneport-migrate
export GEMINI_API_KEY=...   # free at https://aistudio.google.com/apikey

Claude instead of Gemini: pip install oneport-migrate[claude] and set ANTHROPIC_API_KEY.

Usage

# One file, a directory, git state, or a PR
oneport-migrate check app/migrations/0042_drop_legacy.py
oneport-migrate check migrations/
oneport-migrate check --staged
oneport-migrate check --head
oneport-migrate check https://github.com/org/repo/pull/42

# Post findings as an inline PR review (requires GITHUB_TOKEN)
oneport-migrate check https://github.com/org/repo/pull/42 --post

# Options
oneport-migrate check migrations/ --db mysql --format json --min-severity error
oneport-migrate check migrations/ --no-llm     # deterministic rules only
oneport-migrate rules list

Exit codes (the CI contract)

Code Meaning
0 clean, or warnings/info only
1 blocking findings (error/critical)
2 usage / config / auth error

Blocking is computed from the full finding set before any display filtering — --min-severity critical can hide an error from the output, but never from the exit code.

Rule catalog

ID Severity What it catches
OPM001 critical Dropping a column
OPM002 critical Dropping a table
OPM003 critical TRUNCATE / DELETE without WHERE
OPM010 error ALTER COLUMN TYPE (table rewrite under exclusive lock)
OPM011 error Adding a NOT NULL column without a default
OPM012 error CREATE INDEX without CONCURRENTLY (Postgres only)
OPM013 error Adding NOT NULL to an existing column
OPM020 warning Missing reverse migration (reverse_code / downgrade / reverse_sql)
OPM021 error Data migration inside a schema transaction
OPM030 warning Column rename (breaks rolling deploys)
OPM031 warning Table rename (breaks rolling deploys)

Indexes on tables created in the same migration are exempt from OPM012 (the table is empty). OPM012 only fires for --db postgres.

Project config — .oneportmigraterc

db: postgres
rules:
  ignore: [OPM031]
  severity:
    OPM012: warning     # deterministic override, applies before the LLM runs
output:
  format: inline
  min_severity: warning

Team guidelines — .oneport/guidelines.md

The same file oneport-review uses. The LLM reads it and may downgrade or upgrade a finding's severity only when an entry explicitly covers it, quoting the guideline in its reason:

- our users table is small (<10k rows), downgrade lock severity
- the events table is 400M rows and append-only — never rewrite in place

Honest limits

  • Raw SQL parsing is regex-based. Statements are split on ; without full string-literal awareness; $$-quoted function bodies and dynamic SQL (EXECUTE format(...)) are not analyzed. When the parser hits these it attaches a note to the output instead of guessing. Misses are false negatives — it never invents a finding.
  • Django AlterField / Alembic alter_column(type_=...): a single migration file doesn't show the old column definition, so every field alteration is flagged as a potential rewrite. You (or the LLM, with repo context) decide if it's actually a no-op.
  • RunSQL / op.execute with non-literal arguments (variables, f-strings) can't be inspected — recorded as unanalyzed raw SQL with a note.
  • Table sizes are unknown to the rule engine. "Large-table" judgment is exactly what the LLM layer is for; without an API key you get the deterministic worst-case severities.
  • --staged / --head read file contents from the working tree.

CI

See examples/workflows/migrate-gate.yml:

- run: pip install oneport-migrate
- run: oneport-migrate check --head --post "$PR_URL"
  env:
    GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

--post submits one inline PR review per revision; a hidden marker in the review body prevents duplicate reviews when CI re-runs on the same commit.

Privacy

Serverless: your migration files go directly from your machine/CI to the model API using your key. No Oneport server ever sees your code. See PRIVACY.md.

Part of the Oneport suite

  • oneport-review — CodeRabbit-class AI code review
  • oneport-migrate — this tool

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oneport_migrate-0.2.0.tar.gz (49.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oneport_migrate-0.2.0-py3-none-any.whl (57.9 kB view details)

Uploaded Python 3

File details

Details for the file oneport_migrate-0.2.0.tar.gz.

File metadata

  • Download URL: oneport_migrate-0.2.0.tar.gz
  • Upload date:
  • Size: 49.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.0

File hashes

Hashes for oneport_migrate-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d1b4732114db71162804bba72bfa3436f8b40a8bb4a6f754df6cb9dee06d5ba2
MD5 fae6b21cec91b90bb926cb8b0c7fbddf
BLAKE2b-256 1df188087f948387ed13bbfd9e1339d0a84ffa714cc2d06e06ceebb9d2e12261

See more details on using hashes here.

File details

Details for the file oneport_migrate-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for oneport_migrate-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd8c260906627d8e3d4ededb4141703ce1c0e465fb0bdd5f85f8011c3bd89d8f
MD5 4fac67134b6a172b4d83b9ef27e8b2ed
BLAKE2b-256 d8e9b0fcf0947833d58a460687b816a49a10bc821c90d35f544870b2d6ab0390

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page