feat(db): fix Alembic migrations and add documentation (#7) - SQLite batch mode for constraints, migrations README
This commit is contained in:
parent
1ea006e41f
commit
68be12c451
|
|
@ -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.
|
||||||
|
|
@ -11,6 +11,8 @@ This migration adds four new fields to the users table:
|
||||||
- is_verified: Email verification status (default: False)
|
- is_verified: Email verification status (default: False)
|
||||||
|
|
||||||
All existing users will get default values for the new required fields.
|
All existing users will get default values for the new required fields.
|
||||||
|
|
||||||
|
Uses batch mode for SQLite compatibility.
|
||||||
"""
|
"""
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
|
@ -33,48 +35,57 @@ def upgrade() -> None:
|
||||||
- timezone defaults to "Australia/Sydney"
|
- timezone defaults to "Australia/Sydney"
|
||||||
- api_key_hash is NULL
|
- api_key_hash is NULL
|
||||||
- is_verified is False
|
- is_verified is False
|
||||||
|
|
||||||
|
Uses batch mode for SQLite compatibility with constraints.
|
||||||
"""
|
"""
|
||||||
# Add tax_jurisdiction column
|
# Use batch_alter_table for SQLite compatibility
|
||||||
op.add_column(
|
with op.batch_alter_table('users', schema=None) as batch_op:
|
||||||
'users',
|
# Add tax_jurisdiction column
|
||||||
sa.Column(
|
batch_op.add_column(
|
||||||
'tax_jurisdiction',
|
sa.Column(
|
||||||
sa.String(length=10),
|
'tax_jurisdiction',
|
||||||
nullable=False,
|
sa.String(length=10),
|
||||||
server_default='AU',
|
nullable=False,
|
||||||
comment='Tax jurisdiction code (e.g., US, US-CA, AU-NSW)'
|
server_default='AU',
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Add timezone column
|
# Add timezone column
|
||||||
op.add_column(
|
batch_op.add_column(
|
||||||
'users',
|
sa.Column(
|
||||||
sa.Column(
|
'timezone',
|
||||||
'timezone',
|
sa.String(length=50),
|
||||||
sa.String(length=50),
|
nullable=False,
|
||||||
nullable=False,
|
server_default='Australia/Sydney',
|
||||||
server_default='Australia/Sydney',
|
)
|
||||||
comment='IANA timezone identifier (e.g., America/New_York, UTC)'
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Add api_key_hash column with unique constraint and index
|
# Add api_key_hash column
|
||||||
op.add_column(
|
batch_op.add_column(
|
||||||
'users',
|
sa.Column(
|
||||||
sa.Column(
|
'api_key_hash',
|
||||||
'api_key_hash',
|
sa.String(length=255),
|
||||||
sa.String(length=255),
|
nullable=True,
|
||||||
nullable=True,
|
)
|
||||||
comment='Bcrypt hash of API key for programmatic access'
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
# Create unique constraint for api_key_hash
|
# Add is_verified column
|
||||||
op.create_unique_constraint(
|
batch_op.add_column(
|
||||||
'uq_users_api_key_hash',
|
sa.Column(
|
||||||
'users',
|
'is_verified',
|
||||||
['api_key_hash']
|
sa.Boolean(),
|
||||||
)
|
nullable=False,
|
||||||
# Create index for fast lookups
|
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(
|
op.create_index(
|
||||||
'ix_users_api_key_hash',
|
'ix_users_api_key_hash',
|
||||||
'users',
|
'users',
|
||||||
|
|
@ -82,34 +93,24 @@ def upgrade() -> None:
|
||||||
unique=False
|
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:
|
def downgrade() -> None:
|
||||||
"""Remove tax_jurisdiction, timezone, api_key_hash, and is_verified columns from users table.
|
"""Remove tax_jurisdiction, timezone, api_key_hash, and is_verified columns from users table.
|
||||||
|
|
||||||
WARNING: This will permanently delete data in these columns!
|
WARNING: This will permanently delete data in these columns!
|
||||||
|
|
||||||
|
Uses batch mode for SQLite compatibility.
|
||||||
"""
|
"""
|
||||||
# Remove is_verified column
|
# Drop index first (outside batch)
|
||||||
op.drop_column('users', 'is_verified')
|
|
||||||
|
|
||||||
# Remove api_key_hash column (drop index and constraint first)
|
|
||||||
op.drop_index('ix_users_api_key_hash', 'users')
|
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
|
# Use batch_alter_table for SQLite compatibility
|
||||||
op.drop_column('users', 'timezone')
|
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
|
# Remove columns
|
||||||
op.drop_column('users', 'tax_jurisdiction')
|
batch_op.drop_column('is_verified')
|
||||||
|
batch_op.drop_column('api_key_hash')
|
||||||
|
batch_op.drop_column('timezone')
|
||||||
|
batch_op.drop_column('tax_jurisdiction')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue