如何在SQLAlchemy中创建两个相互依赖的对象?

时间:2013-01-08 22:45:38

标签: python session transactions sqlalchemy flask-sqlalchemy

我有两个Python类NoteLink映射到PostgresQL表。 Note具有对Link的外键引用,而Link通过一段JSON文本指向节点。链接指向Note以外的其他内容,但这并不重要。

               Note
+------+------------------+---------+
|  ID  |       NAME       | NOTE_ID |
+------+------------------+---------+
|   1  |  Alice           |     5   |
|   2  |  Bob             |    20   |
|   3  |  Carla           |     6   |
+------+------------------+---------+

          Link
+------+--------------+
|  ID  |  CONTENT     |
+------+--------------+
| ...  |    ...       |
|   5  |  {"t":1}     |
|   6  |  {"t":3}     |
| ...  |    ...       |
|  20  |  {"t":2}     |
+------+--------------+

现在我想要的是每当我创建一个新的Note

note = Note('Danielle')

它会自动进入行

(4, 'Danielle', 21)

进入Note,然后输入

(21, '{"t":4}')

进入Link。以下是我到目前为止所尝试的内容:我创建了Note对象,然后尝试在Link事件中创建@events.after_insert

class Note(Entity):
    name = Field(Unicode)
    link = ManyToOne('Link')

    . . .

    @events.after_insert
    def create_link(self):
        """
        Create and persist the short link for this note.  Must be done
        in this after_insert event because the link table has a foreign
        key that points back to the note.  We need the note to be 
        already inserted so we can use its id.
        """
        self.link = Link.build_link_for_note(self)
        elixir.session.flush()
        print("NOTE %s GOT LINK %s" % (self, self.link))

在Link类中我有

class Link(Entity):
    . . . 
    @classmethod
    def build_link_for_note(cls, note):
        return Link(content='{"id": %d}' % note.id)

两个表都有自动增加的主键,所以不用担心。我用这段代码得到的错误是:

File ".../sqlalchemy/orm/session.py", line 1469, in flush
    raise sa_exc.InvalidRequestError("Session is already flushing")
    InvalidRequestError: Session is already flushing

我会买的。在将注释存储到数据库之后,@after_insert事件被调用(我认为),这在当前会话刷新期间发生。当然,如果我删除elixir.session.flush()电话,那么当然会打印

NOTE <Note id:4 name:Danielle> GOT LINK <id:None content:{"t": 4}>

这也是有道理的,因为我无法坚持链接!

所以我的问题是,如何在单个请求中创建注释和链接,以便相互依赖的ID可用并正确记录?

P.S。我知道这里的模式有点不同寻常,我可以通过(1)产生任务来异步创建链接或(2)使Link.content方法懒惰地创建链接来解决这个问题。这些解决方案需要一些并发关注,所以我真的希望一个简单,直接的SQLAlchemy解决方案可以使用一个会话。

2 个答案:

答案 0 :(得分:8)

我建议不要使用Elixir的方法,例如“save()”,这会错误地使用SQLAlchemy的API。以下是使用标准SQLAlchemy事件的上述方法。一切都是在一次冲洗中实现的。

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
import json

Base = declarative_base()

class Note(Base):
    __tablename__ = "note"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    note_id = Column(Integer, ForeignKey('link.id'))

    link = relationship("Link")

    # if using __init__ here is distasteful to you,
    # feel free to use the "init" event illustrated
    # below instead
    def __init__(self, name):
        self.name = name
        self.link = Link()

class Link(Base):
    __tablename__ = "link"

    id = Column(Integer, primary_key=True)
    content = Column(String)

# using an event instead of Note.__init__
#@event.listens_for(Note, "init")
#def init(target, args, kwargs):
#    target.link = Link()

@event.listens_for(Note, "after_insert")
def after_insert(mapper, connection, target):
    connection.execute(
        Link.__table__.update().\
            values(content=json.dumps({"t": target.id}))
    )

e = create_engine("sqlite://", echo=True)

Base.metadata.create_all(e)

s = Session(e)

note = Note('Danielle')

s.add(note)
s.commit()

note = s.query(Note).first()
assert s.query(Link.content).scalar() == ('{"t": %d}' % note.id)

答案 1 :(得分:0)

由于两个对象都有自动生成的ID,这些ID来自DB 想要存储彼此的ID,因此需要先保存两个对象,然后再次保存其中一个对象,并使用更新的ID其他对象。

所以我要删除flush调用,并为每个相关对象明确调用save