Migrations¶
alchemiq ships a unified migration CLI that covers both PostgreSQL
(Alembic-backed) and ClickHouse (a custom, async runner) from a single
alchemiq entry-point. You never edit alembic.ini or write raw
env.py files - all configuration lives in pyproject.toml.
Installation¶
Migrations are an optional feature. For PostgreSQL install the
[migrations] extra (Alembic) together with the [postgres] extra
(the asyncpg driver the CLI connects with):
pip install "alchemiq[postgres,migrations]"
ClickHouse migrations are part of the [clickhouse] extra:
pip install "alchemiq[clickhouse]"
Configuration¶
Declare the [tool.alchemiq] section in your pyproject.toml. The CLI
searches upward from the working directory to find it:
[tool.alchemiq]
models = ["myapp.domain.models"] # import paths that register your models
[tool.alchemiq.postgres]
host = "${DB_HOST}" # ${VAR} placeholders expand from the environment
port = 5432 # optional, defaults to 5432
database = "myapp"
username = "${DB_USER}"
password = "${DB_PASSWORD}"
[tool.alchemiq.clickhouse]
host = "ch.internal"
port = 8123 # optional, defaults to 8123
database = "analytics"
username = "default"
password = "${CH_PASSWORD}"
secure = false # optional, defaults to false
Either [tool.alchemiq.postgres] or [tool.alchemiq.clickhouse] (or both)
can be present. Sections that are absent are simply skipped.
PostgreSQL migrations¶
PostgreSQL migrations are powered by Alembic. alchemiq wraps Alembic’s
autogenerate pipeline so that your custom field types (Email, Money,
Encrypted, etc.) map correctly to SQL column types without any extra
configuration.
Migrations are generated as standard Alembic revision files and stored under
the migrations/ directory (configurable via migrations_dir in
[tool.alchemiq]).
ClickHouse migrations¶
ClickHouse migrations use a custom async runner because Alembic does not
support ClickHouse. Each migration is a Python class that subclasses
Migration and declares revision, down_revision, up, and
down:
from alchemiq.migrations import Migration
from alchemiq.migrations.clickhouse.operations import Operations
class AddPageViews(Migration):
revision = "0002"
down_revision = "0001"
def up(self, op: Operations) -> None:
op.create_table("page_views", ...)
def down(self, op: Operations) -> None:
op.drop_table("page_views")
Migration history is tracked in a dedicated ClickHouse table so no external state store is needed.
Autogenerate support: the runner can detect new tables and new columns and
generate the corresponding up / down methods automatically. Destructive
operations (dropping columns, changing a table engine) are not autogenerated -
write those migrations by hand so no data loss is accidental.
Unified CLI¶
The five sub-commands below work identically for both backends. Omit
--db to run the command against every configured database; pass
--db postgres or --db clickhouse to target one:
Command |
Description |
|---|---|
|
Autogenerate a new migration from model changes |
|
Apply all pending migrations |
|
Roll back the last applied migration |
|
List applied and pending migrations |
|
Print the SQL for pending migrations without running them |
These five commands - makemigrations, migrate, rollback,
history, and showsql - are the complete migration surface in v1.
Common usage examples¶
# Autogenerate for both databases:
alchemiq makemigrations
# Autogenerate with a descriptive message (Postgres only):
alchemiq makemigrations -m "add user table" --db postgres
# Apply pending migrations to Postgres only:
alchemiq migrate --db postgres
# Roll back the last ClickHouse migration:
alchemiq rollback --db clickhouse
# Inspect pending SQL without running it:
alchemiq showsql
# Show migration history for both databases:
alchemiq history
Exit codes¶
Code |
Meaning |
|---|---|
|
Success |
|
Runtime error (connection failure, migration error) |
|
Configuration error (missing |
Multiple databases¶
When both [tool.alchemiq.postgres] and [tool.alchemiq.clickhouse] are
configured, running a command without --db executes it against both in
sequence - Postgres first, then ClickHouse. Each backend is independent: a
failure in one does not roll back the other.