Ответ 1
Я принял удар, чтобы ответить на это. Мое решение должно работать с SQLAlchemy >= 0.8.
По сути, здесь ничего удивительного не происходит, однако при использовании таких шаблонов следует применять надлежащую осторожность, так как состояние идентификационной карты Session
не будет постоянно отражать состояние БД.
Я использовал переключатель post_update
в relationship
, чтобы разорвать циклическую зависимость, которая возникает из этой настройки. Для получения дополнительной информации см. Документацию SQLAlchemy об этом.
Предупреждение. Тот факт, что Session
не всегда отражает состояние БД, может быть причиной неприятных ошибок и других путаниц. В этом примере я использую expire_all
, чтобы показать реальное состояние БД, но это не очень хорошее решение, потому что оно перезагружает все объекты, а все un- flush
ed изменения теряются. Используйте expire
и expire_all
с большой осторожностью!
Сначала определим модель
#!/usr/bin/env python
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base
engine = sa.create_engine('sqlite:///blah.db')
Base = declarative_base()
Base.bind = engine
class Obj(Base):
__table__ = sa.Table(
'objs', Base.metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('parent_id', sa.Integer, sa.ForeignKey('objs.id')),
sa.Column('deleted', sa.Boolean),
)
# I used the remote() annotation function to make the whole thing more
# explicit and readable.
children = orm.relationship(
'Obj',
primaryjoin=sa.and_(
orm.remote(__table__.c.parent_id) == __table__.c.id,
orm.remote(__table__.c.deleted) == False,
),
backref=orm.backref('parent',
remote_side=[__table__.c.id]),
# This breaks the cyclical dependency which arises from my setup.
# For more information see: http://stackoverflow.com/a/18284518/15274
post_update=True,
)
def __repr__(self):
return "<Obj id=%d children=%d>" % (self.id, len(self.children))
Затем мы попробуем
def main():
session = orm.sessionmaker(bind=engine)
db = session()
Base.metadata.create_all(engine)
p1 = Obj()
db.add(p1)
db.flush()
p2 = Obj()
p2.deleted = True
p1.children.append(p2)
db.flush()
# prints <Obj id=1 children=1>
# This means the object is in the `children` collection, even though
# it is deleted. If you want to prevent this you may want to use
# custom collection classes (not for novices!).
print p1
# We let SQLalchemy forget everything and fetch the state from the DB.
db.expire_all()
p3 = db.query(Obj).first()
# prints <Obj id=1 children=0>
# This indicates that the children which is still linked is not
# loaded into the relationship, which is what we wanted.
print p3
db.rollback()
if __name__ == '__main__':
main()