¿Cuándo necesito usar sqlalchemy back_poplates?

81

Cuando pruebo el ejemplo de relación de SQLAlchemy siguiendo esta guía: Patrones de relación básicos

Tengo este codigo

#!/usr/bin/env python
# encoding: utf-8
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base(bind=engine)

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent")

Base.metadata.create_all()

p = Parent()
session.add(p)
session.commit()
c = Child(parent_id=p.id)
session.add(c)
session.commit()
print "children: {}".format(p.children[0].id)
print "parent: {}".format(c.parent.id)

Funciona bien, pero en la guía, dice que el modelo debería ser:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    **children = relationship("Child", back_populates="parent")**

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    **parent = relationship("Parent", back_populates="children")**

¿Por qué no necesito back_populateso backrefen mi ejemplo? ¿Cuándo debo usar uno u otro?

Liqang Lau
fuente

Respuestas:

157

Si usa backref, no necesita declarar la relación en la segunda tabla.

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

Si está sin usar backref, y definiendo el relationshipque está separado, a continuación, si no se utiliza back_populates, sqlalchemy no sabrá para conectar las relaciones, por lo que la modificación de uno también modifica la otra.

Entonces, en su ejemplo, donde ha definido por relationshipseparado, pero no proporcionó un back_populatesargumento, modificar un campo no actualizaría automáticamente el otro en su transacción.

>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print(parent.children)
[]

¿Ves cómo no llenó el childrencampo automáticamente ?

Ahora, si proporciona un back_populatesargumento, sqlalchemy conectará los campos.

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent", back_populates="children")

Entonces ahora tenemos

>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print(parent.children)
[Child(...)]

Sqlalchemy sabe que estos dos campos están relacionados ahora y se actualizarán cada uno a medida que se actualice el otro. Vale la pena señalar que el uso backreftambién hará esto. Usar back_populateses bueno si desea definir las relaciones en cada clase, por lo que es fácil ver todos los campos simplemente mirando la clase modelo, en lugar de tener que mirar otras clases que definen campos a través de backref.

Brendan Abel
fuente
43
Una nota sobre back_populatesvs backref: backrefes más sucinta porque no es necesario declarar la relación en ambas clases, pero en la práctica considero que no vale la pena guardar esto en línea. Creo que back_populateses mejor, no solo porque en la cultura de Python "Explícito es mejor que implícito" (Zen de Python), pero cuando tienes muchos modelos, con un vistazo rápido a su declaración puedes ver todas las relaciones y sus nombres en lugar de repasar todos los modelos relacionados. Además, un buen beneficio adicional back_populateses que obtienes autocompletado en ambas direcciones en la mayoría de IDE =)
Fabiano
backref puede parecer más fácil de implementar (especialmente si indica claramente la relación en ambas clases usando comentarios, al menos eso pensé ...), hasta que necesite implementar múltiples relaciones en una sola tabla y trabajar con contenedores. Al final, back_poplates hace que su código sea más fácil de entender
Rhdr
¿Es lo parent_idrealmente necesario bajo Child? Y qué hay de las tablas de ayuda, como se indica en la documentación
Luiz Tauffer
1
@LuizTauffer El parent_ides el campo de clave externa real que almacena la relación padre-hijo. Es necesario. Las tablas auxiliares sirven para definir relaciones de varios a varios (por ejemplo, si un hijo puede tener más de un padre). El ejemplo de fkey anterior es el ejemplo clásico de uno a manay donde cada hijo tiene un solo padre y un padre puede tener muchos hijos.
Brendan Abel