Skip to main content

Flyway-inspired versioned SQL migration engine for Python

Project description

alt-python-flyway

Language Python License: MIT

Flyway-inspired versioned SQL migration engine for Python. Discovers V{version}__{description}.sql files, tracks applied migrations in a flyway_schema_history table, and provides the full Flyway OSS operation set: migrate(), info(), validate(), baseline(), repair(), and clean().

Port of @alt-javascript/flyway. Inspired by Flyway (Apache 2.0) by Redgate Software Ltd.

Part of the alt-python/boot monorepo.

Install

uv add alt-python-flyway pydbc-sqlite   # or: pip install alt-python-flyway pydbc-sqlite

Requires Python 3.12+ and alt-python-boot-pydbc.

Quick Start

import pydbc_sqlite  # registers the SQLite driver
from pydbc_core import PooledDataSource
from flyway import Flyway

ds = PooledDataSource('pydbc:sqlite::memory:', pool={'max': 2})
flyway = Flyway(data_source=ds, locations=['db/migration'])

result = flyway.migrate()
print(result['migrations_executed'])  # 3

Migration files follow Flyway naming convention:

db/migration/
  V1__create_notes_table.sql
  V2__add_priority_column.sql
  V3__seed_notes.sql

Operations

migrate()

Apply all pending versioned migrations in version order. Safe to call repeatedly — a second call applies nothing when all migrations are already applied.

result = flyway.migrate()
# {'migrations_executed': 3, 'applied_migrations': [...]}

Each entry in applied_migrations: version, description, script, execution_time, state (SUCCESS).

Raises FlywayMigrationError if a migration SQL fails. Raises FlywayValidationError if validate_on_migrate=True and a checksum drift is detected before migration starts.

info()

Return a status report for all known migrations (applied and pending).

for m in flyway.info():
    print(m['version'], m['state'], m['description'])
    # 1  SUCCESS  create notes table
    # 2  PENDING  add priority column

Each entry: version, description, script, checksum, state (PENDING, SUCCESS, or FAILED), installed_on, execution_time.

validate()

Verify that applied migration checksums match the files on disk. Raises FlywayValidationError if any applied migration's file has been modified since it was applied. Use in CI before deploying to catch accidental file edits.

flyway.validate()  # raises FlywayValidationError on mismatch

baseline()

Mark the current database state as a known baseline. Records a BASELINE entry in the history table so that migrate() starts from baseline_version + 1. Raises FlywayError if the history table already has entries.

flyway = Flyway(data_source=ds, locations=['db/migration'], baseline_version='3')
flyway.baseline()
# History now contains a BASELINE entry for version '3'
# Next migrate() will only apply V4 and later

repair()

Remove failed migration entries from the history table. Safe to call at any time — does not touch application tables.

result = flyway.repair()
print(result['removed_entries'])  # 1

clean()

Drop the schema history table. Destructive — does not drop application tables. Use only in development and test environments.

flyway.clean()

Constructor

Flyway(
    data_source,                            # required — pydbc DataSource
    locations=['db/migration'],             # migration file paths
    table='flyway_schema_history',          # history table name
    baseline_version='1',                   # version for baseline()
    baseline_description='Flyway Baseline', # baseline history entry description
    out_of_order=False,                     # allow out-of-order migrations
    validate_on_migrate=True,               # validate checksums before migrating
    installed_by='flyway',                  # user recorded in history
)
Parameter Default Description
data_source required pydbc DataSource or PooledDataSource
locations ['db/migration'] Filesystem paths to scan for migration files
table 'flyway_schema_history' History table name
baseline_version '1' Version string for baseline()
baseline_description 'Flyway Baseline' Description written to baseline entry
out_of_order False Allow applying migrations older than the latest applied
validate_on_migrate True Check checksums before each migrate() call
installed_by 'flyway' User name written to installed_by column

Migration File Format

File names must follow the pattern V{version}__{description}.sql:

  • V prefix (case-insensitive)
  • Version: dot-separated numeric segments — 1, 1.1, 2, 10 sort as 1 < 1.1 < 2 < 10
  • Double underscore (__) separates version from description
  • Underscores in the description are replaced with spaces in the history table
-- V1__create_notes_table.sql
CREATE TABLE IF NOT EXISTS notes (
    id       INTEGER PRIMARY KEY,
    title    TEXT    NOT NULL,
    body     TEXT,
    priority INTEGER NOT NULL DEFAULT 0,
    done     INTEGER NOT NULL DEFAULT 0
);

-- V3__seed_notes.sql
INSERT INTO notes (id, title, body) VALUES (1, 'First note', '');
INSERT INTO notes (id, title, body) VALUES (2, 'Second note', '');

Each file may contain multiple statements separated by ;. Line comments (-- ...) are stripped before execution.

Multiple Locations

flyway = Flyway(
    data_source=ds,
    locations=['db/notes-migration', 'db/tags-migration'],
)

Migrations from all locations are merged and sorted by version before applying. Version numbers must be unique across all locations.

SQLite In-Memory Databases

Use PooledDataSource with max=2 or higher. The schema history table and migration executor each need a connection. With an in-memory SQLite database, closing the last connection destroys the database — the pool keeps it alive across calls.

from pydbc_core import PooledDataSource

ds = PooledDataSource('pydbc:sqlite::memory:', pool={'max': 2})

Schema History Table

The flyway_schema_history table tracks every applied migration:

Column Type Description
installed_rank INTEGER Auto-incrementing primary key
version TEXT Migration version string
description TEXT Human-readable description from filename
type TEXT SQL or BASELINE
script TEXT Migration filename
checksum INTEGER Signed 32-bit CRC32 of the file content
installed_by TEXT User (configurable via installed_by=)
installed_on TEXT ISO-8601 timestamp
execution_time INTEGER Execution time in milliseconds
success INTEGER 1 = success, 0 = failed

API Reference

MigrationVersion

Parsed version wrapper with segment-aware numeric comparison.

from flyway import MigrationVersion

v = MigrationVersion.parse('1.1')
v.compare_to(MigrationVersion.parse('2'))  # -1 (less than)
v < MigrationVersion.parse('2')            # True
v == MigrationVersion.parse('1.1')         # True
str(v)                                     # '1.1'

MigrationVersion supports < and ==, which means Python's sorted() and list.sort() work correctly on lists of versions.

MigrationState

from flyway import MigrationState

MigrationState.PENDING   # 'PENDING'
MigrationState.SUCCESS   # 'SUCCESS'
MigrationState.FAILED    # 'FAILED'
MigrationState.BASELINE  # 'BASELINE'

checksum(sql: str) -> int

Returns a signed 32-bit CRC32 checksum matching Flyway OSS format. Used internally for drift detection; also available if you need to compute checksums independently.

from flyway import checksum

print(checksum('SELECT 1'))  # e.g. 1153854338

Error Types

Exception Raised when
FlywayError Base class; also raised by baseline() on non-empty history
FlywayValidationError Checksum mismatch in validate() or validate_on_migrate
FlywayMigrationError Migration SQL fails in migrate(). Attributes: .migration, .cause
from flyway import FlywayError, FlywayValidationError, FlywayMigrationError

try:
    flyway.migrate()
except FlywayMigrationError as e:
    print(e.migration['script'])  # which file failed
    print(e.cause)                # the underlying exception

All Exports

from flyway import (
    Flyway,
    FlywayError,
    FlywayValidationError,
    FlywayMigrationError,
    MigrationState,
    MigrationVersion,
    MigrationLoader,
    MigrationExecutor,
    SchemaHistoryTable,
    checksum,
)

Running Tests

uv run pytest packages/flyway -v

Troubleshooting

FlywayError: Flyway requires a data_source Pass data_source= explicitly. Flyway() with no arguments raises immediately.

FlywayValidationError: Migration checksum mismatch A migration file was edited after it was applied. Either restore the original file or call repair() to remove the failed history entry, then re-apply with the corrected SQL.

FlywayError: Cannot baseline a non-empty schema history baseline() only works on a fresh (empty) history table. If migrations have already been applied, use repair() to clean up failed entries, or clean() to reset the history table entirely (destructive).

Migrations not found — migrations_executed: 0 Check that locations= points to the directory containing the .sql files, not a parent directory. Check that filenames exactly match V{version}__{description}.sql (capital V, double underscore).

In-memory SQLite: RuntimeError or empty results after migrate() Use PooledDataSource with max=2. A plain DataSource or SingleConnectionDataSource closes the underlying connection after each PydbcTemplate call, which destroys an in-memory database.

Flyway Attribution

This project is inspired by Flyway (Apache License 2.0) by Redgate Software Ltd. It implements the open-source feature set only. Flyway is a registered trademark of Boxfuse GmbH, which is owned by Red Gate Software. This project is independent and not affiliated with Boxfuse GmbH, Red Gate Software, or the Flyway team.

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

alt_python_flyway-1.1.1.tar.gz (13.4 kB view details)

Uploaded Source

Built Distribution

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

alt_python_flyway-1.1.1-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file alt_python_flyway-1.1.1.tar.gz.

File metadata

  • Download URL: alt_python_flyway-1.1.1.tar.gz
  • Upload date:
  • Size: 13.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for alt_python_flyway-1.1.1.tar.gz
Algorithm Hash digest
SHA256 da547914f1a248a115a581efd4d4d4a74bb2e0ac18ec81d9e7725cfc000ee7e1
MD5 869284f3491ae5681c5974286d9bf6da
BLAKE2b-256 6d7de34466e04db4645448b764f306c86fe5376a3a351a8eaf4097a9df6ccd7f

See more details on using hashes here.

File details

Details for the file alt_python_flyway-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: alt_python_flyway-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 13.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for alt_python_flyway-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 51bea0b4e5df77df026d48283c398c49993145350b987f4f578225ea70d11bda
MD5 bd083dab70e92ba57f1e8e45935ccef9
BLAKE2b-256 9b57d885a96f6d1ff268b058d2fa4d6ee7191559c94133bf04f3f6bced92ea22

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