Migrations¶
Oxyde provides Django-style migrations for schema management.
Overview¶
Migrations track database schema changes:
- Define models in Python
- Run
oxyde makemigrationsto generate migration files - Run
oxyde migrateto apply changes to the database
CLI Commands¶
makemigrations¶
Generate migration files from model changes:
# Generate migrations
oxyde makemigrations
# With custom name
oxyde makemigrations --name "add_user_profile"
# Dry run (show without creating)
oxyde makemigrations --dry-run
# Custom migrations directory
oxyde makemigrations --migrations-dir ./db/migrations
# Specify dialect
oxyde makemigrations --dialect postgres
migrate¶
Apply pending migrations:
# Apply all pending
oxyde migrate
# Custom migrations directory
oxyde migrate --migrations-dir ./db/migrations
# Target specific migration
oxyde migrate 0003
# Rollback to specific migration
oxyde migrate 0001
showmigrations¶
List migration status:
Output:
[X]= Applied[ ]= Pending
sqlmigration¶
Show SQL for a migration without running it:
Migration Files¶
Migrations are Python files in the migrations/ directory:
Migration Structure¶
# 0001_initial.py
dependencies = []
operations = [
{
"type": "create_table",
"table": {
"name": "users",
"columns": [
{"name": "id", "type": "INTEGER", "pk": True},
{"name": "name", "type": "TEXT", "nullable": False},
{"name": "email", "type": "TEXT", "unique": True},
],
"indexes": [
{"name": "ix_users_email", "columns": ["email"]},
],
},
},
]
Supported Operations¶
Create Table¶
{
"type": "create_table",
"table": {
"name": "users",
"columns": [
{"name": "id", "type": "INTEGER", "pk": True},
{"name": "name", "type": "TEXT"},
],
},
}
Drop Table¶
Add Column¶
{
"type": "add_column",
"table": "users",
"field": {
"name": "age",
"type": "INTEGER",
"nullable": True,
},
}
Drop Column¶
Alter Column¶
{
"type": "alter_column",
"table": "users",
"field": "name",
"changes": {
"nullable": False,
"type": "VARCHAR(255)",
},
}
Add Index¶
{
"type": "add_index",
"table": "users",
"index": {
"name": "ix_users_email",
"columns": ["email"],
"unique": True,
},
}
Drop Index¶
Add Foreign Key¶
{
"type": "add_foreign_key",
"table": "posts",
"constraint": {
"name": "fk_posts_author",
"column": "author_id",
"references_table": "users",
"references_column": "id",
"on_delete": "CASCADE",
},
}
Workflow Example¶
1. Define Initial Models¶
# models.py
from oxyde import OxydeModel, Field
class User(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
name: str
email: str = Field(db_unique=True)
2. Generate Initial Migration¶
Creates migrations/0001_initial.py.
3. Apply Migration¶
4. Add New Field¶
class User(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
name: str
email: str = Field(db_unique=True)
age: int | None = Field(default=None) # New field
5. Generate Migration for Change¶
Creates migrations/0002_add_age.py.
6. Apply New Migration¶
Database-Specific Considerations¶
PostgreSQL¶
- Full ALTER TABLE support
- Transactional DDL
- Concurrent index creation
SQLite¶
- Limited ALTER TABLE (add column only)
- Table recreation for complex changes
- No transactional DDL
MySQL¶
- ALTER TABLE with some limitations
- No transactional DDL
- Column changes may require data copy
Migration Dependencies¶
Specify dependencies for ordering:
# 0003_add_posts.py
dependencies = ["0002_add_age"]
operations = [
{
"type": "create_table",
"table": {
"name": "posts",
"columns": [
{"name": "id", "type": "INTEGER", "pk": True},
{"name": "title", "type": "TEXT"},
{"name": "author_id", "type": "INTEGER"},
],
"foreign_keys": [
{
"column": "author_id",
"references_table": "users",
"references_column": "id",
"on_delete": "CASCADE",
},
],
},
},
]
Best Practices¶
1. Review Generated Migrations¶
Always review generated SQL before applying:
2. Test on Development First¶
3. One Change Per Migration¶
# Good
oxyde makemigrations --name "add_user_age"
oxyde makemigrations --name "add_user_bio"
# Avoid: multiple unrelated changes
oxyde makemigrations --name "various_changes"
4. Don't Edit Applied Migrations¶
Once a migration is applied to production, create new migrations for fixes.
5. Keep Migrations in Version Control¶
Commit migration files alongside model changes.
Troubleshooting¶
Migration Not Detected¶
Ensure models are imported before running makemigrations:
Schema Mismatch¶
If the database is out of sync:
# Show current state
oxyde showmigrations
# Fake migration (mark as applied without running)
oxyde migrate --fake 0002
Rollback Failed Migration¶
Complete Example¶
# models.py
from datetime import datetime
from oxyde import OxydeModel, Field, Index
class User(OxydeModel):
class Meta:
is_table = True
table_name = "users"
id: int | None = Field(default=None, db_pk=True)
email: str = Field(db_unique=True)
name: str
created_at: datetime = Field(db_default="CURRENT_TIMESTAMP")
class Post(OxydeModel):
class Meta:
is_table = True
table_name = "posts"
indexes = [
Index(("author_id", "created_at")),
]
id: int | None = Field(default=None, db_pk=True)
title: str
content: str
author: "User" | None = Field(default=None, db_on_delete="CASCADE")
created_at: datetime = Field(db_default="CURRENT_TIMESTAMP")
# Generate and apply
oxyde makemigrations --name "initial"
oxyde migrate
# Check status
oxyde showmigrations
# [X] 0001_initial.py
Next Steps¶
- Models — Model definition
- Fields — Field options
- Connections — Database connections