Sqlalchemy, самостоятельно ссылающийся на «многие ко многим» с помощью прокси-сервера ассоциации и таблицы с отображением объектов

#python #flask #sqlalchemy #many-to-many

#python #flask #sqlalchemy #многие-ко-многим

Вопрос:

Я пытаюсь создать самоссылающуюся модель «многие ко многим» в Flask. Я уже проверил множество примеров в Интернете, но в моем случае я хотел бы использовать таблицу, отображаемую с объектами, для обработки взаимосвязи. Также, поскольку я использую Flask с marshmallow, я хотел бы включить прокси-серверы ассоциации в свою таблицу, чтобы упростить сериализацию модели. Каждый пример, который я нашел, использует backref но я хотел сделать это с back_populates ради удобства чтения. В настоящее время я не уверен, возможно ли это. Пожалуйста, найдите ниже мой минимальный пример, чтобы продемонстрировать проблему.

 from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()

Base = declarative_base()


class NodeRelation(Base):
  __tablename__ = "node_relation"
  parent_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
  parent = relationship('Node',
                        primaryjoin="NodeRelation.parent_id == node.c.id",
                        back_populates='parent_childs',
                        #foreign_keys=parent_id
                        )

  child_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
  child = relationship('Node',
                        primaryjoin="NodeRelation.child_id == node.c.id",
                        back_populates='child_parents',
                        #foreign_keys=child_id
                        )

  def __init__(self, parent=None, child=None, **kwargs):
      super().__init__(**kwargs)
      if child:
          self.child = child
      if parent:
          self.parent = parent

  def __repr__(self):
      return "(parent_id: %s, child_id: %s)" % (self.parent_id, self.child_id)


class Node(Base):
  __tablename__ = "node"
  id = Column(Integer, primary_key=True, autoincrement=True)
  title = Column(String())
  parent_childs = relationship('NodeRelation',
                                primaryjoin="Node.id==node_relation.c.parent_id",
                                back_populates='parent',
                                cascade='all, delete',
                                #foreign_keys=NodeRelation.parent_id
                                )
  parents = association_proxy('parent_childs', 'parent',
                            creator=lambda parent: NodeRelation(parent=parent))

  child_parents = relationship('NodeRelation',
                                primaryjoin="Node.id==node_relation.c.child_id",
                                back_populates='child',
                                cascade='all, delete',
                                #foreign_keys=NodeRelation.child_id
                                )
  childs = association_proxy('child_parents', 'child',
                            creator=lambda child: NodeRelation(child=child))

  def __init__(self, title, **kwargs):
    super().__init__(**kwargs)
    self.title = title

  def __repr__(self):
      return "(id: %s, title: %s, childs: %s)" % (self.id, self.title, self.childs)


Base.metadata.create_all(engine)

n1 = Node("First")
n2 = Node("Second")
"""
# This is failing with: NOT NULL constraint failed: node_relation.parent_id
n1.childs.append(n2)

session.add(n1)
session.add(n2)
session.commit()
"""
# This one is working
c = NodeRelation(n1, n2)
session.add(n1)
session.add(n2)
session.add(c)

# Node 1 and Node 2 exists
q = session.query(NodeRelation).all()
print(q)

# This is failing with infinite recursion when childs property is displayed.
q2 = session.query(Node).all()
print(q2)
  

Если я использую n1.childs.append() , я получаю ошибку null contsraint. Если я напрямую создаю объект mapper с помощью n1 и n2, он работает нормально, но как только я получаю доступ к childs свойству, я получаю бесконечную рекурсию.

Редактировать:

Я выяснил, что пользовательский creator lambda вызывает ошибку добавления. Итак, обновленный код выглядит следующим образом:

 from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()

Base = declarative_base()


class NodeRelation(Base):
  __tablename__ = "node_relation"
  parent_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
  parent = relationship('Node',
                        primaryjoin="NodeRelation.parent_id == node.c.id",
                        back_populates='parent_childs',
                        #foreign_keys=parent_id
                        )

  child_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
  child = relationship('Node',
                        primaryjoin="NodeRelation.child_id == node.c.id",
                        back_populates='child_parents',
                        #foreign_keys=child_id
                        )

  def __init__(self, parent=None, child=None, **kwargs):
      super().__init__(**kwargs)
      if child:
          self.child = child
      if parent:
          self.parent = parent

  def __repr__(self):
      return "(parent_id: %s, child_id: %s)" % (self.parent_id, self.child_id)


class Node(Base):
  __tablename__ = "node"
  id = Column(Integer, primary_key=True, autoincrement=True)
  title = Column(String())
  parent_childs = relationship('NodeRelation',
                                primaryjoin="Node.id==node_relation.c.parent_id",
                                back_populates='parent',
                                cascade='all, delete',
                                #foreign_keys=NodeRelation.parent_id
                                )
  parents = association_proxy('parent_childs', 'parent')

  child_parents = relationship('NodeRelation',
                                primaryjoin="Node.id==node_relation.c.child_id",
                                back_populates='child',
                                cascade='all, delete',
                                #foreign_keys=NodeRelation.child_id
                                )
  childs = association_proxy('child_parents', 'child')

  def __init__(self, title, **kwargs):
    super().__init__(**kwargs)
    self.title = title

  def __repr__(self):
      return "(id: %s, title: %s, childs: %s)" % (self.id, self.title, self.childs)


Base.metadata.create_all(engine)

n1 = Node("First")
n2 = Node("Second")

# This is failing with: NOT NULL constraint failed: node_relation.parent_id
n1.childs.append(n2)

session.add(n1)
session.add(n2)
session.commit()

# Node 1 and Node 2 exists
q = session.query(NodeRelation).all()
print(q)

# This is failing with infinite recursion when childs property is displayed.
q2 = session.query(Node).all()
print(q2)
  

Итак, единственная проблема заключается в том, что, когда я пытаюсь получить доступ к childs свойству, я получаю бесконечную рекурсию. Я думаю, я что-то перепутал с отношениями.