ExpireEye Backend uses Alembic for database migrations, allowing you to version control your database schema and apply changes systematically.
Overview
Alembic is a database migration tool for SQLAlchemy. It tracks schema changes and allows you to:
- Version control your database schema
- Generate migrations automatically from model changes
- Apply or rollback migrations safely
- Collaborate with team members on schema changes
Prerequisites
Before working with migrations, ensure:
- Your environment is configured with valid database credentials
- The target database exists and is accessible
- You have the project dependencies installed
Migration Workflow
Initial Setup
The project comes pre-configured with Alembic. The configuration is located in:
alembic.ini - Main configuration file
alembic/env.py - Migration environment setup
alembic/versions/ - Generated migration scripts
The Alembic configuration in alembic/env.py automatically imports all models and constructs the database URL from environment variables.
Understanding the Configuration
The alembic/env.py file is configured to:
from app.models.base import Base
from app.models.user_model import User
from app.models.product_model import Product
from app.models.scanlog_model import ScanLog
from app.models.nutrition_model import Nutrition
from app.models.notification_model import Notification
from app.models.user_product import UserProduct
# Load environment variables
load_dotenv()
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
# Configure database URL
config.set_main_option(
"sqlalchemy.url",
f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}",
)
All model imports must be present in alembic/env.py for autogenerate to detect schema changes. If you create new models, add their imports here.
Common Migration Tasks
Creating a New Migration
When you modify your SQLAlchemy models, generate a migration to apply those changes:
Modify your models
Make changes to your model files in app/models/.
Generate the migration
Use Alembic’s autogenerate feature to create a migration script:alembic revision --autogenerate -m "Add user email verification"
This creates a new file in alembic/versions/ with the detected changes. Review the migration
Always review the generated migration script before applying it:# Check the latest migration file
ls -lt alembic/versions/ | head -n 2
Open the file and verify the upgrade() and downgrade() functions. Apply the migration
Apply the migration to your database:
Applying Migrations
# Apply all pending migrations
alembic upgrade head
Rolling Back Migrations
If you need to revert changes:
# Revert the most recent migration
alembic downgrade -1
Always backup your database before downgrading, especially in production. Data loss may occur if migrations drop tables or columns.
Viewing Migration History
Check the current state and history of migrations:
# Show the current database revision
alembic current
Database Schema
The ExpireEye Backend includes the following main models:
| Model | Purpose | Key Fields |
|---|
User | User accounts | id, email, password_hash, created_at |
Product | Product catalog | id, name, category, barcode |
UserProduct | User inventory | user_id, product_id, expiry_date, quantity |
Notification | User notifications | id, user_id, message, is_read |
ScanLog | Scan history | id, user_id, product_id, scanned_at |
Nutrition | Nutritional data | product_id, calories, protein, carbs |
Advanced Migration Scenarios
Creating Empty Migration
For manual schema changes or data migrations:
alembic revision -m "Add custom indexes"
Edit the generated file to add your custom operations:
def upgrade():
op.create_index('idx_user_email', 'users', ['email'])
op.create_index('idx_product_expiry', 'user_products', ['expiry_date'])
def downgrade():
op.drop_index('idx_product_expiry', 'user_products')
op.drop_index('idx_user_email', 'users')
Data Migrations
For populating or transforming data:
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import table, column
def upgrade():
# Define minimal table representation
users = table('users',
column('id', sa.Integer),
column('status', sa.String)
)
# Update existing data
op.execute(
users.update()
.where(users.c.status == None)
.values(status='active')
)
def downgrade():
pass # Data migrations often can't be reversed
Handling Conflicts
When multiple developers create migrations:
Identify the conflict
Look for branches in the revision history. Merge the branches
alembic merge -m "Merge migration branches" head1 head2
Apply the merged migration
Best Practices
Development Workflow
- Always review autogenerated migrations - Alembic may miss certain changes or generate incorrect operations
- Test migrations on a copy of production data - Ensure migrations work with real data
- Keep migrations small and focused - One logical change per migration
- Write descriptive migration messages - Use clear, actionable descriptions
alembic revision --autogenerate -m "Add user email verification field"
alembic revision --autogenerate -m "Create indexes for product search"
alembic revision --autogenerate -m "Add cascade delete to user_products"
Testing Migrations
Always test the complete migration cycle:
# Test upgrade
alembic upgrade head
# Verify the changes
mysql -u $DB_USER -p$DB_PASSWORD -e "SHOW TABLES; DESCRIBE users;" $DB_NAME
# Test downgrade
alembic downgrade -1
# Verify the rollback
mysql -u $DB_USER -p$DB_PASSWORD -e "DESCRIBE users;" $DB_NAME
# Re-apply
alembic upgrade head
Production Deployment
Before running migrations in production:
- Backup your database completely
- Test migrations on a staging environment with production data copy
- Schedule downtime if the migration requires it
- Have a rollback plan ready
Production migration workflow:
Backup the database
mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME > backup_$(date +%Y%m%d_%H%M%S).sql
Preview pending migrations
Verify application health
Test critical endpoints to ensure the application works correctly.
Troubleshooting
Common Issues
“Target database is not up to date”
This means your database is out of sync with the migrations:
# Check current state
alembic current
# Check expected state
alembic heads
# Stamp the database if needed (use cautiously)
alembic stamp head
“Can’t locate revision identified by ‘xyz’”
The migration file is missing or was deleted:
- Restore the migration file from version control
- Or stamp to the last known good revision:
alembic stamp abc123
Autogenerate detects no changes
Ensure your models are imported in alembic/env.py:
# Add missing model imports
from app.models.your_new_model import YourNewModel
Migration fails with foreign key constraint error
Reorder operations in the migration to handle dependencies:
def upgrade():
# Drop constraints first
op.drop_constraint('fk_user_product', 'user_products')
# Then modify tables
op.drop_column('user_products', 'old_column')
# Recreate constraints
op.create_foreign_key('fk_user_product', 'user_products', 'products', ['product_id'], ['id'])
Integration with Application
The FastAPI application automatically uses the migrated schema:
from sqlalchemy import create_engine
from app.models.base import Base
# The engine connects to the migrated database
engine = create_engine(DATABASE_URL)
No additional configuration is needed - once migrations are applied, the application will use the updated schema.
Next Steps
Additional Resources