Многие-ко-многим SQLAlchemy

Есть две таблицы с предопредленными именами имя_класса.lower()+'s' и id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)

Не получается создать связь многие-ко-многим между 2 таблицами и между записями первой таблицы.

Таблица "сборки":

class Assembly(Base):
    serial_number: Mapped[str_uniq] = mapped_column(String)
    name: Mapped[str] = mapped_column(String)
    link: Mapped[str] = mapped_column(String)
    deleted_at: Mapped[DateTime] = mapped_column(DateTime)

    # Связь с таблицей assemblyparts через backref
    parts: Mapped[list['Part']] = relationship(
        'Part',
        secondary='assemblyparts',
        back_populates='assemblys'
    )

    # Связь с другими сборками через backref
    child_assemblys: Mapped[list['Assembly']] = relationship(
        'Assembly',
        secondary='assemblycompositions',
        primaryjoin=id == column('parent_assembly_id'),
        secondaryjoin=id == column('child_assembly_id'),
        back_populates='parent_assemblys'
    )

    parent_assemblys: Mapped[list['Assembly']] = relationship(
        'Assembly',
        secondary='assemblycompositions',
        primaryjoin=id == column('child_assembly_id'),
        secondaryjoin=id == column('parent_assembly_id'),
        back_populates='child_assemblys'
    )

и "Детали":

class Part(Base):
    serial_number: Mapped[str_uniq] = mapped_column(String)
    name: Mapped[str] = mapped_column(String)
    link: Mapped[str] = mapped_column(String)
    deleted_at: Mapped[DateTime] = mapped_column(DateTime)

    # Связь с таблицей assembly_parts через backref
    assemblys: Mapped[list['Assembly']] = relationship(
        'Assembly',
        secondary='assemblyparts',
        back_populates='parts'
    )

А также таблицы ассоциации:

class AssemblyPart(Base):
    '''Ассоциативная таблица для связей между сборками и деталями'''
    assembly_id: Mapped[int] = mapped_column(ForeignKey('assemblys.id'), primary_key=True)
    part_id: Mapped[int] = mapped_column(ForeignKey('parts.id'), primary_key=True)
    quantity: Mapped[int] = mapped_column(Integer)


class AssemblyComposition(Base):
    '''Промежуточная таблица для связи many-to-many между сборками'''
    parent_assembly_id: Mapped[int] = mapped_column(ForeignKey('assemblys.id'))
    child_assembly_id: Mapped[int] = mapped_column(ForeignKey('assemblys.id'))
    quantity: Mapped[int] = mapped_column(Integer)
    # Составной первичный ключ
    __table_args__ = (
        PrimaryKeyConstraint('parent_assembly_id', 'child_assembly_id'),
)

Для связи многие-ко-многим между Assembly и Part используется таблица AssemblyPart - каждая деталь может входить в несколько сборок, а каждая сборка может содержать несколько деталей. Кроме того, каждая сборка может включать в себя другие сборки и сама входить в другие сборки.

При инициализации таблиц появляется ошибка:

sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign key columns for primary join condition 'parent_assembly_id = :parent_assembly_id_1' on relationship Assembly.child_assemblys.

Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or are annotated in the join condition with the foreign() annotation.

Если убрать ассоциативную таблицу для связи сборок между собой (AssemblyComposition) и удалить для нее параметры из Assembly , то инициализация таблиц срабатывает корректно. Т.е. связь между деталями и сборками определена верно.

Что я делаю не так?


Ответы (1 шт):

Автор решения: Irking

Пока не создашь топик - не получится.

В общем помогло явное указание в параметрах primaryjoin и secondaryjoin -какие поля в Assembly используются для связи:

# Связь с другими сборками через backref

    child_assemblys: Mapped[list['Assembly']] = relationship(
        'Assembly',
        secondary='assemblycompositions',
        primaryjoin="Assembly.id == AssemblyComposition.parent_assembly_id",
        secondaryjoin="Assembly.id == AssemblyComposition.child_assembly_id",
        back_populates='parent_assemblys'
    )
    
    parent_assemblys: Mapped[list['Assembly']] = relationship(
        'Assembly',
        secondary='assemblycompositions',
        primaryjoin="Assembly.id == AssemblyComposition.child_assembly_id",
        secondaryjoin="Assembly.id == AssemblyComposition.parent_assembly_id",
        back_populates='child_assemblys'
    )
→ Ссылка