← Назад к вопросам

Какой метод есть в миграции для запуска кода Python?

1.0 Junior🔥 241 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Метод для запуска кода Python в миграциях

В контексте миграций баз данных (Alembic, sqlalchemy-migrate и др.) часто возникает необходимость выполнить Python код наряду с SQL миграциями. Существует несколько подходов в зависимости от инструмента.

1. Alembic — op.execute() и op.run_sync()

op.execute() — выполняет SQL код:

from alembic import op
import sqlalchemy as sa

def upgrade():
    # SQL миграция
    op.create_table('users',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('name', sa.String(50)),
    )
    
    # Выполнить сырой SQL
    op.execute("INSERT INTO users (name) VALUES ('John')")

def downgrade():
    op.drop_table('users')

2. Выполнение Python кода — get_bind()

Для сложной логики используй op.get_bind() для получения соединения с БД:

from alembic import op
import sqlalchemy as sa

def upgrade():
    # Получить соединение
    bind = op.get_bind()
    connection = bind.connection if hasattr(bind, 'connection') else bind
    
    # Выполнить запрос
    result = connection.execute(sa.text("SELECT COUNT(*) FROM users"))
    user_count = result.scalar()
    
    # Python логика
    if user_count > 0:
        print(f"Found {user_count} users")
        connection.execute(sa.text("UPDATE users SET active = true"))

def downgrade():
    pass

3. Выполнение Python функций во время миграции

Для более сложной логики определи функцию и вызови её:

from alembic import op
import sqlalchemy as sa

def process_users(connection):
    """Обработать пользователей и обновить их статусы"""
    result = connection.execute(sa.text("SELECT id, email FROM users"))
    for user_id, email in result:
        # Python логика обработки
        is_valid = validate_email(email)
        status = 'active' if is_valid else 'inactive'
        
        update_sql = "UPDATE users SET status = :status WHERE id = :id"
        connection.execute(
            sa.text(update_sql),
            {"status": status, "id": user_id}
        )
    connection.commit()

def upgrade():
    bind = op.get_bind()
    process_users(bind)

def downgrade():
    pass

def validate_email(email):
    """Простая валидация email"""
    return '@' in email and '.' in email.split('@')[1]

4. Работа с ORM моделями в миграции

Это более сложный, но мощный подход:

from alembic import op
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker

# Определить простую модель для миграции
Base = sa.orm.declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = sa.Column(sa.Integer, primary_key=True)
    email = sa.Column(sa.String(100), unique=True)
    verified = sa.Column(sa.Boolean, default=False)

def upgrade():
    # Получить сессию
    bind = op.get_bind()
    session = sessionmaker(bind=bind)()
    
    try:
        # Получить всех пользователей
        users = session.query(User).all()
        
        for user in users:
            # Бизнес логика
            if is_email_valid(user.email):
                user.verified = True
        
        session.commit()
    finally:
        session.close()

def downgrade():
    bind = op.get_bind()
    session = sessionmaker(bind=bind)()
    session.query(User).update({User.verified: False})
    session.commit()
    session.close()

def is_email_valid(email):
    return '@' in email

5. Условное выполнение кода по среде

from alembic import op, context
import sqlalchemy as sa

def upgrade():
    # Получить переменные конфига
    config = context.config
    sqlalchemy_url = config.get_main_option("sqlalchemy.url")
    
    # Выполнить код только в production
    if 'postgresql://prod' in sqlalchemy_url:
        bind = op.get_bind()
        bind.execute(sa.text("SELECT * FROM users LIMIT 100"))
    
    op.create_table('new_table',
        sa.Column('id', sa.Integer, primary_key=True),
    )

def downgrade():
    op.drop_table('new_table')

6. Лучшие практики для Python кода в миграциях

Избегай импортов основного приложения:

# ❌ Плохо — может сломаться при развитии приложения
from myapp.models import User

# ✅ Хорошо — дефинируй модели локально
Base = sa.orm.declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = sa.Column(sa.Integer, primary_key=True)

Обработай исключения:

def upgrade():
    bind = op.get_bind()
    try:
        result = bind.execute(sa.text("SELECT COUNT(*) FROM users"))
        count = result.scalar()
        print(f"Processing {count} users")
    except Exception as e:
        print(f"Error: {e}")
        # Решить, откатывать ли миграцию
        raise

Ключевые моменты

  • op.execute() — для простого SQL
  • op.get_bind() — для получения соединения с БД
  • sessionmaker — для работы с ORM и транзакциями
  • Дефинируй модели локально — не импортируй из приложения
  • Обработай ошибки — миграции должны быть атомарными
  • Логируй изменения — помоги себе разобраться в будущем
  • Документируй сложную логику — миграции могут быть сложными

Выполнение Python кода в миграциях — мощный инструмент, но используй его осторожно. Лучше полагаться на SQL, когда это возможно, но иногда Python логика необходима для сложных трансформаций данных.