import logging import os import sys from logging.config import fileConfig from typing import Optional from alembic import context from sqlalchemy import engine_from_config, event, pool sys.path.append(os.path.dirname(os.path.dirname(__file__))) # Подсказка create_app: не поднимать bootstrap/фон до upgrade. os.environ.setdefault("WESP_ALEMBIC", "1") def _configure_alembic_logging() -> None: """Логи Alembic без второго console handler (иначе каждая строка ×2). При upgrade из create_app root уже настроен — fileConfig из alembic.ini дублировал вывод. Прямой ``alembic upgrade`` из shell (без handlers) — читаем ini как раньше. """ mig = logging.getLogger("alembic.runtime.migration") if logging.getLogger().handlers: for name in ("alembic", "alembic.runtime", "alembic.runtime.migration"): logging.getLogger(name).setLevel(logging.INFO) mig.propagate = True return ini = context.config.config_file_name if ini: fileConfig(ini) mig.propagate = bool(mig.handlers) from app import _alembic_host_app, create_app, db # noqa: E402 from config import get_config_class # noqa: E402 config = context.config _configure_alembic_logging() # Повторный create_app открывал бы SQLite поверх пулов Flask → database is locked. if _alembic_host_app is not None: app = _alembic_host_app else: app = create_app(get_config_class(), run_migrations=False) target_metadata = db.metadata def _recipes_db_url() -> str: """URI основной БД: из окружения (выставляет create_app при автомиграции) или из конфига.""" return os.getenv("WESP_RECIPES_DB_URI") or app.config["SQLALCHEMY_DATABASE_URI"] def _reports_db_url() -> Optional[str]: """URI отчётов; None — второй проход Alembic не выполняется.""" return os.getenv("WESP_REPORTS_DB_URI") or app.config.get("SQLALCHEMY_BINDS", {}).get( "reports" ) def run_migrations_offline() -> None: url = _recipes_db_url() context.configure( url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True, ) with context.begin_transaction(): context.run_migrations() def _migration_engine(url: str): from app.services.migration_pace import apply_sqlite_migration_pragmas engine = engine_from_config( config.get_section(config.config_ini_section), prefix="sqlalchemy.", poolclass=pool.NullPool, url=url, ) if not str(url).lower().startswith("sqlite:"): return engine @event.listens_for(engine, "connect") def _sqlite_migration_pragmas(dbapi_connection, connection_record): # noqa: ARG001 apply_sqlite_migration_pragmas(dbapi_connection) return engine def run_migrations_online() -> None: from app.services.migration_pace import migration_pause, migration_pause_bind migration_pause("перед recipes.db") recipes_url = _recipes_db_url() connectable = _migration_engine(recipes_url) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata, version_table="alembic_version", compare_type=True, ) with context.begin_transaction(): context.run_migrations() # wesp_2_0 на recipes.db (1-я строка Running upgrade) if str(recipes_url).lower().startswith("sqlite:"): from app.services.migration_pace import sqlite_flush_after_migration raw = connection.connection.dbapi_connection # type: ignore[attr-defined] if raw is not None: sqlite_flush_after_migration(raw) reports_url = _reports_db_url() if not reports_url: migration_pause("после recipes.db") return migration_pause_bind("recipes.db → reports.db") reports_engine = _migration_engine(reports_url) with reports_engine.connect() as connection: context.configure( connection=connection, target_metadata=None, version_table="alembic_version_reports", ) with context.begin_transaction(): context.run_migrations(tag="reports") # та же ревизия на reports.db (2-я строка) if str(reports_url).lower().startswith("sqlite:"): from app.services.migration_pace import sqlite_flush_after_migration raw_rep = connection.connection.dbapi_connection # type: ignore[attr-defined] if raw_rep is not None: sqlite_flush_after_migration(raw_rep) migration_pause("после reports.db") if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online()