-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[WIP] - SQL Model Data Layer #2469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
06dc0b2
c23dfd6
7820c1d
0b34727
220fbb6
39c87c8
0379fca
254608d
3e39563
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ build | |
dist | ||
|
||
*.egg-info | ||
*.ipynb | ||
|
||
.env | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Alembic Migrations for Chainlit SQLModelDataLayer | ||
|
||
This directory contains Alembic migration scripts for the SQLModel-based data layer. | ||
|
||
## Best Practices | ||
|
||
- **Do not use `SQLModel.metadata.create_all()` in production.** | ||
- Always manage schema changes with Alembic migrations. | ||
- Keep migration scripts in version control. | ||
- Run migrations before starting the app, or enable auto-migration with `CHAINLIT_AUTO_MIGRATE=true`. | ||
|
||
## Usage | ||
|
||
1. **Configure your database URL** in `alembic.ini`: | ||
```ini | ||
sqlalchemy.url = <your-database-url> | ||
``` | ||
|
||
2. **Autogenerate a migration** (after changing models): | ||
```bash | ||
alembic revision --autogenerate -m "Initial tables" | ||
``` | ||
|
||
3. **Apply migrations**: | ||
```bash | ||
alembic upgrade head | ||
``` | ||
|
||
## Initial Migration | ||
|
||
The first migration should create all tables defined in `chainlit.models`. | ||
|
||
## env.py | ||
|
||
Alembic is configured to use `SQLModel.metadata` from `chainlit.models`. | ||
|
||
--- | ||
|
||
For more details, see the [Alembic documentation](https://alembic.sqlalchemy.org/en/latest/). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import sys | ||
import os | ||
from logging.config import fileConfig | ||
from sqlalchemy import engine_from_config, pool | ||
from alembic import context | ||
|
||
# Add the parent directory to sys.path to import models | ||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) | ||
from chainlit.models import SQLModel | ||
|
||
# this is the Alembic Config object, which provides | ||
# access to the values within the .ini file in use. | ||
config = context.config | ||
|
||
# Interpret the config file for Python logging. | ||
# This line sets up loggers basically. | ||
fileConfig(config.config_file_name) | ||
|
||
target_metadata = SQLModel.metadata | ||
|
||
def run_migrations_offline(): | ||
""" | ||
Run migrations in 'offline' mode. | ||
""" | ||
url = config.get_main_option("sqlalchemy.url") | ||
context.configure( | ||
url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"} | ||
) | ||
|
||
with context.begin_transaction(): | ||
context.run_migrations() | ||
|
||
def run_migrations_online(): | ||
""" | ||
Run migrations in 'online' mode. | ||
""" | ||
connectable = engine_from_config( | ||
config.get_section(config.config_ini_section), | ||
prefix="sqlalchemy.", | ||
poolclass=pool.NullPool, | ||
) | ||
|
||
with connectable.connect() as connection: | ||
context.configure(connection=connection, target_metadata=target_metadata) | ||
|
||
with context.begin_transaction(): | ||
context.run_migrations() | ||
|
||
if context.is_offline_mode(): | ||
run_migrations_offline() | ||
else: | ||
run_migrations_online() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
""" | ||
Initial migration: migrate camelCase columns to snake_case for SQLModelDataLayer | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
Check failure on line 5 in backend/chainlit/data/alembic/versions/0001_create_tables.py
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = '0001_create_tables' | ||
Check failure on line 8 in backend/chainlit/data/alembic/versions/0001_create_tables.py
|
||
down_revision = None | ||
branch_labels = None | ||
depends_on = None | ||
|
||
def upgrade(): | ||
# Thread table: rename camelCase columns to snake_case | ||
with op.batch_alter_table('thread') as batch_op: | ||
Check failure on line 15 in backend/chainlit/data/alembic/versions/0001_create_tables.py
|
||
batch_op.rename_column('createdAt', 'created_at') | ||
Check failure on line 16 in backend/chainlit/data/alembic/versions/0001_create_tables.py
|
||
batch_op.rename_column('userId', 'user_id') | ||
Check failure on line 17 in backend/chainlit/data/alembic/versions/0001_create_tables.py
|
||
batch_op.rename_column('userIdentifier', 'user_identifier') | ||
# tags and metadata are already snake_case or compatible | ||
# Repeat for other tables (User, Step, Element, Feedback) | ||
with op.batch_alter_table('user') as batch_op: | ||
batch_op.rename_column('createdAt', 'created_at') | ||
with op.batch_alter_table('step') as batch_op: | ||
batch_op.rename_column('threadId', 'thread_id') | ||
batch_op.rename_column('parentId', 'parent_id') | ||
batch_op.rename_column('createdAt', 'created_at') | ||
with op.batch_alter_table('element') as batch_op: | ||
batch_op.rename_column('threadId', 'thread_id') | ||
batch_op.rename_column('objectKey', 'object_key') | ||
with op.batch_alter_table('feedback') as batch_op: | ||
batch_op.rename_column('forId', 'for_id') | ||
# If tables do not exist, Alembic will error; users should run this only once during migration. | ||
|
||
def downgrade(): | ||
# Reverse the renames for downgrade | ||
with op.batch_alter_table('thread') as batch_op: | ||
batch_op.rename_column('created_at', 'createdAt') | ||
batch_op.rename_column('user_id', 'userId') | ||
batch_op.rename_column('user_identifier', 'userIdentifier') | ||
with op.batch_alter_table('user') as batch_op: | ||
batch_op.rename_column('created_at', 'createdAt') | ||
with op.batch_alter_table('step') as batch_op: | ||
batch_op.rename_column('thread_id', 'threadId') | ||
batch_op.rename_column('parent_id', 'parentId') | ||
batch_op.rename_column('created_at', 'createdAt') | ||
with op.batch_alter_table('element') as batch_op: | ||
batch_op.rename_column('thread_id', 'threadId') | ||
batch_op.rename_column('object_key', 'objectKey') | ||
with op.batch_alter_table('feedback') as batch_op: | ||
batch_op.rename_column('for_id', 'forId') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the initial file should be the creation of the tables, right? So that new users do not have to problem to create the tables by themself.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Today our SQL tables are stored in cameCase which is abnormal so my idea for the 1st migration would rename the problematic columns like threadId to thread_id. That would be the most "hands-off" migration. I have not actually done that here yet though.
Alternatively we could create brand new tables in a new database schema and use COPY INTO scripts.
I really don't think keeping these as camelCase would be good long term as we'd have to handle this into the future for all changes and it's not the right way to make SQL tables.