Migrations
Ryx can introspect your database, detect schema changes, and generate DDL automatically โ with full multi-database routing and colored CLI output.
CLI in Actionโ
The CLI uses ANSI colors on supported terminals (macOS, Linux). Colors auto-disable when NO_COLOR is set or output is piped:
[ryx] โ 0001_initial โ bold blue prefix, green check
[ryx] โ No migration files exist โ yellow warning
[ryx] Skipped database blog โ yellow "Skipped", magenta alias
Two Approachesโ
Direct Migration (No Files)โ
Best for prototyping and simple projects:
from ryx.migrations import MigrationRunner
await ryx.setup("sqlite:///app.db")
runner = MigrationRunner([Author, Post, Tag])
await runner.migrate()
# Preview without applying
await runner.migrate(dry_run=True)
File-Based Migrationsโ
Best for production and team projects:
# Generate migration files
python -m ryx makemigrations \
--models myapp.models \
--dir migrations/
# Apply migrations
python -m ryx migrate \
--url postgres://user:pass@localhost/mydb \
--models myapp.models
# Preview SQL
python -m ryx sqlmigrate 0001_initial --dir migrations/
# Check status
python -m ryx showmigrations \
--url postgres://user:pass@localhost/mydb \
--dir migrations/
Migration File Formatโ
Generated migration files are clean, multi-line Python with model class references for automatic database routing:
from myapp.models import Author, Post
from ryx.migrations.autodetect import CreateTable, AddField, ColumnState
Migration = [
CreateTable(
table='authors',
columns=[
ColumnState(name='id', db_type='INTEGER', primary_key=True, unique=True),
ColumnState(name='name', db_type='VARCHAR(100)'),
],
model=Author,
),
CreateTable(
table='posts',
columns=[
ColumnState(name='id', db_type='INTEGER', primary_key=True, unique=True),
ColumnState(name='title', db_type='VARCHAR(200)'),
ColumnState(name='author_id', db_type='INTEGER'),
],
model=Post,
),
AddField(
table='posts',
column=ColumnState(name='views', db_type='INTEGER', nullable=False),
model=Post,
),
]
Key features:
- Each operation embeds the Model class (
model=Author) so the runner knows which database it belongs to viamodel._meta.database - Models are imported at the top of the file (
from myapp.models import Author) - Output is indented and readable, one argument per line
- Backward compatible โ legacy files without
model=still work
File Discoveryโ
Migration files are discovered recursively under the migrations directory. All files matching [0-9]*.py are found, sorted globally by numeric prefix:
migrations/
โโโ 0001_initial.py
โโโ 0002_add_views.py
โโโ blog/
โ โโโ 0001_blog_tables.py
โ โโโ 0002_add_tags.py
โโโ shop/
โโโ 0001_shop_tables.py
Files in subdirectories are found automatically โ no per-alias configuration needed.
Multi-Database Routingโ
When using multiple databases, each operation knows its target database from the embedded Model class. The runner:
- Discovers all migration files recursively
- For each database alias, filters operations whose
model._meta.databasematches - Applies only relevant operations per alias
- Tracks applied state as
alias|steminryx_migrations
# Apply only to the "blog" alias
python -m ryx migrate --database blog
# Target a specific PostgreSQL schema (schema-per-tenant)
python -m ryx migrate --schema tenant1
# Non-interactive mode (for CI/CD)
python -m ryx migrate --no-interactive
Interactive Fallbackโ
When no migration files exist yet, ryx migrate offers an interactive menu:
[ryx] โ No migration files exist for database default
3 model(s) are not yet tracked.
L)ive DDL โ apply changes directly (development only)
A)uto-generate migration files, then migrate
M)anual โ run ryx makemigrations first
S)kip this database for now
[ryx] Choice [S]:
Use --no-interactive to skip this prompt in scripts or CI/CD (exits with a hint instead).
Migration Trackingโ
Ryx creates a ryx_migrations table to track applied migrations:
| Column | Type |
|---|---|
id | INTEGER |
name | TEXT (`alias |
applied_at | TIMESTAMP |
Applied entries use alias|stem (e.g. default|0001_initial, blog|0001_blog_tables). Legacy bare-stem entries are still recognized for backward compatibility.
What Migrations Handleโ
- Creating and dropping tables
- Adding, altering, and dropping columns
- Creating and dropping indexes
- Adding and dropping constraints
- Creating ManyToMany join tables
- Unique constraints and composite indexes
DDL Generationโ
Generate backend-aware DDL programmatically:
from ryx.migrations import generate_schema_ddl, DDLGenerator
# All models at once
stmts = generate_schema_ddl([Author, Post], backend="postgres")
for sql in stmts:
print(sql)
# Fine-grained control
gen = DDLGenerator("sqlite")
print(gen.create_table(Post._meta_to_table_state()))
print(gen.add_column("posts", column_state))
Backend Differencesโ
| Feature | PostgreSQL | MySQL | SQLite |
|---|---|---|---|---|
| ALTER COLUMN | Yes | Yes | No (recreate table) |
| Native UUID | Yes | No | No |
| SERIAL | Yes | No | No |
| JSONB | Yes | No | No |
| Array types | Yes | No | No |
| Database schemas | "schema"."table" | No | No |
Next Stepsโ
โ Filtering โ Start querying your data โ CLI Reference โ All migration commands