Propagar automaticamente a exclusão ao usar um bidirecional association_proxy

9

Estou usando um association_proxy bidirecional para associar as propriedades Group.members e User.groups . Estou tendo problemas com a remoção de um membro de Group.members . Em particular, Group.members.remove removerá com sucesso uma entrada de Group.members , , mas deixará um None no lugar da entrada correspondente em User.groups .

Mais concretamente, o trecho de código representativo (mínimo-ish) a seguir falha em sua última afirmação:

import sqlalchemy as sa

from sqlalchemy.orm import Session
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()


class Group(Base):
    __tablename__ = 'group'
    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
    name = sa.Column(sa.UnicodeText())
    members = association_proxy('group_memberships', 'user',
            creator=lambda user: GroupMembership(user=user))


class User(Base):
    __tablename__ = 'user'
    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
    username = sa.Column(sa.UnicodeText())
    groups = association_proxy('group_memberships', 'group',
            creator=lambda group: GroupMembership(group=group))


class GroupMembership(Base):
    __tablename__ = 'user_group'
    user_id = sa.Column(sa.Integer, sa.ForeignKey('user.id'), primary_key=True)
    group_id = sa.Column(sa.Integer, sa.ForeignKey('group.id'), primary_key=True)

    user = sa.orm.relationship(
            'User',
            backref=sa.orm.backref('group_memberships', cascade="all, delete-orphan"))
    group = sa.orm.relationship(
            'Group',
            backref=sa.orm.backref('group_memberships', cascade="all, delete-orphan"),
            order_by='Group.name')


if __name__ == '__main__':
    engine = sa.create_engine('sqlite://')
    Base.metadata.create_all(engine)
    session = Session(engine)

    group = Group(name='group name')
    user = User(username='user name')
    group.members.append(user)
    session.add(group)
    session.add(user)
    session.flush()
    assert group.members == [user]
    assert user.groups == [group]
    group.members.remove(user)
    session.flush()
    assert group.members == []
    assert user.groups == []  # This assertion fails, user.groups is [None]

Eu tentei seguir as respostas para relacionamento SQLAlchemy com association_proxy problemas e Como o SQLAlchemy association_proxy pode ser usado direcionalmente? mas eles não parecem ajudar.

    
por mickeyh 15.08.2016 в 21:56
fonte

1 resposta

2

Eu descobri seu problema quase inteiramente por acidente, já que eu estava tentando descobrir o que está acontecendo.

Como não havia dados no banco de dados, adicionei session.commit() . Acontece que (da resposta vinculada):

  

As alterações não são permanentemente permanentes no disco ou visíveis a outras transações até que o banco de dados receba um COMMIT para a transação atual (que é o que session.commit () faz).

Como você está apenas .flush() ing as alterações, o sqlalchemy nunca consulta novamente o banco de dados. Você pode verificar isso adicionando:

import logging
logging.getLogger('sqlalchemy').setLevel(logging.INFO)
logging.getLogger('sqlalchemy').addHandler(logging.StreamHandler())

E simplesmente executando seu código. Ele exibirá todas as consultas que são executadas conforme elas acontecem. Em seguida, você pode alterar session.flush() para session.commit() e, em seguida, executar novamente e verá que várias instruções SELECT são executadas após seu commit .

Parece que session.expire(user) ou session.refresh(user) forçará uma atualização do usuário também. Não tenho certeza se existe uma maneira de forçar a atualização a se propagar para o outro objeto sem ser explícito sobre isso (ou se isso é mesmo desejável).

    
por Wayne Werner 15.08.2016 / 22:43
fonte