2015-08-15 10 views
5

Ho giocato con SQLAlchemy e ho scoperto che non riesco a tenere traccia di ciò che viene modificato nel database.Rendi l'oggetto principale non visualizzato all'interno di session.dirty di before_flush listener di eventi

ho creato un esempio che spiega che cosa la mia preoccupazione è:

import re 
import datetime 

from sqlalchemy import create_engine 

from sqlalchemy.ext.declarative import (
    declarative_base, 
    declared_attr, 
    ) 

from sqlalchemy import (
    create_engine, 
    event, 
    Column, 
    Boolean, 
    Integer, 
    String, 
    Unicode, 
    DateTime, 
    Index, 
    ForeignKey, 
    CheckConstraint, 
    ) 

from sqlalchemy.orm import (
    scoped_session, 
    sessionmaker, 
    Session, 
    relationship, 
    backref, 
    ) 

import transaction 

from zope.sqlalchemy import ZopeTransactionExtension 

class ExtendedSession(Session): 
    my_var = None 

DBSession = scoped_session(
    sessionmaker(extension=ZopeTransactionExtension(), 
     class_=ExtendedSession 
     ) 
    ) 

class BaseModel(object): 
    query = DBSession.query_property() 

    id = Column(
     Integer, 
     primary_key=True, 
     ) 

    @declared_attr 
    def __tablename__(cls): 
     class_name = re.sub(r"([A-Z])", r"_\1", cls.__name__).lower()[1:] 
     return "{0}".format(
      class_name, 
      ) 

Base = declarative_base(cls=BaseModel) 

def initialize_sql(engine): 
    DBSession.configure(bind=engine) 
    Base.metadata.bind = engine 

engine = create_engine("sqlite://") 
initialize_sql(engine) 

class Parent(Base): 
    # *** Columns 
    col1 = Column (
     String, 
     nullable=False, 
     ) 
    # *** Relationships 
    # *** Methods 
    def __repr__(self): 
     return "<Parent(id: '{0}', col1: '{1}')>".format(
      self.id,\ 
      self.col1,\ 
      ) 

class Child(Base): 
    # *** Columns 
    col1 = Column (
     String, 
     nullable=False, 
     ) 
    parent_id = Column (
     Integer, 
     ForeignKey (
      Parent.id, 
      ondelete="CASCADE", 
      ), 
     nullable=False, 
     ) 
    # *** Relationships 
    parent = relationship (
     Parent, 
     backref=backref(
      "child_elements", 
      uselist=True, 
      cascade="save-update, delete", 
      lazy="dynamic", 
      ), 
     # If below is uncommented then instance of Parent won't appear in session.dirty 
     # However this relationship will never be loaded (even if needed) 
     #lazy="noload", 
     ) 
    # *** Methods 
    def __repr__(self): 
     return "<Child(id: '{0}', col1: '{1}', parent_id: '{2}')>".format(
      self.id,\ 
      self.col1,\ 
      self.parent_id,\ 
      ) 

@event.listens_for(DBSession, 'before_flush') 
def before_flush(session, flush_context, instances): 
    time_stamp = datetime.datetime.utcnow() 

    if session.new: 
     for elem in session.new: 
      print(" ### NEW {0}".format(repr(elem))) 

    if session.dirty: 
     for elem in session.dirty: 
      print(" ### DIRTY {0}".format(repr(elem))) 

    if session.deleted: 
     for elem in session.deleted: 
      print(" ### DELETED {0}".format(repr(elem))) 

Base.metadata.drop_all(engine) 
Base.metadata.create_all(engine) 

with transaction.manager: 
    parent = Parent(col1="parent") 
    DBSession.add(parent) 
    DBSession.flush() 

    # Below loop is to demonstrate that 
    # each time child object is created and linked to parent 
    # parent is also marked as modified 
    # how to avoid that? 
    # or optionally is it possible to detect this in before_flush event 
    # without issuing additional SQL query? 
    for i in range(0, 10): 
     parent=Parent.query.filter(Parent.col1 == "parent").first() 
     child = Child(col1="{0}".format(i)) 
     child.parent = parent 
     DBSession.add(child) 
     DBSession.flush() 

    # Below update will not cause associated instance of Parent appearing in session.dirty 
    child = Child.query.filter(Child.col1=="3").first() 
    child.col1="updated" 
    DBSession.add(child) 
    DBSession.flush() 

In breve - ci sono due oggetti:

  • controllanti
  • Child - legata alla controllante

Ogni volta che aggiungo una nuova istanza di Child e la collego con l'istanza di Parent quell'istanza o f Anche il genitore appare all'interno di session.dirty di before_flush.

comunità SQLAlchemy avvertita questo comportamento è previsto (anche se credo che ci deve essere la possibilità di cambiare il comportamento di default - Non riuscivo a trovare entro doco)

Quindi ecco la mia domanda: è possibile configurare rapporto così modo che quando aggiungo una nuova istanza di Child e la collego all'istanza di Parent, quell'istanza di Parent non apparirà all'interno di session.dirty?

Ho provato a fissare rapporto come lazy="noload" e non è un'opzione da quando può essere necessario utilizzare quel rapporto (così che io possa bisogno di caricarlo)

Vorrei anche accettare una soluzione che mi permettesse di rilevare quel genitore non è stato cambiato all'interno del gestore di eventi before_load, tuttavia non voglio attivare query aggiuntive per ottenere questo risultato.

Gradirei il vostro aiuto,

Greg

risposta

4

Dopo ore di ricerca e un suggerimento da comunità SQLAlchemy ho trovato la soluzione che sembra funzionare il modo in cui ho bisogno (avviso di condizione aggiuntiva all'interno session.dirty blocco).

@event.listens_for(DBSession, 'before_flush') 
def before_flush(session, flush_context, instances): 
    time_stamp = datetime.datetime.utcnow() 

    if session.new: 
     for elem in session.new: 
      print(" ### NEW {0}".format(repr(elem))) 

    if session.dirty: 
     for elem in session.dirty: 
      # Below check was added to solve the problem 
      if (session.is_modified(elem, include_collections=False)): 
       print(" ### DIRTY {0}".format(repr(elem))) 

    if session.deleted: 
     for elem in session.deleted: 
      print(" ### DELETED {0}".format(repr(elem))) 

La documentazione relativa alla mia soluzione può essere trovata qui: http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.is_modified

In breve - specificando include_collections=False entro session.is_modified rende SQLAlchemy di ignorare le situazioni in cui sono state modificate le collezioni più valori (nel mio caso se il bambino è stato cambiato poi il genitore verrebbe filtrato da quel controllo aggiuntivo).