Почему запрос вызывает автозагрузку в SQLAlchemy?

Код, который вы видите выше, является просто образцом, но он работает, чтобы воспроизвести эту ошибку:

sqlalchemy.exc.IntegrityError: (raised as a result of Query-invoked autoflush; 
consider using a session.no_autoflush block if this flush is occurring prematurely)
(sqlite3.IntegrityError) NOT NULL constraint failed: X.nn 
[SQL: 'INSERT INTO "X" (nn, val) VALUES (?, ?)'] [parameters: (None, 1)]

Отображаемый экземпляр по-прежнему добавляется к сеансу. Экземпляр хочет знать (что означает запрос в базе данных), если другие экземпляры имеют собственный тип, имеющий одинаковые значения. Существует второй атрибут/столбец (_nn). Он указан в NOT NULL. Но по умолчанию это NULL.

Когда экземпляр (как в образце) по-прежнему добавляется в сеанс, вызов query.one() вызывает автозапуск. Этот флеш создаст INSERT, который пытается сохранить экземпляр. Это терпит неудачу, потому что _nn по-прежнему является нулевым и нарушает ограничение NOT NULL.

Вот что я понимаю в настоящее время. Но вопрос в том, почему он вызывает авто-флеш? Могу ли я заблокировать это?

#!/usr/bin/env python3

import os.path
import os
import sqlalchemy as sa 
import sqlalchemy.orm as sao
import sqlalchemy.ext.declarative as sad
from sqlalchemy_utils import create_database

_Base = sad.declarative_base()
session = None


class X(_Base):
    __tablename__ = 'X'

    _oid = sa.Column('oid', sa.Integer, primary_key=True)
    _nn = sa.Column('nn', sa.Integer, nullable=False) # NOT NULL!
    _val = sa.Column('val', sa.Integer)

    def __init__(self, val):
        self._val = val

    def test(self, session):
        q = session.query(X).filter(X._val == self._val)
        x = q.one()
        print('x={}'.format(x))

dbfile = 'x.db'

def _create_database():
    if os.path.exists(dbfile):
        os.remove(dbfile)

    engine = sa.create_engine('sqlite:///{}'.format(dbfile), echo=True)
    create_database(engine.url)
    _Base.metadata.create_all(engine)
    return sao.sessionmaker(bind=engine)()


if __name__ == '__main__':
    session = _create_database()

    for val in range(3):
        x = X(val)
        x._nn = 0
        session.add(x)
    session.commit()

    x = X(1)
    session.add(x)
    x.test(session)

Конечно, решение заключалось бы в том, чтобы не добавлять экземпляр в сеанс до вызова query.one(). Эта работа. Но в моем реальном (но сложном для этого вопроса) прецеденте это нехорошее решение.

Ответ 1

Как отключить функцию autoflush:

  • Временно: вы можете использовать контекстный менеджер no_autoflush в фрагменте, где вы запрашиваете базу данных, то есть в X.test:

    def test(self, session):
        with session.no_autoflush:
            q = session.query(X).filter(X._val == self._val)
            x = q.one()
            print('x={}'.format(x))
    
  • Общесистемный: просто передайте autoflush=False вашему сессионному игроку:

    return sao.sessionmaker(bind=engine, autoflush=False)()