SQLAlchemy如何通过事件监听器有条件地添加到集合?

2024-06-02 05:14:42 发布

您现在位置:Python中文网/ 问答频道 /正文

在sqlalchemy中,我可以监听集合上的append事件来拦截并可能更改要附加的值。例如,当我想用任意的标准实现一个类似set的行为时,我该如何静默地删除该值(而不是追加)?在

MCVE(除了sqlalchemy之外没有任何依赖关系,只需复制并粘贴):

from sqlalchemy import create_engine, Integer, Text

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.schema import Column, ForeignKey

from sqlalchemy.event import listens_for

engine = create_engine('sqlite:///:memory:')
Base = declarative_base()

_next_id = 1


def get_id():
    global _next_id
    _ = _next_id
    _next_id += 1
    return _


class A1(Base):
    __tablename__ = 'a1'
    id = Column(Integer, primary_key=True, default=get_id)
    a2 = relationship('A2', back_populates='a')


class A2(Base):
    __tablename__ = 'a2'
    id = Column(Integer, primary_key=True, default=get_id)
    a_id = Column(Integer, ForeignKey('a1.id'))
    a = relationship('A1', back_populates='a2')

    name = Column(Text)


@listens_for(A1.a2, 'append', retval=True)
def coll_listener(target, val, initiator):
    for _ in target.a2:
        if _.name == val.name:
            # this is justs some arbitrary condition to illustrate things
            return
    return val


Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
_session = Session()

a2_1 = A2(name='a2_1')
a2_2 = A2(name='a2_2')
a2_3 = A2(name='a2_1')

a1 = A1()

for thing in (a2_1, a2_2, a2_3, a1):
    _session.add(thing)

_session.flush()

for a in (a2_1, a2_2, a2_3):
    a1.a2.append(a)

_session.flush()

# raises FlushError: Can't flush None value found in collection A1.a2

Tags: namefromimportida2forbasesqlalchemy
1条回答
网友
1楼 · 发布于 2024-06-02 05:14:42

对我自己说,既然没人感兴趣。。。在

我现在相信在使用事件侦听器添加时不可能删除元素。所以我最终实现了一个custom collection(这并不是完全鼓励的),结果证明这相当简单:

from sqlalchemy import create_engine, Integer, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.schema import Column, ForeignKey

engine = create_engine('sqlite:///:memory:')
Base = declarative_base()

_next_id = 1


def get_id():
    global _next_id
    _ = _next_id
    _next_id += 1
    return _


class NamedThingCollection(object):

    def __init__(self):
        self.data = []

    def __contains__(self, thing):
        return next((True for x in self.data if x.name == thing.name), False)

    def append(self, item):
        if item not in self:
            self.data.append(item)

    def remove(self, item):
        self.data.remove(item)

    def extend(self, items):
        self.data.extend(items)

    def __iter__(self):
        return iter(self.data)

    def __len__(self):
        return len(self.data)


class A1(Base):
    __tablename__ = 'a1'
    id = Column(Integer, primary_key=True, default=get_id)
    a2 = relationship(
        'A2', back_populates='a', collection_class=NamedThingCollection)


class A2(Base):
    __tablename__ = 'a2'
    id = Column(Integer, primary_key=True, default=get_id)
    a_id = Column(Integer, ForeignKey('a1.id'))
    a = relationship('A1', back_populates='a2')

    name = Column(Text)


Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
_session = Session()

a2_1 = A2(name='a2_1')
a2_2 = A2(name='a2_2')
a2_3 = A2(name='a2_1')

a1 = A1()

for thing in (a2_1, a2_2, a2_3, a1):
    _session.add(thing)

for a in (a2_1, a2_2, a2_3, a2_1):
    a1.a2.append(a)

_session.flush()

assert len(a1.a2) == 2

当然,我们必须根据需要实现进一步的行为,例如__getitem__()/__setitem__()/__delitem__()用于索引等

相关问题 更多 >