我最近遇到了这个问题,现在我已经坚持了几天。基本上,这里感兴趣的两个表是“活动”和“目标”,其中活动是目标的单亲;目标只有一个父级,而一个活动有一个或多个目标子级:
class Activity(Base, Table):
__tablename__ = 'activity'
pk_id = Column(Integer, primary_key=True)
name = Column(String)
wave = Column(Integer)
enabled = Column(Boolean)
goals = relationship("Goal", backref='activity', single_parent=True, cascade="all,delete-orphan")
activity_tags = relationship("ActivityTags", backref='tag_activity', cascade="all,delete-orphan")
和
class Goal(Base, Table):
__tablename__ = 'goal'
pk_id = Column(Integer, primary_key=True)
activity_id = Column(Integer, ForeignKey('activity.pk_id'))
category_id = Column(Integer, ForeignKey('category.pk_id'))
name = Column(String)
minutes_expected = Column(Integer)
date_last_invoked = Column(Date)
date_last_invalidated = Column(Date)
enabled = Column(Boolean)
milestones = relationship("Milestone", backref='goal', single_parent=True, cascade="all,delete-orphan")
现在,当我创建一个Activity对象(进而创建1个Goal子对象)时,我遇到了这个错误:
Traceback (most recent call last):
File "unit.py", line 49, in wrapper
r = func(*args, **kwargs)
File "unit.py", line 158, in testActivityWaveOverdue
a = Activity(c, 'Pull-Ups', wave)
File "<string>", line 4, in __init__
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/state.py", line 98, in initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "/home/me/dev/coach/coach/activity.py", line 64, in __init__
self.add_goal(category, 'maintenance')
File "/home/me/dev/coach/coach/activity.py", line 190, in add_goal
return Goal(self, category, name, minutes_expected)
File "<string>", line 4, in __init__
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/state.py", line 98, in initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "/home/me/dev/coach/coach/goal.py", line 76, in __init__
db.session.commit()
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 656, in commit
self.transaction.commit()
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 314, in commit
self._prepare_impl()
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 298, in _prepare_impl
self.session.flush()
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1583, in flush
self._flush(objects)
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1636, in _flush
is_orphan = _state_mapper(state)._is_orphan(state) and state.has_identity
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 1237, in _is_orphan
state, key, optimistic=bool(state.key)):
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/instrumentation.py", line 331, in has_parent
return self.get_impl(key).hasparent(state, optimistic=optimistic)
File "/home/me/dev/coach/.venv/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 349, in hasparent
assert self.trackparent, "This AttributeImpl is not configured to track parents."
AssertionError: This AttributeImpl is not configured to track parents.
这是Activity init代码:
def __init__(self, category, name, wave):
self.name = name
self.wave = wave
self.enabled = True
db.session.add(self)
db.session.commit()
self.add_goal(category, 'maintenance')
def add_goal(self, category, name, minutes_expected=30):
return Goal(self, category, name, minutes_expected)
目标:
def __init__(self, activity, category, name, minutes_expected):
self.activity_id = activity.pk_id
self.category_id = category.pk_id
self.name = name
self.minutes_expected = minutes_expected
self.date_last_invoked = START_OF_TIME
self.date_last_invalidated = START_OF_TIME
self.enabled = True
db.session.add(self)
db.session.commit()
请告诉我,如果您还有其他任何想要我加入上述内容的话 - 我不想让这个帖子比我们需要的更大,所以只包括我认为与问题相关的内容
最后,Activity和Goal继承的“Table”超类只提供辅助/辅助方法。
答案 0 :(得分:2)
这里有一些非传统的用法,并且你的_ init _s中有一些循环,这是不必要的,因为sqlalchemy的ORM已经为你做了什么,并且可能导致问题。
ORM已经能够将您的活动和目标实例逻辑地链接在一起,而不必在__init__
方法中强制执行。
尝试以下方法:
不要将Goal.__init__
作为参数进行活动。顺便说一句,你甚至不必为使用sqlalchemy的声明基类的模型类编写__init__
方法;你可以,但只有你真的需要完成额外的事情才有必要。那么您的新目标实例将如何链接到其一个Activity实例? Activity.goals关系及其backref为您解决了这个问题。
顺便说一下,由于每个目标只与一个活动相关联,因此您应该更改backref的形式。您当前的backref='activity'
将导致与每个目标相关联的活动集合(在本例中为默认情况下,列表)。根据您的模型描述,您需要的是backref=backref('activity', uselist=False)
。这将导致引用somegoal.activity返回单个对象(以sqlalchemy术语,'标量'),而不是列表。
没有Activity.__init__
添加目标;只需在调用Activity()以创建活动实例的代码部分中执行此操作。一般来说,设置像你在这里的inits链是不必要的复杂,其中实例化一件内部导致另一件事的实例化。最好将该活动提升到一个级别并将其放在想要执行这些操作的主程序代码中,并使用自己的代码行清楚地实现每个实例。比将它隐藏在__init__
内更好。这也是更加pythonic(显式优于隐式,使用更少'魔术')。
请注意,在您的Activity.add_goal()
方法中,实际上没有任何内容已完成并带有返回值。不要编写抛弃其返回值的方法,或者仅为副作用调用其他方法(在本例中为构造函数)。上面的第1点和第3点可以概括为“不要编写带有副作用的构造函数”。
我的观点不是教条主义;我希望您可以看到,这些建议中的每一个都是为了创建更清晰的代码,以更少的复杂性完成所需的事情,并且更容易阅读,跟踪和调试。
至于你的错误追溯中发生了什么的实际分析:我们需要一个真正的工作代码示例来真正做到这一点,但我的猜测是sqlalchemy按照你的要求创建你的Activity对象,并尝试按照Activity
班级映射器的relationship
创建从新活动到目标的前向引用,以及从目标到活动的反向引用。但顺便说一下,你试图让__init__
做一些相反的工作让你感到困惑。我认为,如果你纠结并简化一点,你就会得到你想要的东西。
最后注意事项:如果您没有逐节阅读sqlalchemy的ORM教程并直接跟踪它的每一点,强烈建议您这样做。在整个过程中(以及在旁边的一些背景参考中),您会发现示例显示您想在此处建模的情况,以及其他非常相似的情况,您会看到最常见的(最简单/最清晰) /最简单的方法来设置它们。