A lightweight, Python-based, framework-agnostic, async-compatible PostgreSQL migration tool.
- Lightweight: Minimal dependencies, focused solely on PostgreSQL migrations
- Language-agnostic: Works with any language or framework, no ORM required
- Environment-aware: Supports configuration via environment variables
- Migration locking: Prevents concurrent migrations for data safety
- Schema verification: Ensures your schema.sql and migrations stay in sync
- Fixture support: Load test data and fixtures easily
- Async-compatible Python API: Built on
asyncpgfor integration with modern async Python applications
pip install dbami-
Initialize a new dbami project:
dbami init
-
Create your first migration:
dbami new create_users_table
-
Edit the generated migration files in the
migrations/directory -
Create and migrate your database:
dbami up --database myapp_db
dbami can be configured using environment variables. All environment
variables are prefixed with DBAMI_ (except for standard PostgreSQL connection
variables).
dbami uses standard PostgreSQL environment variables for database
connections:
PGHOST- Database host (default: localhost)PGPORT- Database port (default: 5432)PGUSER- Database userPGPASSWORD- Database passwordPGDATABASE- Database name
DBAMI_PROJECT_DIRECTORY- Project directory containing migrations (default: current directory)DBAMI_WAIT_TIMEOUT- Seconds to wait for database connection (default: 60)DBAMI_SCHEMA_VERSION_TABLE- Name of the schema version tracking table (default: schema_version)
usage: dbami [-h] [command] ...
The database friend you didn't know you needed.
options:
-h, --help show this help message and exit
commands:
[command]
init Initialize a new dbami project (in the current directory unless specified)
new Create a new migration with the given name
create Create a database
drop Drop a database
pending List all unapplied migrations
current-schema Get current schema version
load-schema Load the schema.sql into a database
migrate Migrate the database to the latest (or specified) version
rollback Rollback the database to the last (or specified) version
up Migrate to the latest version, creating the database if necessary
verify Check that the schema and migrations are in sync
version Print the cli version
list-fixtures List all available fixture files on search path
load-fixture Load a sql fixture into the database
execute-sql Run SQL from stdin against the database
usage: dbami init [-h] [--project-directory PROJECT_DIRECTORY]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
Creates the following structure:
.
├── migrations/
├── fixtures/
└── schema.sql
usage: dbami new [-h] [--project-directory PROJECT_DIRECTORY] migration_name
positional arguments:
migration_name
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
Creates two files:
migrations/YYYYMMDDHHMMSS_<name>.up.sql- Apply migrationmigrations/YYYYMMDDHHMMSS_<name>.down.sql- Rollback migration
usage: dbami create [-h] [--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
options:
-h, --help show this help message and exit
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
usage: dbami drop [-h] [--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
options:
-h, --help show this help message and exit
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
usage: dbami migrate [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
[--target TARGET_MIGRATION_ID]
[--schema-version-table SCHEMA_VERSION_TABLE] [--no-lock]
[--lock-timeout LOCK_TIMEOUT]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
--target TARGET_MIGRATION_ID
(default: 'latest')
--schema-version-table SCHEMA_VERSION_TABLE
name of the table (optionally schema-qualified) in
which to store applied schema versions (default:
'schema_version'; env: $DBAMI_SCHEMA_VERSION_TABLE)
--no-lock do not lock db access during migration
--lock-timeout LOCK_TIMEOUT
seconds to wait for db lock; 0 waits indefinitely
usage: dbami rollback [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
[--target TARGET_MIGRATION_ID]
[--schema-version-table SCHEMA_VERSION_TABLE]
[--no-lock] [--lock-timeout LOCK_TIMEOUT]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
--target TARGET_MIGRATION_ID
(default: 'last')
--schema-version-table SCHEMA_VERSION_TABLE
name of the table (optionally schema-qualified) in
which to store applied schema versions (default:
'schema_version'; env: $DBAMI_SCHEMA_VERSION_TABLE)
--no-lock do not lock db access during migration
--lock-timeout LOCK_TIMEOUT
seconds to wait for db lock; 0 waits indefinitely
usage: dbami up [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
[--schema-version-table SCHEMA_VERSION_TABLE] [--no-lock]
[--lock-timeout LOCK_TIMEOUT]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
--schema-version-table SCHEMA_VERSION_TABLE
name of the table (optionally schema-qualified) in
which to store applied schema versions (default:
'schema_version'; env: $DBAMI_SCHEMA_VERSION_TABLE)
--no-lock do not lock db access during migration
--lock-timeout LOCK_TIMEOUT
seconds to wait for db lock; 0 waits indefinitely
The up command is equivalent to running:
create(if database doesn't exist)migrate
usage: dbami pending [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
[--schema-version-table SCHEMA_VERSION_TABLE]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
--schema-version-table SCHEMA_VERSION_TABLE
name of the table (optionally schema-qualified) in
which to store applied schema versions (default:
'schema_version'; env: $DBAMI_SCHEMA_VERSION_TABLE)
usage: dbami current-schema [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
[--schema-version-table SCHEMA_VERSION_TABLE]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
--schema-version-table SCHEMA_VERSION_TABLE
name of the table (optionally schema-qualified) in
which to store applied schema versions (default:
'schema_version'; env: $DBAMI_SCHEMA_VERSION_TABLE)
usage: dbami load-schema [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
usage: dbami verify [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT]
[--schema-version-table SCHEMA_VERSION_TABLE]
[--pg-dump PG_DUMP]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
--schema-version-table SCHEMA_VERSION_TABLE
name of the table (optionally schema-qualified) in
which to store applied schema versions (default:
'schema_version'; env: $DBAMI_SCHEMA_VERSION_TABLE)
--pg-dump PG_DUMP path to pg_dump executable or name to lookup on path
(default: 'pg_dump'; env: $DBAMI_PG_DUMP)
usage: dbami list-fixtures [-h] [--project-directory PROJECT_DIRECTORY]
[--fixture-dir FIXTURE_DIRS]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--fixture-dir FIXTURE_DIRS
directory from which to load sql fixtures; later
directories take precedence
usage: dbami load-fixture [-h] [--project-directory PROJECT_DIRECTORY]
[--fixture-dir FIXTURE_DIRS]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
fixture_name
positional arguments:
fixture_name name of fixture to load
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--fixture-dir FIXTURE_DIRS
directory from which to load sql fixtures; later
directories take precedence
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
usage: dbami execute-sql [-h] [--project-directory PROJECT_DIRECTORY]
[--wait-timeout WAIT_TIMEOUT] [-d DATABASE_NAME]
options:
-h, --help show this help message and exit
--project-directory PROJECT_DIRECTORY
(default: '/Users/jkeifer/e84/dbami/dbami'; env:
$DBAMI_PROJECT_DIRECTORY)
--wait-timeout WAIT_TIMEOUT
seconds to wait for db connection (default: '60'; env:
$DBAMI_WAIT_TIMEOUT)
-d DATABASE_NAME, --database DATABASE_NAME
(required; env: $PGDATABASE)
Example:
echo "SELECT version();" | dbami execute-sql -d mydbusage: dbami version [-h]
options:
-h, --help show this help message and exit
A dbami project structure might look like this:
myproject/
├── migrations/
│ ├── 20240115120000_create_users_table.up.sql
│ ├── 20240115120000_create_users_table.down.sql
│ ├── 20240116093000_add_email_to_users.up.sql
│ └── 20240116093000_add_email_to_users.down.sql
├── fixtures/
│ ├── test_users.sql
│ └── sample_data.sql
└── schema.sql
The cli init command can start this for you. new will create new
migration files.
Migrations are SQL files that define database changes:
- Up migrations (
*.up.sql): Apply changes - Down migrations (
*.down.sql): Rollback changes
Migration files are named with the pattern:
YYYYMMDDHHMMSS_description.{up|down}.sql
The schema.sql file contains the complete database schema. This file should
be maintained to reflect the current state of your database schema after all
migrations have been applied. The verify command can help ensure your
schema.sql and migrations stay in sync.
To understand the motivations behind this design, see this discussion in the dbmate repo.
Fixtures are SQL files containing test data. They can be loaded using:
dbami load-fixture test_users -d mydbBy default, dbami uses advisory locks to prevent concurrent migrations. You can
disable this with --no-lock:
dbami migrate -d mydb --no-lockYou can use a custom table name or schema for tracking migrations:
dbami migrate -d mydb --schema-version-table myschema.migrationsMigrate to a specific version:
# Migrate up to (and including) a specific migration
dbami migrate -d mydb --target 20240115120000
# Rollback to a specific migration
dbami rollback -d mydb --target 20240115120000While dbami includes a comprehensive CLI, you can also use it programmatically via its async Python API:
from dbami.db import DB
from dbami.util import syncrun
async def setup_database():
db = DB("/path/to/project")
await db.create_database("mydb")
await db.migrate("mydb")
# Run async function
syncrun(setup_database())# Clone the repository
git clone https://github.com/yourusername/dbami.git
cd dbami
# Install with development dependencies
pip install -e ".[dev]"
# Run tests
pytestThis README uses cogapp to keep the CLI documentation up-to-date. To regenerate:
cog -r README.mddbami was initially inspired by the tool
dbmate, and therefore takes inspiration
from dbmate when it comes to name. That is, "ami" is French for "friend," a
synonym of "mate."
Other languages may have yielded suitable words for friend, but "ami" was
chosen due to its short length (easier to type) and the allowance for a future
golang-based implementation, which, most serendipitously, could be named
dbamigo.