From 807e3dadd32583c10ac3083edf5b2606f479deb1 Mon Sep 17 00:00:00 2001 From: machka-pasla Date: Thu, 4 Sep 2025 19:52:57 +0300 Subject: [PATCH] Update database setup to include simple migrations and enhance .gitignore - Added the `run_simple_migrations` function call in the database initialization process to ensure any missing columns are added during setup. - Updated the .gitignore file to exclude the `models_old.py` file, improving project cleanliness. --- .gitignore | 1 + db/database_setup.py | 3 ++ db/migrator.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 db/migrator.py diff --git a/.gitignore b/.gitignore index fd0a62f..0a8d3fa 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ __pycache__/ *.pid locales/ru_backup.json locales/en_backup.json +db/models_old.py diff --git a/db/database_setup.py b/db/database_setup.py index 0b48c3f..4c1fbf8 100644 --- a/db/database_setup.py +++ b/db/database_setup.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import sessionmaker from config.settings import Settings from .models import Base +from .migrator import run_simple_migrations async_engine = None @@ -62,6 +63,8 @@ async def init_db(settings: Settings, session_factory: sessionmaker): async with async_engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) + # Run lightweight, idempotent migrations to add any missing columns + await conn.run_sync(run_simple_migrations) logging.info( "PostgreSQL database initialized/checked successfully using SQLAlchemy." ) diff --git a/db/migrator.py b/db/migrator.py new file mode 100644 index 0000000..3f37cc0 --- /dev/null +++ b/db/migrator.py @@ -0,0 +1,66 @@ +import logging +from typing import Set + +from sqlalchemy import inspect, text +from sqlalchemy.engine import Connection + +from .models import Base + + +def _add_missing_columns(connection: Connection) -> None: + inspector = inspect(connection) + metadata = Base.metadata + + existing_tables: Set[str] = set(inspector.get_table_names()) + + for table in metadata.tables.values(): + table_name = table.name + if table_name not in existing_tables: + # Tables are created elsewhere via create_all; skip here. + continue + + existing_columns = {col_info["name"] for col_info in inspector.get_columns(table_name)} + + for desired_column in table.columns: + if desired_column.name in existing_columns: + continue + + # Build ADD COLUMN DDL + preparer = connection.dialect.identifier_preparer + table_quoted = preparer.format_table(table) + column_name_quoted = preparer.quote(desired_column.name) + column_type_sql = desired_column.type.compile(dialect=connection.dialect) + + default_clause = "" + server_default = getattr(desired_column, "server_default", None) + if server_default is not None and getattr(server_default, "arg", None) is not None: + try: + compiled_default = str( + server_default.arg.compile(dialect=connection.dialect) + ) + default_clause = f" DEFAULT {compiled_default}" + except Exception: # best-effort + pass + + # For safety, add new columns as NULLable to avoid failures on existing rows + # If strict NOT NULL is needed, it can be enforced manually later. + ddl = f"ALTER TABLE {table_quoted} ADD COLUMN {column_name_quoted} {column_type_sql}{default_clause}" + + logging.info( + f"Migrator: adding missing column {desired_column.name} to table {table_name}" + ) + connection.execute(text(ddl)) + + +def run_simple_migrations(connection: Connection) -> None: + """ + Run lightweight, idempotent migrations: + - Ensure missing columns are added to existing tables to match models in db/models.py + Note: Table creation is handled separately via Base.metadata.create_all. + """ + try: + _add_missing_columns(connection) + logging.info("Migrator: schema synchronized (columns added as needed).") + except Exception as e: + logging.error(f"Migrator: failed to run simple migrations: {e}", exc_info=True) + raise