Как я могу избавиться от ошибки циклической зависимости при создании базы данных в sqlalchemy?
Я новичок в использовании sqlalchemy. Как избавиться от ошибки циклической зависимости для таблиц, показанных ниже. В основном моя цель состоит в том, чтобы создать таблицу вопросов с одним-единственным отношением "лучший ответ", чтобы ответить, а также отношение "один к многим" "возможно-респонденты".
class Answer(Base):
__tablename__ = 'answers'
id = Column(Integer, primary_key=True)
text = Column(String)
question_id = Column(Integer, ForeignKey('questions.id'))
def __init__(self, text, question_id):
self.text = text
def __repr__(self):
return "<Answer '%s'>" % self.text
class Question(Base):
__tablename__ = 'questions'
id = Column(Integer, primary_key=True)
text = Column(String)
picture = Column(String)
depth = Column(Integer)
amount_of_tasks = Column(Integer)
voting_threshold = Column(Integer)
best_answer_id = Column(Integer, ForeignKey('answers.id'), nullable=True)
possible_answers = relationship("Answer", post_update=True, primaryjoin = id==Answer.question_id)
def __init__(self, text, picture, depth, amount_of_tasks):
self.text = text
self.picture = picture
self.depth = depth
self.amount_of_tasks = amount_of_tasks
def __repr__(self):
return "<Question, '%s', '%s', '%s', '%s'>" % (self.text, self.picture, self.depth, self.amount_of_tasks)
def __repr__(self):
return "<Answer '%s'>" % self.text
Это сообщение об ошибке:
CircularDependencyError: обнаружена круговая зависимость. Циклы:
Ответы
Ответ 1
По-видимому, SQLAlchemy не хорошо работает с круговыми зависимостями. Вместо этого вы можете использовать таблицу сопоставлений для представления наилучшего ответа...
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy import Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
engine = create_engine('sqlite:///:memory:')
Base = declarative_base()
class Answer(Base):
__tablename__ = 'answer'
id = Column(Integer, primary_key=True)
question_id = Column(Integer, ForeignKey('question.id'))
text = Column(String)
question = relationship('Question', backref='answers')
def __repr__(self):
return "<Answer '%s'>" % self.text
class Question(Base):
__tablename__ = 'question'
id = Column(Integer, primary_key=True)
text = Column(String)
best_answer = relationship('Answer',
secondary=lambda: best_answer,
uselist=False)
def __repr__(self):
return "<Question, '%s'>" % (self.text)
best_answer = Table('best_answer', Base.metadata,
Column('question_id',
Integer,
ForeignKey('question.id'),
primary_key=True),
Column('answer_id',
Integer,
ForeignKey('answer.id'))
)
if __name__ == '__main__':
session = sessionmaker(bind=engine)()
Base.metadata.create_all(engine)
question = Question(text='How good is SQLAlchemy?')
somewhat = Answer(text='Somewhat good')
very = Answer(text='Very good')
excellent = Answer(text='Excellent!')
question.answers.extend([somewhat, very, excellent])
question.best_answer = excellent
session.add(question)
session.commit()
question = session.query(Question).first()
print(question.answers)
print(question.best_answer)
Ответ 2
Отметить решение работает, но я хотел найти способ сделать это, не создавая дополнительную таблицу. После обширного поиска я наконец нашел этот пример в документах:
http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html (второй пример)
Подходом является использование primaryjoin
[1] для обеих отношений в модели Question
и добавление post_update=True
к одному из них. post_update
сообщает sqlalchemy установить best_answer_id
в качестве дополнительного оператора UPDATE
, обходя круговую зависимость.
Вам также нужно foreign_keys
, указанное в отношении Question
в модели Answer
.
Ниже изменен код Mark, чтобы следовать приведенному выше примеру. Я тестировал его с помощью sqlalchemy v1.1.9
.
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
engine = create_engine('sqlite:///:memory:')
Base = declarative_base()
class Answer(Base):
__tablename__ = 'answer'
id = Column(Integer, primary_key=True)
text = Column(String)
question_id = Column(Integer, ForeignKey('question.id'))
question = relationship('Question', back_populates='answers', foreign_keys=[question_id])
def __repr__(self):
return "<Answer '%s'>" % self.text
class Question(Base):
__tablename__ = 'question'
id = Column(Integer, primary_key=True)
text = Column(String)
best_answer_id = Column(Integer, ForeignKey('answer.id'))
answers = relationship('Answer', primaryjoin= id==Answer.question_id)
best_answer = relationship('Answer', primaryjoin= best_answer_id==Answer.id, post_update=True)
def __repr__(self):
return "<Question, '%s'>" % (self.text)
if __name__ == '__main__':
session = sessionmaker(bind=engine)()
Base.metadata.create_all(engine)
question = Question(text='How good is SQLAlchemy?')
somewhat = Answer(text='Somewhat good')
very = Answer(text='Very good')
excellent = Answer(text='Excellent!')
question.answers.extend([somewhat, very, excellent])
question.best_answer = excellent
session.add(question)
session.commit()
question = session.query(Question).first()
print(question.answers)
print(question.best_answer)
[1] Интересно, что "строковый формат" для primaryjoin
, кажется, вызывает ошибку, - но работает SQL-выражение с перегруженными операторами в объектах столбца.
Ответ 3
Вы также можете сортировать "украсить" свои модели, если они изначально определены.
class Answer(Base):
__tablename__ = 'answers'
id = Column(Integer, primary_key=True)
text = Column(String)
class Question(Base):
__tablename__ = 'questions'
id = Column(Integer, primary_key=True)
text = Column(String)
picture = Column(String)
depth = Column(Integer)
amount_of_tasks = Column(Integer)
voting_threshold = Column(Integer)
best_answer_id = Column(Integer, ForeignKey('answers.id'), nullable=True)
Answer.question_id = Column(Integer, ForeignKey(Question.id))
Question.possible_answers = relationship(Answer, post_update=True, primaryjoin=Question.id==Answer.question_id)
Это не слишком приятно, так как определение класса начинает немного перемещаться, но оно делает трюк.
Ответ 4
Правильный способ выглядит как ForeignKeyConstraint(..., use_alter=True)
.
http://docs.sqlalchemy.org/en/latest/core/constraints.html#sqlalchemy.schema.ForeignKeyConstraint.params.use_alter