Как я могу избавиться от ошибки циклической зависимости при создании базы данных в 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)

Это не слишком приятно, так как определение класса начинает немного перемещаться, но оно делает трюк.