diff --git a/migrations/README.md b/migrations/README.md new file mode 100644 index 00000000..520af4d2 --- /dev/null +++ b/migrations/README.md @@ -0,0 +1,123 @@ +# Database Migrations + +This directory contains Alembic database migrations for TradingAgents. + +## Quick Start + +```bash +# Apply all migrations +alembic upgrade head + +# Check current version +alembic current + +# View migration history +alembic history +``` + +## Migration Commands + +### Applying Migrations + +```bash +# Apply all pending migrations +alembic upgrade head + +# Apply to a specific revision +alembic upgrade 003 + +# Apply next migration only +alembic upgrade +1 +``` + +### Rolling Back Migrations + +```bash +# Roll back one migration +alembic downgrade -1 + +# Roll back to specific revision +alembic downgrade 002 + +# Roll back all migrations +alembic downgrade base +``` + +### Checking Status + +```bash +# Show current revision +alembic current + +# Show migration history +alembic history + +# Show pending migrations +alembic heads +``` + +## Creating New Migrations + +### Auto-generate from Model Changes + +After modifying models in `tradingagents/api/models/`: + +```bash +# Generate migration from model changes +alembic revision --autogenerate -m "Add new_feature table" +``` + +### Manual Migration + +```bash +# Create empty migration +alembic revision -m "Manual migration description" +``` + +Then edit the generated file in `migrations/versions/`. + +## Migration Files + +| Revision | Description | +|----------|-------------| +| 001 | Initial migration - Users and Strategies tables | +| 002 | User profile fields - tax_jurisdiction, timezone, api_key_hash | +| 003 | Portfolio model - live, paper, backtest types | +| 004 | Settings model - risk profiles, alert preferences | +| 005 | Trade model - execution history with CGT tracking | + +## SQLite Compatibility + +This project uses SQLite by default. For operations that SQLite doesn't support +natively (like ALTER CONSTRAINT), use batch mode: + +```python +with op.batch_alter_table('table_name') as batch_op: + batch_op.add_column(sa.Column('new_col', sa.String(50))) + batch_op.create_unique_constraint('uq_name', ['column']) +``` + +## Best Practices + +1. **Always test migrations locally** before committing +2. **Include both upgrade() and downgrade()** functions +3. **Use meaningful revision messages** +4. **Add docstrings** explaining what the migration does +5. **Use batch mode** for SQLite compatibility when needed +6. **Create indexes** for foreign keys and frequently queried columns + +## Troubleshooting + +### "Target database is not up to date" + +```bash +alembic stamp head # Mark current DB as up-to-date +``` + +### "Can't locate revision" + +Check that all migrations have correct `down_revision` values forming a chain. + +### SQLite Constraint Error + +Use `batch_alter_table` for constraint operations on SQLite. diff --git a/migrations/versions/002_add_user_profile_fields.py b/migrations/versions/002_add_user_profile_fields.py index 1dc908f0..d406dcaa 100644 --- a/migrations/versions/002_add_user_profile_fields.py +++ b/migrations/versions/002_add_user_profile_fields.py @@ -11,6 +11,8 @@ This migration adds four new fields to the users table: - is_verified: Email verification status (default: False) All existing users will get default values for the new required fields. + +Uses batch mode for SQLite compatibility. """ from typing import Sequence, Union @@ -33,48 +35,57 @@ def upgrade() -> None: - timezone defaults to "Australia/Sydney" - api_key_hash is NULL - is_verified is False + + Uses batch mode for SQLite compatibility with constraints. """ - # Add tax_jurisdiction column - op.add_column( - 'users', - sa.Column( - 'tax_jurisdiction', - sa.String(length=10), - nullable=False, - server_default='AU', - comment='Tax jurisdiction code (e.g., US, US-CA, AU-NSW)' + # Use batch_alter_table for SQLite compatibility + with op.batch_alter_table('users', schema=None) as batch_op: + # Add tax_jurisdiction column + batch_op.add_column( + sa.Column( + 'tax_jurisdiction', + sa.String(length=10), + nullable=False, + server_default='AU', + ) ) - ) - # Add timezone column - op.add_column( - 'users', - sa.Column( - 'timezone', - sa.String(length=50), - nullable=False, - server_default='Australia/Sydney', - comment='IANA timezone identifier (e.g., America/New_York, UTC)' + # Add timezone column + batch_op.add_column( + sa.Column( + 'timezone', + sa.String(length=50), + nullable=False, + server_default='Australia/Sydney', + ) ) - ) - # Add api_key_hash column with unique constraint and index - op.add_column( - 'users', - sa.Column( - 'api_key_hash', - sa.String(length=255), - nullable=True, - comment='Bcrypt hash of API key for programmatic access' + # Add api_key_hash column + batch_op.add_column( + sa.Column( + 'api_key_hash', + sa.String(length=255), + nullable=True, + ) ) - ) - # Create unique constraint for api_key_hash - op.create_unique_constraint( - 'uq_users_api_key_hash', - 'users', - ['api_key_hash'] - ) - # Create index for fast lookups + + # Add is_verified column + batch_op.add_column( + sa.Column( + 'is_verified', + sa.Boolean(), + nullable=False, + server_default='0', + ) + ) + + # Create unique constraint for api_key_hash + batch_op.create_unique_constraint( + 'uq_users_api_key_hash', + ['api_key_hash'] + ) + + # Create index for fast lookups (can be done outside batch) op.create_index( 'ix_users_api_key_hash', 'users', @@ -82,34 +93,24 @@ def upgrade() -> None: unique=False ) - # Add is_verified column - op.add_column( - 'users', - sa.Column( - 'is_verified', - sa.Boolean(), - nullable=False, - server_default='0', - comment='Whether user email has been verified' - ) - ) - def downgrade() -> None: """Remove tax_jurisdiction, timezone, api_key_hash, and is_verified columns from users table. WARNING: This will permanently delete data in these columns! + + Uses batch mode for SQLite compatibility. """ - # Remove is_verified column - op.drop_column('users', 'is_verified') - - # Remove api_key_hash column (drop index and constraint first) + # Drop index first (outside batch) op.drop_index('ix_users_api_key_hash', 'users') - op.drop_constraint('uq_users_api_key_hash', 'users', type_='unique') - op.drop_column('users', 'api_key_hash') - # Remove timezone column - op.drop_column('users', 'timezone') + # Use batch_alter_table for SQLite compatibility + with op.batch_alter_table('users', schema=None) as batch_op: + # Drop unique constraint + batch_op.drop_constraint('uq_users_api_key_hash', type_='unique') - # Remove tax_jurisdiction column - op.drop_column('users', 'tax_jurisdiction') + # Remove columns + batch_op.drop_column('is_verified') + batch_op.drop_column('api_key_hash') + batch_op.drop_column('timezone') + batch_op.drop_column('tax_jurisdiction')