2015-10-03 15 views
8

Il codice che vedete sopra è solo un esempio, ma si lavora per riprodurre questo errore:Perché una query richiama uno svuotamento automatico in 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)] 

Un mappato istanza è ancora aggiunto a una sessione. L'istanza vuole sapere (che significa interrogare sul database) se esistono altre istanze del proprio tipo con gli stessi valori. C'è un secondo attributo/colonna (_nn). È specificato a NOT NULL. Ma per impostazione predefinita è NULL.

Quando l'istanza (come nell'esempio) viene ancora aggiunta alla sessione, una chiamata a query.one() richiama una procedura di svuotamento automatico. Questo colore crea uno INSERT che tenta di archiviare l'istanza. Ciò non riesce perché _nn è ancora null e viola il vincolo NOT NULL.

Questo è quello che capisco attualmente. Ma la domanda è: perché richiama un auto-flush? Posso bloccarlo?

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

Naturalmente una soluzione potrebbe essere quella di non aggiungere l'istanza per la sessione prima query.one() è stato chiamato. Questo lavoro. Ma nel mio caso d'uso reale (ma complesso per questa domanda) non è una buona soluzione.

risposta

15

Come disattivare autoflush caratteristica:

  • temporanea: è possibile utilizzare no_autoflush contesto manager in frammento di cui si richiede il database, cioè in X.test metodo: a livello di sessione-

    def test(self, session): 
        with session.no_autoflush: 
         q = session.query(X).filter(X._val == self._val) 
         x = q.one() 
         print('x={}'.format(x)) 
    
  • : passa il numero autoflush=False al gestore delle sessioni:

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

Qualcuno può spiegare perché è necessario? –

+0

@JonathanLeaders Nella risposta c'è un collegamento ai documenti in cui viene spiegata la funzione di autoflush. Se qualcosa non è chiaro, faresti meglio a porre una domanda specifica. – Palasaty

+0

Si è scoperto che non stavo chiamando 'db.commit()' quando dovevo, quindi mi ha fatto pensare che 'autoflush' non funzionasse come previsto, quindi la mia domanda era basata su un errore. –

Problemi correlati