web-dev-qa-db-fra.com

Pourquoi une requête appelle-t-elle un vidage automatique dans SQLAlchemy?

Le code que vous voyez ci-dessus n'est qu'un exemple mais il fonctionne pour reproduire cette erreur:

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)]

Une instance mappée est toujours ajoutée à une session. L'instance veut savoir (ce qui signifie requête sur la base de données) si d'autres instances son propre type existe avec les mêmes valeurs. Il y a un deuxième attribut/colonne (_nn). Il est spécifié à NOT NULL. Mais par défaut, c'est NULL.

Lorsque l'instance (comme dans l'exemple) est toujours ajoutée à la session, un appel à query.one() appelle un vidage automatique. Cette vidange crée un INSERT qui essaie de stocker l'instance. Cela échoue car _nn Est toujours nul et viole la contrainte NOT NULL.

C'est ce que je comprends actuellement. Mais la question est pourquoi appelle-t-elle un rinçage automatique? Puis-je bloquer cela?

#!/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)

Bien sûr, une solution serait de pas ajouter l'instance à la session avant l'appel de query.one(). Ce travail. Mais dans mon cas d'utilisation réel (mais trop complexe pour cette question), ce n'est pas une bonne solution.

19
buhtz

Comment désactiver la fonction autoflush :

  • Temporaire: vous pouvez utiliser no_autoflush gestionnaire de contexte sur l'extrait de code où vous interrogez la base de données, c'est-à-dire dans X.test méthode:

    def test(self, session):
        with session.no_autoflush:
            q = session.query(X).filter(X._val == self._val)
            x = q.one()
            print('x={}'.format(x))
    
  • À l'échelle de la session: passez simplement autoflush=False à votre sessionmaker:

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