2012-03-12 20 views
7

This source detalles sobre cómo usar los proxies de asociación para crear vistas y objetos con valores de un objeto ORM.Proxy de asociación SQLAlchemy

Sin embargo, cuando anexo un valor que coincide con un objeto existente en la base de datos (y dicho valor es único o una clave principal), crea un objeto en conflicto por lo que no puedo confirmarlo.

Así que, en mi caso, esto solo es útil como una vista, y tendré que usar consultas ORM para recuperar el objeto que se va a anexar.

¿Esta es mi única opción? ¿Puedo usar merge? (Solo puedo hacer esto si es una clave principal y no una restricción única), O configuro el constructor de manera que use un objeto existente en el base de datos si existe en lugar de crear un nuevo objeto?

por ejemplo, de los documentos:

user.keywords.append('cheese inspector') 

# Is translated by the association proxy into the operation: 

user.kw.append(Keyword('cheese inspector')) 

Pero me gustaría ser traducido a algo más parecido a: (por supuesto, la consulta podría fallar).

keyword = session.query(Keyword).filter(Keyword.keyword == 'cheese inspector').one() 
user.kw.append(keyword) 

o idealmente

user.kw.append(Keyword('cheese inspector')) 
session.merge() # retrieves identical object from the database, or keeps new one 
session.commit() # success! 

supongo que esto puede incluso no ser una buena idea, pero podría ser en ciertos casos de uso :)

Respuesta

9

el ejemplo mostrado en la página de documentación se vincula a es un tipo de relación composition (en términos de OOP) y como tal representa el tipo de relación owns en lugar de uses en términos de verbos. Por lo tanto, cada owner tendría su propia copia de la misma palabra clave (en términos de valor).

De hecho, puede utilizar exactamente la sugerencia de la documentación que enlaza en su pregunta para crear un método creator personalizado y hackearlo para reutilizar el objeto existente para la clave dada en lugar de simplemente crear uno nuevo. En este caso, el código de ejemplo de la clase User y creator función se verá a continuación:

def _keyword_find_or_create(kw): 
    keyword = Keyword.query.filter_by(keyword=kw).first() 
    if not(keyword): 
     keyword = Keyword(keyword=kw) 
     # if aufoflush=False used in the session, then uncomment below 
     #session.add(keyword) 
     #session.flush() 
    return keyword 

class User(Base): 
    __tablename__ = 'user' 
    id = Column(Integer, primary_key=True) 
    name = Column(String(64)) 
    kw = relationship("Keyword", secondary=lambda: userkeywords_table) 
    keywords = association_proxy('kw', 'keyword', 
      creator=_keyword_find_or_create, # @note: this is the 
      ) 
+0

Creo que la respuesta está mostrando exactamente lo mismo que los documentos, pero los documentos a mostrar cómo hacerlo sin anulando varios métodos en la clase (supongo que eso es lo que los métodos están haciendo en la clase, no he verificado). Mi pregunta era más acerca de cómo cambiar el comportamiento de ese ejemplo para que sea más acorde con lo que quiero para mi caso de uso específico (no crear un nuevo objeto cada vez que anexo, sino usar uno existente de la base de datos si puede). –

+0

@DerekLitz: lo suficientemente justo ... cambiará la respuesta para basarlo en la documentación vinculada a en su pregunta ... – van

+0

Sí, ese método parece que funcionará, y es superior a simplemente definir un '__init__' que haría haga lo mismo, porque eso haría que todos los objetos tengan que ser instanciados de esa manera, o agregue alguna complejidad al método. Sin embargo, noté algunos errores, y es uno de los problemas del que no estaba muy seguro. ¿Cuál es la mejor manera de obtener la sesión para hacer la consulta antes de insertar?Su código tiene un error en la consulta, ya que debe hacerse con una sesión en el objeto, algo así como 'session.query (Keyword) .filter (Keyword.keyword = kw)'. –

2

Hace poco me encontré con el mismo problema. Mike Bayer, creador de SQLAlchemy, me remitió al “unique object” recipe pero también me mostró una variante que usa un detector de eventos. El último enfoque modifica el proxy de asociación para que UserKeyword.keyword apunta temporalmente a una cadena simple y solo crea un nuevo objeto Keyword si la palabra clave no existe.

from sqlalchemy import event 

# Same User and Keyword classes from documentation 

class UserKeyword(Base): 
    __tablename__ = 'user_keywords' 

    # Columns 
    user_id = Column(Integer, ForeignKey(User.id), primary_key=True) 
    keyword_id = Column(Integer, ForeignKey(Keyword.id), primary_key=True) 
    special_key = Column(String(50)) 

    # Bidirectional attribute/collection of 'user'/'user_keywords' 
    user = relationship(
     User, 
     backref=backref(
      'user_keywords', 
      cascade='all, delete-orphan' 
      ) 
     ) 

    # Reference to the 'Keyword' object 
    keyword = relationship(Keyword) 

    def __init__(self, keyword=None, user=None, special_key=None): 
     self._keyword_keyword = keyword_keyword # temporary, will turn into a 
               # Keyword when we attach to a 
               # Session 
     self.special_key = special_key 

    @property 
    def keyword_keyword(self): 
     if self.keyword is not None: 
      return self.keyword.keyword 
     else: 
      return self._keyword_keyword 

    @event.listens_for(Session, "after_attach") 
    def after_attach(session, instance): 
     # when UserKeyword objects are attached to a Session, figure out what 
     # Keyword in the database it should point to, or create a new one 
     if isinstance(instance, UserKeyword): 
      with session.no_autoflush: 
       keyword = session.query(Keyword).\ 
        filter_by(keyword=instance._keyword_keyword).\ 
        first() 
       if keyword is None: 
        keyword = Keyword(keyword=instance._keyword_keyword) 
       instance.keyword = keyword