SQLite Migrate
sqlite-migrate is a simple database migration system for SQLite, built upon the sqlite-utils library. It allows users to define database schema changes and data manipulations as Python functions within numbered files, which can then be applied incrementally. The current version is 0.1b0, with releases occurring periodically to address bugs and introduce new features during its beta phase.
Common errors
-
sqlite3.IntegrityError: UNIQUE constraint failed: _sqlite_migrations.name
cause This typically occurs when applying `sqlite-migrate 0.1b0` or later to a database that was initially migrated with a version prior to `0.1b0`. The older schema of `_sqlite_migrations` table used `name` as the sole primary key, while the newer version attempts to use `(migration_set, name)`, leading to conflicts if existing 'name' entries are duplicated by design in the new system.fixIf safe, drop and recreate the `_sqlite_migrations` table. For production databases, carefully manage the upgrade of this internal table's schema, potentially by manually adding the `migration_set` column and altering the primary key to `(migration_set, name)` before applying new migrations. -
AttributeError: 'NoneType' object has no attribute 'migrate'
cause A migration file was found and imported, but it did not define the expected `migrate(db)` function, or the function was misspelled/not at the top level of the module.fixReview the migration file(s) that are intended to be executed and ensure each one contains a function named `migrate` that takes a single argument, `db` (e.g., `def migrate(db): ...`).
Warnings
- breaking The internal `_sqlite_migrations` table's primary key was changed from `name` to `(migration_set, name)`. If you migrate an existing database created with `sqlite-migrate < 0.1b0` to `0.1b0` or later, and try to apply new migrations, you may encounter `UNIQUE constraint failed` errors if migration names conflict across different sets (or if the internal table schema isn't properly updated).
- gotcha Migration files must define a top-level function named `migrate` that accepts a `db` argument (an `sqlite-utils` Database object). Files without this function will be ignored.
- gotcha Migrations are applied in alphabetical (lexicographical) order based on their filenames. This means `10_migration.py` will run *before* `2_migration.py` if not zero-padded. Improper naming can lead to unexpected database states.
Install
-
pip install sqlite-migrate
Imports
- Migrations
from sqlite_migrate import Migrations
- MigrationSet
from sqlite_migrate import MigrationSet
Quickstart
import sqlite_utils
from sqlite_migrate import Migrations
import os
# 1. Prepare a temporary database file
db_path = "quickstart.db"
if os.path.exists(db_path):
os.remove(db_path)
db = sqlite_utils.Database(db_path)
# 2. Define a migration in a temporary directory
migrations_dir = "./qs_migrations"
os.makedirs(migrations_dir, exist_ok=True)
migration_file_path = os.path.join(migrations_dir, "001_create_users_table.py")
with open(migration_file_path, "w") as f:
f.write("""
def migrate(db):
db["users"].create({"id": int, "name": str}, pk="id", if_not_exists=True)
db["users"].insert({"id": 1, "name": "Alice"})
""")
# 3. Initialize Migrations and apply them
migrations = Migrations(db, [migrations_dir])
migrations.apply()
print(f"Database '{db_path}' migrated successfully.")
print(f"Tables: {db.table_names()}")
print(f"Users count: {db['users'].count()}")
# 4. Clean up temporary files
os.remove(migration_file_path)
os.rmdir(migrations_dir)
os.remove(db_path)