diff --git a/rag/alembic/env.py b/rag/alembic/env.py index 0df2b4e..c975846 100644 --- a/rag/alembic/env.py +++ b/rag/alembic/env.py @@ -1,46 +1,109 @@ -from logging.config import fileConfig -from sqlalchemy import pool -from sqlalchemy.ext.asyncio import create_async_engine -from alembic import context -import asyncio +# rag/alembic/env.py +# -*- coding: utf-8 -*- +""" +Среда выполнения Alembic для управления схемой PostgreSQL. +Загружает конфигурацию из проекта (AppConfig) для получения строки подключения. +""" +import asyncio +import os +import sys +from logging.config import fileConfig + +from sqlalchemy import engine_from_config, pool +from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine +from alembic import context + +# Добавляем путь к проекту (чтобы импортировать модули) +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from rag.config_models import AppConfig +from rag.utils.config_loader import load_config + +# Это объект конфигурации Alembic, предоставляющий доступ к значениям из alembic.ini config = context.config +# Настройка логирования (если указано в alembic.ini) if config.config_file_name is not None: fileConfig(config.config_file_name) +# Метаданные моделей (если используются SQLAlchemy ORM) – пока None target_metadata = None -def run_migrations_offline(): - url = config.get_main_option("sqlalchemy.url") + +def get_app_config() -> AppConfig: + """ + Загружает конфигурацию проекта из переменной окружения или из стандартного пути. + """ + profile_dir = os.environ.get('FCKBOT_PROFILE_DIR') + if profile_dir: + return load_config(profile_dir) + + # Если не задано, пробуем стандартные пути (для разработки) + possible_paths = [ + '/usr/local/etc/fckbot/bots/metabot', # по умолчанию методолог + '/usr/local/etc/fckbot/profiles/metabot', + './profiles/metabot', + ] + for path in possible_paths: + if os.path.exists(path): + try: + return load_config(path) + except Exception: + continue + raise RuntimeError( + "Не удалось загрузить конфигурацию проекта. " + "Установите переменную окружения FCKBOT_PROFILE_DIR с путём к профилю бота." + ) + + +def run_migrations_offline() -> None: + """ + Запуск миграций в офлайн-режиме (без подключения к БД). + """ + app_config = get_app_config() + url = app_config.db_url # должно быть свойство, которое формирует 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(): + +async def run_migrations_online() -> None: + """ + Запуск миграций в онлайн-режиме (с подключением к БД). + """ + app_config = get_app_config() + url = app_config.db_url + + # Создаём асинхронный движок connectable = create_async_engine( - config.get_main_option("sqlalchemy.url"), + url, poolclass=pool.NullPool, ) - async def run_async_migrations(): - async with connectable.connect() as connection: - await connection.run_sync(do_run_migrations) - await connectable.dispose() + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) - def do_run_migrations(connection): - context.configure(connection=connection, target_metadata=target_metadata) - with context.begin_transaction(): - context.run_migrations() + await connectable.dispose() + + +def do_run_migrations(connection): + """ + Выполняет миграции на синхронном соединении (обёртка для асинхронного). + """ + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() - asyncio.run(run_async_migrations()) if context.is_offline_mode(): run_migrations_offline() else: - run_migrations_online() \ No newline at end of file + asyncio.run(run_migrations_online()) \ No newline at end of file