From 2c2d06c31b463a87d4ece2a4eb4c1d2f73cd3980 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 12 May 2026 03:45:59 +0900 Subject: [PATCH] refac: deprecate peewee migration layer The Alembic init migration (7e5b5dc7342b) already creates the equivalent schema. Peewee migrations are no longer needed for any version >= 0.3.6. --- backend/open_webui/internal/db.py | 35 +- .../internal/migrations/001_initial_schema.py | 253 ------------- .../migrations/002_add_local_sharing.py | 45 --- .../migrations/003_add_auth_api_key.py | 45 --- .../internal/migrations/004_add_archived.py | 45 --- .../internal/migrations/005_add_updated_at.py | 125 ------ .../006_migrate_timestamps_and_charfields.py | 129 ------- .../migrations/007_add_user_last_active_at.py | 78 ---- .../internal/migrations/008_add_memory.py | 52 --- .../internal/migrations/009_add_models.py | 60 --- .../010_migrate_modelfiles_to_models.py | 130 ------- .../migrations/011_add_user_settings.py | 47 --- .../internal/migrations/012_add_tools.py | 60 --- .../internal/migrations/013_add_user_info.py | 47 --- .../internal/migrations/014_add_files.py | 54 --- .../internal/migrations/015_add_functions.py | 60 --- .../016_add_valves_and_is_active.py | 49 --- .../migrations/017_add_user_oauth_sub.py | 44 --- .../migrations/018_add_function_is_global.py | 48 --- backend/open_webui/migrations/env.py | 116 +++--- backend/open_webui/migrations/util.py | 19 +- .../migrations/versions/7e5b5dc7342b_init.py | 358 +++++++++--------- .../versions/ca81bd47c050_add_config_table.py | 33 +- 23 files changed, 258 insertions(+), 1674 deletions(-) delete mode 100644 backend/open_webui/internal/migrations/001_initial_schema.py delete mode 100644 backend/open_webui/internal/migrations/002_add_local_sharing.py delete mode 100644 backend/open_webui/internal/migrations/003_add_auth_api_key.py delete mode 100644 backend/open_webui/internal/migrations/004_add_archived.py delete mode 100644 backend/open_webui/internal/migrations/005_add_updated_at.py delete mode 100644 backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py delete mode 100644 backend/open_webui/internal/migrations/007_add_user_last_active_at.py delete mode 100644 backend/open_webui/internal/migrations/008_add_memory.py delete mode 100644 backend/open_webui/internal/migrations/009_add_models.py delete mode 100644 backend/open_webui/internal/migrations/010_migrate_modelfiles_to_models.py delete mode 100644 backend/open_webui/internal/migrations/011_add_user_settings.py delete mode 100644 backend/open_webui/internal/migrations/012_add_tools.py delete mode 100644 backend/open_webui/internal/migrations/013_add_user_info.py delete mode 100644 backend/open_webui/internal/migrations/014_add_files.py delete mode 100644 backend/open_webui/internal/migrations/015_add_functions.py delete mode 100644 backend/open_webui/internal/migrations/016_add_valves_and_is_active.py delete mode 100644 backend/open_webui/internal/migrations/017_add_user_oauth_sub.py delete mode 100644 backend/open_webui/internal/migrations/018_add_function_is_global.py diff --git a/backend/open_webui/internal/db.py b/backend/open_webui/internal/db.py index 9a6576fd7c..1e91b14768 100644 --- a/backend/open_webui/internal/db.py +++ b/backend/open_webui/internal/db.py @@ -6,7 +6,7 @@ from contextlib import asynccontextmanager, contextmanager from typing import Any, Optional from urllib.parse import parse_qs, urlencode, urlparse, urlunparse -from open_webui.internal.wrappers import register_connection + from open_webui.env import ( OPEN_WEBUI_DIR, DATABASE_URL, @@ -25,7 +25,6 @@ from open_webui.env import ( DATABASE_SQLITE_PRAGMA_JOURNAL_SIZE_LIMIT, ENABLE_DB_MIGRATIONS, ) -from peewee_migrate import Router from sqlalchemy import Dialect, create_engine, MetaData, event, types from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.ext.declarative import declarative_base @@ -139,39 +138,7 @@ class JSONField(types.TypeDecorator): return json.loads(value) -# Workaround to handle the peewee migration -# This is required to ensure the peewee migration is handled before the alembic migration -def handle_peewee_migration(DATABASE_URL): - db = None - try: - # Normalize SSL params so psycopg2 always sees `sslmode=` (never `ssl=`) - # and cert-file params are preserved in the connection string. - url_without_ssl, ssl_params = extract_ssl_params_from_url(DATABASE_URL) - normalized_url = reattach_ssl_params_to_url(url_without_ssl, ssl_params) - # Replace the postgresql:// with postgres:// to handle the peewee migration - db = register_connection(normalized_url.replace('postgresql://', 'postgres://')) - migrate_dir = OPEN_WEBUI_DIR / 'internal' / 'migrations' - router = Router(db, logger=log, migrate_dir=migrate_dir) - router.run() - db.close() - - except Exception as e: - log.error(f'Failed to initialize the database connection: {e}') - log.warning('Hint: If your database password contains special characters, you may need to URL-encode it.') - raise - finally: - # Properly closing the database connection - if db and not db.is_closed(): - db.close() - - # Assert if db connection has been closed - if db is not None: - assert db.is_closed(), 'Database connection is still open.' - - -if ENABLE_DB_MIGRATIONS: - handle_peewee_migration(DATABASE_URL) # Normalize SSL params from the URL once; the sync engine needs them diff --git a/backend/open_webui/internal/migrations/001_initial_schema.py b/backend/open_webui/internal/migrations/001_initial_schema.py deleted file mode 100644 index 4268201ae7..0000000000 --- a/backend/open_webui/internal/migrations/001_initial_schema.py +++ /dev/null @@ -1,253 +0,0 @@ -"""Peewee migrations -- 001_initial_schema.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # We perform different migrations for SQLite and other databases - # This is because SQLite is very loose with enforcing its schema, and trying to migrate other databases like SQLite - # will require per-database SQL queries. - # Instead, we assume that because external DB support was added at a later date, it is safe to assume a newer base - # schema instead of trying to migrate from an older schema. - if isinstance(database, pw.SqliteDatabase): - migrate_sqlite(migrator, database, fake=fake) - else: - migrate_external(migrator, database, fake=fake) - - -def migrate_sqlite(migrator: Migrator, database: pw.Database, *, fake=False): - @migrator.create_model - class Auth(pw.Model): - id = pw.CharField(max_length=255, unique=True) - email = pw.CharField(max_length=255) - password = pw.CharField(max_length=255) - active = pw.BooleanField() - - class Meta: - table_name = 'auth' - - @migrator.create_model - class Chat(pw.Model): - id = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.CharField() - chat = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'chat' - - @migrator.create_model - class ChatIdTag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - tag_name = pw.CharField(max_length=255) - chat_id = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'chatidtag' - - @migrator.create_model - class Document(pw.Model): - id = pw.AutoField() - collection_name = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255, unique=True) - title = pw.CharField() - filename = pw.CharField() - content = pw.TextField(null=True) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'document' - - @migrator.create_model - class Modelfile(pw.Model): - id = pw.AutoField() - tag_name = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - modelfile = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'modelfile' - - @migrator.create_model - class Prompt(pw.Model): - id = pw.AutoField() - command = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.CharField() - content = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'prompt' - - @migrator.create_model - class Tag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - data = pw.TextField(null=True) - - class Meta: - table_name = 'tag' - - @migrator.create_model - class User(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - email = pw.CharField(max_length=255) - role = pw.CharField(max_length=255) - profile_image_url = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'user' - - -def migrate_external(migrator: Migrator, database: pw.Database, *, fake=False): - @migrator.create_model - class Auth(pw.Model): - id = pw.CharField(max_length=255, unique=True) - email = pw.CharField(max_length=255) - password = pw.TextField() - active = pw.BooleanField() - - class Meta: - table_name = 'auth' - - @migrator.create_model - class Chat(pw.Model): - id = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.TextField() - chat = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'chat' - - @migrator.create_model - class ChatIdTag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - tag_name = pw.CharField(max_length=255) - chat_id = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'chatidtag' - - @migrator.create_model - class Document(pw.Model): - id = pw.AutoField() - collection_name = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255, unique=True) - title = pw.TextField() - filename = pw.TextField() - content = pw.TextField(null=True) - user_id = pw.CharField(max_length=255) - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'document' - - @migrator.create_model - class Modelfile(pw.Model): - id = pw.AutoField() - tag_name = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - modelfile = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'modelfile' - - @migrator.create_model - class Prompt(pw.Model): - id = pw.AutoField() - command = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - title = pw.TextField() - content = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'prompt' - - @migrator.create_model - class Tag(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - user_id = pw.CharField(max_length=255) - data = pw.TextField(null=True) - - class Meta: - table_name = 'tag' - - @migrator.create_model - class User(pw.Model): - id = pw.CharField(max_length=255, unique=True) - name = pw.CharField(max_length=255) - email = pw.CharField(max_length=255) - role = pw.CharField(max_length=255) - profile_image_url = pw.TextField() - timestamp = pw.BigIntegerField() - - class Meta: - table_name = 'user' - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('user') - - migrator.remove_model('tag') - - migrator.remove_model('prompt') - - migrator.remove_model('modelfile') - - migrator.remove_model('document') - - migrator.remove_model('chatidtag') - - migrator.remove_model('chat') - - migrator.remove_model('auth') diff --git a/backend/open_webui/internal/migrations/002_add_local_sharing.py b/backend/open_webui/internal/migrations/002_add_local_sharing.py deleted file mode 100644 index e3e557602b..0000000000 --- a/backend/open_webui/internal/migrations/002_add_local_sharing.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields('chat', share_id=pw.CharField(max_length=255, null=True, unique=True)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields('chat', 'share_id') diff --git a/backend/open_webui/internal/migrations/003_add_auth_api_key.py b/backend/open_webui/internal/migrations/003_add_auth_api_key.py deleted file mode 100644 index acb63fc728..0000000000 --- a/backend/open_webui/internal/migrations/003_add_auth_api_key.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields('user', api_key=pw.CharField(max_length=255, null=True, unique=True)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields('user', 'api_key') diff --git a/backend/open_webui/internal/migrations/004_add_archived.py b/backend/open_webui/internal/migrations/004_add_archived.py deleted file mode 100644 index abed1727b9..0000000000 --- a/backend/open_webui/internal/migrations/004_add_archived.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields('chat', archived=pw.BooleanField(default=False)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields('chat', 'archived') diff --git a/backend/open_webui/internal/migrations/005_add_updated_at.py b/backend/open_webui/internal/migrations/005_add_updated_at.py deleted file mode 100644 index bff311e2d4..0000000000 --- a/backend/open_webui/internal/migrations/005_add_updated_at.py +++ /dev/null @@ -1,125 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - if isinstance(database, pw.SqliteDatabase): - migrate_sqlite(migrator, database, fake=fake) - else: - migrate_external(migrator, database, fake=fake) - - -def migrate_sqlite(migrator: Migrator, database: pw.Database, *, fake=False): - # Adding fields created_at and updated_at to the 'chat' table - migrator.add_fields( - 'chat', - created_at=pw.DateTimeField(null=True), # Allow null for transition - updated_at=pw.DateTimeField(null=True), # Allow null for transition - ) - - # Populate the new fields from an existing 'timestamp' field - migrator.sql('UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL') - - # Now that the data has been copied, remove the original 'timestamp' field - migrator.remove_fields('chat', 'timestamp') - - # Update the fields to be not null now that they are populated - migrator.change_fields( - 'chat', - created_at=pw.DateTimeField(null=False), - updated_at=pw.DateTimeField(null=False), - ) - - -def migrate_external(migrator: Migrator, database: pw.Database, *, fake=False): - # Adding fields created_at and updated_at to the 'chat' table - migrator.add_fields( - 'chat', - created_at=pw.BigIntegerField(null=True), # Allow null for transition - updated_at=pw.BigIntegerField(null=True), # Allow null for transition - ) - - # Populate the new fields from an existing 'timestamp' field - migrator.sql('UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL') - - # Now that the data has been copied, remove the original 'timestamp' field - migrator.remove_fields('chat', 'timestamp') - - # Update the fields to be not null now that they are populated - migrator.change_fields( - 'chat', - created_at=pw.BigIntegerField(null=False), - updated_at=pw.BigIntegerField(null=False), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - if isinstance(database, pw.SqliteDatabase): - rollback_sqlite(migrator, database, fake=fake) - else: - rollback_external(migrator, database, fake=fake) - - -def rollback_sqlite(migrator: Migrator, database: pw.Database, *, fake=False): - # Recreate the timestamp field initially allowing null values for safe transition - migrator.add_fields('chat', timestamp=pw.DateTimeField(null=True)) - - # Copy the earliest created_at date back into the new timestamp field - # This assumes created_at was originally a copy of timestamp - migrator.sql('UPDATE chat SET timestamp = created_at') - - # Remove the created_at and updated_at fields - migrator.remove_fields('chat', 'created_at', 'updated_at') - - # Finally, alter the timestamp field to not allow nulls if that was the original setting - migrator.change_fields('chat', timestamp=pw.DateTimeField(null=False)) - - -def rollback_external(migrator: Migrator, database: pw.Database, *, fake=False): - # Recreate the timestamp field initially allowing null values for safe transition - migrator.add_fields('chat', timestamp=pw.BigIntegerField(null=True)) - - # Copy the earliest created_at date back into the new timestamp field - # This assumes created_at was originally a copy of timestamp - migrator.sql('UPDATE chat SET timestamp = created_at') - - # Remove the created_at and updated_at fields - migrator.remove_fields('chat', 'created_at', 'updated_at') - - # Finally, alter the timestamp field to not allow nulls if that was the original setting - migrator.change_fields('chat', timestamp=pw.BigIntegerField(null=False)) diff --git a/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py b/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py deleted file mode 100644 index 86f90eb880..0000000000 --- a/backend/open_webui/internal/migrations/006_migrate_timestamps_and_charfields.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Peewee migrations -- 006_migrate_timestamps_and_charfields.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Alter the tables with timestamps - migrator.change_fields( - 'chatidtag', - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - 'document', - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - 'modelfile', - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - 'prompt', - timestamp=pw.BigIntegerField(), - ) - migrator.change_fields( - 'user', - timestamp=pw.BigIntegerField(), - ) - # Alter the tables with varchar to text where necessary - migrator.change_fields( - 'auth', - password=pw.TextField(), - ) - migrator.change_fields( - 'chat', - title=pw.TextField(), - ) - migrator.change_fields( - 'document', - title=pw.TextField(), - filename=pw.TextField(), - ) - migrator.change_fields( - 'prompt', - title=pw.TextField(), - ) - migrator.change_fields( - 'user', - profile_image_url=pw.TextField(), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - if isinstance(database, pw.SqliteDatabase): - # Alter the tables with timestamps - migrator.change_fields( - 'chatidtag', - timestamp=pw.DateField(), - ) - migrator.change_fields( - 'document', - timestamp=pw.DateField(), - ) - migrator.change_fields( - 'modelfile', - timestamp=pw.DateField(), - ) - migrator.change_fields( - 'prompt', - timestamp=pw.DateField(), - ) - migrator.change_fields( - 'user', - timestamp=pw.DateField(), - ) - migrator.change_fields( - 'auth', - password=pw.CharField(max_length=255), - ) - migrator.change_fields( - 'chat', - title=pw.CharField(), - ) - migrator.change_fields( - 'document', - title=pw.CharField(), - filename=pw.CharField(), - ) - migrator.change_fields( - 'prompt', - title=pw.CharField(), - ) - migrator.change_fields( - 'user', - profile_image_url=pw.CharField(), - ) diff --git a/backend/open_webui/internal/migrations/007_add_user_last_active_at.py b/backend/open_webui/internal/migrations/007_add_user_last_active_at.py deleted file mode 100644 index 19a26c3515..0000000000 --- a/backend/open_webui/internal/migrations/007_add_user_last_active_at.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Adding fields created_at and updated_at to the 'user' table - migrator.add_fields( - 'user', - created_at=pw.BigIntegerField(null=True), # Allow null for transition - updated_at=pw.BigIntegerField(null=True), # Allow null for transition - last_active_at=pw.BigIntegerField(null=True), # Allow null for transition - ) - - # Populate the new fields from an existing 'timestamp' field - migrator.sql( - 'UPDATE "user" SET created_at = timestamp, updated_at = timestamp, last_active_at = timestamp WHERE timestamp IS NOT NULL' - ) - - # Now that the data has been copied, remove the original 'timestamp' field - migrator.remove_fields('user', 'timestamp') - - # Update the fields to be not null now that they are populated - migrator.change_fields( - 'user', - created_at=pw.BigIntegerField(null=False), - updated_at=pw.BigIntegerField(null=False), - last_active_at=pw.BigIntegerField(null=False), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - # Recreate the timestamp field initially allowing null values for safe transition - migrator.add_fields('user', timestamp=pw.BigIntegerField(null=True)) - - # Copy the earliest created_at date back into the new timestamp field - # This assumes created_at was originally a copy of timestamp - migrator.sql('UPDATE "user" SET timestamp = created_at') - - # Remove the created_at and updated_at fields - migrator.remove_fields('user', 'created_at', 'updated_at', 'last_active_at') - - # Finally, alter the timestamp field to not allow nulls if that was the original setting - migrator.change_fields('user', timestamp=pw.BigIntegerField(null=False)) diff --git a/backend/open_webui/internal/migrations/008_add_memory.py b/backend/open_webui/internal/migrations/008_add_memory.py deleted file mode 100644 index f3af64fe95..0000000000 --- a/backend/open_webui/internal/migrations/008_add_memory.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - @migrator.create_model - class Memory(pw.Model): - id = pw.CharField(max_length=255, unique=True) - user_id = pw.CharField(max_length=255) - content = pw.TextField(null=False) - updated_at = pw.BigIntegerField(null=False) - created_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = 'memory' - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('memory') diff --git a/backend/open_webui/internal/migrations/009_add_models.py b/backend/open_webui/internal/migrations/009_add_models.py deleted file mode 100644 index 45f4a3d163..0000000000 --- a/backend/open_webui/internal/migrations/009_add_models.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class Model(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - base_model_id = pw.TextField(null=True) - - name = pw.TextField() - - meta = pw.TextField() - params = pw.TextField() - - created_at = pw.BigIntegerField(null=False) - updated_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = 'model' - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('model') diff --git a/backend/open_webui/internal/migrations/010_migrate_modelfiles_to_models.py b/backend/open_webui/internal/migrations/010_migrate_modelfiles_to_models.py deleted file mode 100644 index e523d6a098..0000000000 --- a/backend/open_webui/internal/migrations/010_migrate_modelfiles_to_models.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator -import json - -from open_webui.utils.misc import parse_ollama_modelfile - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Fetch data from 'modelfile' table and insert into 'model' table - migrate_modelfile_to_model(migrator, database) - # Drop the 'modelfile' table - migrator.remove_model('modelfile') - - -def migrate_modelfile_to_model(migrator: Migrator, database: pw.Database): - ModelFile = migrator.orm['modelfile'] - Model = migrator.orm['model'] - - modelfiles = ModelFile.select() - - for modelfile in modelfiles: - # Extract and transform data in Python - - modelfile.modelfile = json.loads(modelfile.modelfile) - meta = json.dumps( - { - 'description': modelfile.modelfile.get('desc'), - 'profile_image_url': modelfile.modelfile.get('imageUrl'), - 'ollama': {'modelfile': modelfile.modelfile.get('content')}, - 'suggestion_prompts': modelfile.modelfile.get('suggestionPrompts'), - 'categories': modelfile.modelfile.get('categories'), - 'user': {**modelfile.modelfile.get('user', {}), 'community': True}, - } - ) - - info = parse_ollama_modelfile(modelfile.modelfile.get('content')) - - # Insert the processed data into the 'model' table - Model.create( - id=f'ollama-{modelfile.tag_name}', - user_id=modelfile.user_id, - base_model_id=info.get('base_model_id'), - name=modelfile.modelfile.get('title'), - meta=meta, - params=json.dumps(info.get('params', {})), - created_at=modelfile.timestamp, - updated_at=modelfile.timestamp, - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - recreate_modelfile_table(migrator, database) - move_data_back_to_modelfile(migrator, database) - migrator.remove_model('model') - - -def recreate_modelfile_table(migrator: Migrator, database: pw.Database): - query = """ - CREATE TABLE IF NOT EXISTS modelfile ( - user_id TEXT, - tag_name TEXT, - modelfile JSON, - timestamp BIGINT - ) - """ - migrator.sql(query) - - -def move_data_back_to_modelfile(migrator: Migrator, database: pw.Database): - Model = migrator.orm['model'] - Modelfile = migrator.orm['modelfile'] - - models = Model.select() - - for model in models: - # Extract and transform data in Python - meta = json.loads(model.meta) - - modelfile_data = { - 'title': model.name, - 'desc': meta.get('description'), - 'imageUrl': meta.get('profile_image_url'), - 'content': meta.get('ollama', {}).get('modelfile'), - 'suggestionPrompts': meta.get('suggestion_prompts'), - 'categories': meta.get('categories'), - 'user': {k: v for k, v in meta.get('user', {}).items() if k != 'community'}, - } - - # Insert the processed data back into the 'modelfile' table - Modelfile.create( - user_id=model.user_id, - tag_name=model.id, - modelfile=modelfile_data, - timestamp=model.created_at, - ) diff --git a/backend/open_webui/internal/migrations/011_add_user_settings.py b/backend/open_webui/internal/migrations/011_add_user_settings.py deleted file mode 100644 index 73d27392f7..0000000000 --- a/backend/open_webui/internal/migrations/011_add_user_settings.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Adding fields settings to the 'user' table - migrator.add_fields('user', settings=pw.TextField(null=True)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - # Remove the settings field - migrator.remove_fields('user', 'settings') diff --git a/backend/open_webui/internal/migrations/012_add_tools.py b/backend/open_webui/internal/migrations/012_add_tools.py deleted file mode 100644 index a488678c3c..0000000000 --- a/backend/open_webui/internal/migrations/012_add_tools.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class Tool(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - - name = pw.TextField() - content = pw.TextField() - specs = pw.TextField() - - meta = pw.TextField() - - created_at = pw.BigIntegerField(null=False) - updated_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = 'tool' - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('tool') diff --git a/backend/open_webui/internal/migrations/013_add_user_info.py b/backend/open_webui/internal/migrations/013_add_user_info.py deleted file mode 100644 index db77cfff3a..0000000000 --- a/backend/open_webui/internal/migrations/013_add_user_info.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Peewee migrations -- 002_add_local_sharing.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - # Adding fields info to the 'user' table - migrator.add_fields('user', info=pw.TextField(null=True)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - # Remove the settings field - migrator.remove_fields('user', 'info') diff --git a/backend/open_webui/internal/migrations/014_add_files.py b/backend/open_webui/internal/migrations/014_add_files.py deleted file mode 100644 index 9c01ac08c3..0000000000 --- a/backend/open_webui/internal/migrations/014_add_files.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class File(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - filename = pw.TextField() - meta = pw.TextField() - created_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = 'file' - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('file') diff --git a/backend/open_webui/internal/migrations/015_add_functions.py b/backend/open_webui/internal/migrations/015_add_functions.py deleted file mode 100644 index 488e546ab1..0000000000 --- a/backend/open_webui/internal/migrations/015_add_functions.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - @migrator.create_model - class Function(pw.Model): - id = pw.TextField(unique=True) - user_id = pw.TextField() - - name = pw.TextField() - type = pw.TextField() - - content = pw.TextField() - meta = pw.TextField() - - created_at = pw.BigIntegerField(null=False) - updated_at = pw.BigIntegerField(null=False) - - class Meta: - table_name = 'function' - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('function') diff --git a/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py b/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py deleted file mode 100644 index 57a2dfbd5b..0000000000 --- a/backend/open_webui/internal/migrations/016_add_valves_and_is_active.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Peewee migrations -- 009_add_models.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields('tool', valves=pw.TextField(null=True)) - migrator.add_fields('function', valves=pw.TextField(null=True)) - migrator.add_fields('function', is_active=pw.BooleanField(default=False)) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields('tool', 'valves') - migrator.remove_fields('function', 'valves') - migrator.remove_fields('function', 'is_active') diff --git a/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py b/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py deleted file mode 100644 index f998c742d1..0000000000 --- a/backend/open_webui/internal/migrations/017_add_user_oauth_sub.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Peewee migrations -- 017_add_user_oauth_sub.py. -Some examples (model - class or model name):: - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields( - 'user', - oauth_sub=pw.TextField(null=True, unique=True), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields('user', 'oauth_sub') diff --git a/backend/open_webui/internal/migrations/018_add_function_is_global.py b/backend/open_webui/internal/migrations/018_add_function_is_global.py deleted file mode 100644 index 7f7cd4f725..0000000000 --- a/backend/open_webui/internal/migrations/018_add_function_is_global.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Peewee migrations -- 017_add_user_oauth_sub.py. - -Some examples (model - class or model name):: - - > Model = migrator.orm['table_name'] # Return model in current state by name - > Model = migrator.ModelClass # Return model in current state by name - - > migrator.sql(sql) # Run custom SQL - > migrator.run(func, *args, **kwargs) # Run python function with the given args - > migrator.create_model(Model) # Create a model (could be used as decorator) - > migrator.remove_model(model, cascade=True) # Remove a model - > migrator.add_fields(model, **fields) # Add fields to a model - > migrator.change_fields(model, **fields) # Change fields - > migrator.remove_fields(model, *field_names, cascade=True) - > migrator.rename_field(model, old_field_name, new_field_name) - > migrator.rename_table(model, new_table_name) - > migrator.add_index(model, *col_names, unique=False) - > migrator.add_not_null(model, *field_names) - > migrator.add_default(model, field_name, default) - > migrator.add_constraint(model, name, sql) - > migrator.drop_index(model, *col_names) - > migrator.drop_not_null(model, *field_names) - > migrator.drop_constraints(model, *constraints) - -""" - -from contextlib import suppress - -import peewee as pw -from peewee_migrate import Migrator - -with suppress(ImportError): - import playhouse.postgres_ext as pw_pext - - -def migrate(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your migrations here.""" - - migrator.add_fields( - 'function', - is_global=pw.BooleanField(default=False), - ) - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_fields('function', 'is_global') diff --git a/backend/open_webui/migrations/env.py b/backend/open_webui/migrations/env.py index 961a92becf..dc581c6bb3 100644 --- a/backend/open_webui/migrations/env.py +++ b/backend/open_webui/migrations/env.py @@ -1,119 +1,105 @@ +"""Alembic environment configuration. + +Configures the migration context for both offline (SQL script generation) +and online (live database connection) modes. Handles SQLCipher URLs, +SSL parameter normalisation, and JSON log formatting. +""" + import logging from logging.config import fileConfig from alembic import context +from sqlalchemy import create_engine, engine_from_config, pool + +from open_webui.env import DATABASE_PASSWORD, DATABASE_URL, LOG_FORMAT +from open_webui.internal.db import extract_ssl_params_from_url, reattach_ssl_params_to_url from open_webui.models.auths import Auth from open_webui.models.calendar import Calendar, CalendarEvent, CalendarEventAttendee # noqa: F401 -from open_webui.env import DATABASE_URL, DATABASE_PASSWORD, LOG_FORMAT -from open_webui.internal.db import extract_ssl_params_from_url, reattach_ssl_params_to_url -from sqlalchemy import engine_from_config, pool, create_engine -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. +# ── Alembic config & logging ───────────────────────────────────────────────── + config = context.config -# Interpret the config file for Python logging. -# This line sets up loggers basically. if config.config_file_name is not None: fileConfig(config.config_file_name, disable_existing_loggers=False) # Re-apply JSON formatter after fileConfig replaces handlers. -if LOG_FORMAT == 'json': +if LOG_FORMAT == "json": from open_webui.env import JSONFormatter for handler in logging.root.handlers: handler.setFormatter(JSONFormatter()) -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = Auth.metadata +# ── Database URL ───────────────────────────────────────────────────────────── -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. +target_metadata = Auth.metadata DB_URL = DATABASE_URL -# Normalize SSL query params for psycopg2 (Alembic uses psycopg2 for sync migrations). -url_without_ssl, ssl_params = extract_ssl_params_from_url(DB_URL) -DB_URL = reattach_ssl_params_to_url(url_without_ssl, ssl_params) if ssl_params else DB_URL +# Normalise SSL query params for psycopg2 (Alembic uses psycopg2 for sync). +_url_no_ssl, _ssl_params = extract_ssl_params_from_url(DB_URL) +if _ssl_params: + DB_URL = reattach_ssl_params_to_url(_url_no_ssl, _ssl_params) if DB_URL: - config.set_main_option('sqlalchemy.url', DB_URL.replace('%', '%%')) + config.set_main_option("sqlalchemy.url", DB_URL.replace("%", "%%")) + + +# ── Migration runners ──────────────────────────────────────────────────────── def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option('sqlalchemy.url') + """Generate SQL script without a live database connection.""" + url = config.get_main_option("sqlalchemy.url") context.configure( url=url, target_metadata=target_metadata, literal_binds=True, - dialect_opts={'paramstyle': 'named'}, + dialect_opts={"paramstyle": "named"}, ) - with context.begin_transaction(): context.run_migrations() -def run_migrations_online() -> None: - """Run migrations in 'online' mode. +def _build_connectable(): + """Create the appropriate SQLAlchemy engine for the configured DB URL.""" + if DB_URL and DB_URL.startswith("sqlite+sqlcipher://"): + if not DATABASE_PASSWORD or DATABASE_PASSWORD.strip() == "": + raise ValueError( + "DATABASE_PASSWORD is required when using sqlite+sqlcipher:// URLs" + ) - In this scenario we need to create an Engine - and associate a connection with the context. + db_path = DB_URL.replace("sqlite+sqlcipher://", "") + if db_path.startswith("/"): + db_path = db_path[1:] - """ - # Handle SQLCipher URLs - if DB_URL and DB_URL.startswith('sqlite+sqlcipher://'): - if not DATABASE_PASSWORD or DATABASE_PASSWORD.strip() == '': - raise ValueError('DATABASE_PASSWORD is required when using sqlite+sqlcipher:// URLs') - - # Extract database path from SQLCipher URL - db_path = DB_URL.replace('sqlite+sqlcipher://', '') - if db_path.startswith('/'): - db_path = db_path[1:] # Remove leading slash for relative paths - - # Create a custom creator function that uses sqlcipher3 - def create_sqlcipher_connection(): + def _sqlcipher_creator(): import sqlcipher3 conn = sqlcipher3.connect(db_path, check_same_thread=False) conn.execute(f"PRAGMA key = '{DATABASE_PASSWORD}'") return conn - connectable = create_engine( - 'sqlite://', # Dummy URL since we're using creator - creator=create_sqlcipher_connection, - echo=False, - ) - else: - # Standard database connection (existing logic) - connectable = engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix='sqlalchemy.', - poolclass=pool.NullPool, - ) + return create_engine("sqlite://", creator=_sqlcipher_creator, echo=False) + return engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + +def run_migrations_online() -> None: + """Run migrations against a live database connection.""" + connectable = _build_connectable() with connectable.connect() as connection: context.configure(connection=connection, target_metadata=target_metadata) - with context.begin_transaction(): context.run_migrations() +# ── Entrypoint ─────────────────────────────────────────────────────────────── + if context.is_offline_mode(): run_migrations_offline() else: diff --git a/backend/open_webui/migrations/util.py b/backend/open_webui/migrations/util.py index 6ea2a5f4bb..e9f3aa7dd4 100644 --- a/backend/open_webui/migrations/util.py +++ b/backend/open_webui/migrations/util.py @@ -1,15 +1,18 @@ +"""Alembic migration utilities.""" + from alembic import op -from sqlalchemy import Inspector +from sqlalchemy import inspect as sa_inspect -def get_existing_tables(): - con = op.get_bind() - inspector = Inspector.from_engine(con) - tables = set(inspector.get_table_names()) - return tables +def get_existing_tables() -> set[str]: + """Return the set of table names already present in the database.""" + bind = op.get_bind() + inspector = sa_inspect(bind) + return set(inspector.get_table_names()) -def get_revision_id(): +def get_revision_id() -> str: + """Generate a short random revision identifier.""" import uuid - return str(uuid.uuid4()).replace('-', '')[:12] + return uuid.uuid4().hex[:12] diff --git a/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py b/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py index 39f488d72e..d93c007671 100644 --- a/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py +++ b/backend/open_webui/migrations/versions/7e5b5dc7342b_init.py @@ -1,9 +1,8 @@ -"""init +"""Initial Alembic schema — creates all base tables. Revision ID: 7e5b5dc7342b -Revises: +Revises: — Create Date: 2024-06-24 13:15:33.808998 - """ from typing import Sequence, Union @@ -11,194 +10,189 @@ from typing import Sequence, Union import sqlalchemy as sa from alembic import op -import open_webui.internal.db +import open_webui.internal.db # noqa: F401 from open_webui.internal.db import JSONField from open_webui.migrations.util import get_existing_tables -# revision identifiers, used by Alembic. -revision: str = '7e5b5dc7342b' +revision: str = "7e5b5dc7342b" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None +# ── Table definitions ──────────────────────────────────────────────────────── +# Each table is only created if it doesn't already exist, because databases +# migrated from the Peewee era will already have these tables. + +_TABLES: list[tuple[str, list[sa.Column], list]] = [ + ( + "auth", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("email", sa.String(), nullable=True), + sa.Column("password", sa.Text(), nullable=True), + sa.Column("active", sa.Boolean(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "chat", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("chat", sa.Text(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("share_id", sa.Text(), nullable=True), + sa.Column("archived", sa.Boolean(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("share_id")], + ), + ( + "chatidtag", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("tag_name", sa.String(), nullable=True), + sa.Column("chat_id", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "document", + [ + sa.Column("collection_name", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("filename", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("collection_name"), sa.UniqueConstraint("name")], + ), + ( + "file", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("filename", sa.Text(), nullable=True), + sa.Column("meta", JSONField(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "function", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("type", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("meta", JSONField(), nullable=True), + sa.Column("valves", JSONField(), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=True), + sa.Column("is_global", sa.Boolean(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "memory", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "model", + [ + sa.Column("id", sa.Text(), nullable=False), + sa.Column("user_id", sa.Text(), nullable=True), + sa.Column("base_model_id", sa.Text(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("params", JSONField(), nullable=True), + sa.Column("meta", JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "prompt", + [ + sa.Column("command", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("title", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("timestamp", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("command")], + ), + ( + "tag", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("data", sa.Text(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "tool", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("name", sa.Text(), nullable=True), + sa.Column("content", sa.Text(), nullable=True), + sa.Column("specs", JSONField(), nullable=True), + sa.Column("meta", JSONField(), nullable=True), + sa.Column("valves", JSONField(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + ], + [sa.PrimaryKeyConstraint("id")], + ), + ( + "user", + [ + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("email", sa.String(), nullable=True), + sa.Column("role", sa.String(), nullable=True), + sa.Column("profile_image_url", sa.Text(), nullable=True), + sa.Column("last_active_at", sa.BigInteger(), nullable=True), + sa.Column("updated_at", sa.BigInteger(), nullable=True), + sa.Column("created_at", sa.BigInteger(), nullable=True), + sa.Column("api_key", sa.String(), nullable=True), + sa.Column("settings", JSONField(), nullable=True), + sa.Column("info", JSONField(), nullable=True), + sa.Column("oauth_sub", sa.Text(), nullable=True), + ], + [ + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("api_key"), + sa.UniqueConstraint("oauth_sub"), + ], + ), +] + def upgrade() -> None: - existing_tables = set(get_existing_tables()) - - # ### commands auto generated by Alembic - please adjust! ### - if 'auth' not in existing_tables: - op.create_table( - 'auth', - sa.Column('id', sa.String(), nullable=False), - sa.Column('email', sa.String(), nullable=True), - sa.Column('password', sa.Text(), nullable=True), - sa.Column('active', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'chat' not in existing_tables: - op.create_table( - 'chat', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('chat', sa.Text(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('share_id', sa.Text(), nullable=True), - sa.Column('archived', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('share_id'), - ) - - if 'chatidtag' not in existing_tables: - op.create_table( - 'chatidtag', - sa.Column('id', sa.String(), nullable=False), - sa.Column('tag_name', sa.String(), nullable=True), - sa.Column('chat_id', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'document' not in existing_tables: - op.create_table( - 'document', - sa.Column('collection_name', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('filename', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('collection_name'), - sa.UniqueConstraint('name'), - ) - - if 'file' not in existing_tables: - op.create_table( - 'file', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('filename', sa.Text(), nullable=True), - sa.Column('meta', JSONField(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'function' not in existing_tables: - op.create_table( - 'function', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('type', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('meta', JSONField(), nullable=True), - sa.Column('valves', JSONField(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=True), - sa.Column('is_global', sa.Boolean(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'memory' not in existing_tables: - op.create_table( - 'memory', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'model' not in existing_tables: - op.create_table( - 'model', - sa.Column('id', sa.Text(), nullable=False), - sa.Column('user_id', sa.Text(), nullable=True), - sa.Column('base_model_id', sa.Text(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('params', JSONField(), nullable=True), - sa.Column('meta', JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'prompt' not in existing_tables: - op.create_table( - 'prompt', - sa.Column('command', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('title', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('timestamp', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('command'), - ) - - if 'tag' not in existing_tables: - op.create_table( - 'tag', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('data', sa.Text(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'tool' not in existing_tables: - op.create_table( - 'tool', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('name', sa.Text(), nullable=True), - sa.Column('content', sa.Text(), nullable=True), - sa.Column('specs', JSONField(), nullable=True), - sa.Column('meta', JSONField(), nullable=True), - sa.Column('valves', JSONField(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.PrimaryKeyConstraint('id'), - ) - - if 'user' not in existing_tables: - op.create_table( - 'user', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('email', sa.String(), nullable=True), - sa.Column('role', sa.String(), nullable=True), - sa.Column('profile_image_url', sa.Text(), nullable=True), - sa.Column('last_active_at', sa.BigInteger(), nullable=True), - sa.Column('updated_at', sa.BigInteger(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=True), - sa.Column('api_key', sa.String(), nullable=True), - sa.Column('settings', JSONField(), nullable=True), - sa.Column('info', JSONField(), nullable=True), - sa.Column('oauth_sub', sa.Text(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('api_key'), - sa.UniqueConstraint('oauth_sub'), - ) - # ### end Alembic commands ### + existing = set(get_existing_tables()) + for name, columns, constraints in _TABLES: + if name not in existing: + op.create_table(name, *columns, *constraints) def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - op.drop_table('tool') - op.drop_table('tag') - op.drop_table('prompt') - op.drop_table('model') - op.drop_table('memory') - op.drop_table('function') - op.drop_table('file') - op.drop_table('document') - op.drop_table('chatidtag') - op.drop_table('chat') - op.drop_table('auth') - # ### end Alembic commands ### + for name, _, _ in reversed(_TABLES): + op.drop_table(name) diff --git a/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py b/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py index 5fdf933dd6..9f6a2541ea 100644 --- a/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py +++ b/backend/open_webui/migrations/versions/ca81bd47c050_add_config_table.py @@ -1,9 +1,8 @@ -"""Add config table +"""Add config table. Revision ID: ca81bd47c050 Revises: 7e5b5dc7342b Create Date: 2024-08-25 15:26:35.241684 - """ from typing import Sequence, Union @@ -11,22 +10,27 @@ from typing import Sequence, Union import sqlalchemy as sa from alembic import op -# revision identifiers, used by Alembic. -revision: str = 'ca81bd47c050' -down_revision: Union[str, None] = '7e5b5dc7342b' +revision: str = "ca81bd47c050" +down_revision: Union[str, None] = "7e5b5dc7342b" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None -def upgrade(): +def upgrade() -> None: + """Create a key-value config table with versioning.""" op.create_table( - 'config', - sa.Column('id', sa.Integer, primary_key=True), - sa.Column('data', sa.JSON(), nullable=False), - sa.Column('version', sa.Integer, nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.func.now()), + "config", + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("data", sa.JSON(), nullable=False), + sa.Column("version", sa.Integer, nullable=False), sa.Column( - 'updated_at', + "created_at", + sa.DateTime(), + nullable=False, + server_default=sa.func.now(), + ), + sa.Column( + "updated_at", sa.DateTime(), nullable=True, server_default=sa.func.now(), @@ -35,5 +39,6 @@ def upgrade(): ) -def downgrade(): - op.drop_table('config') +def downgrade() -> None: + """Drop the config table.""" + op.drop_table("config")