m2m relationship с одной таблицей

Есть 2 таблицы:

class User(Base):
    __tablename__ = 'user'
    id: Mapped[str_50] = mapped_column(primary_key=True, autoincrement=False)
    username: Mapped[str_50] = mapped_column(nullable=True)
    cash: Mapped[int] = mapped_column(nullable=False, server_default=text('0'))
    is_admin: Mapped[bool] = mapped_column(nullable=False, server_default=text('false'))
    at_create: Mapped[datetime.datetime] = mapped_column(nullable=False, server_default=func.now())
    friends: Mapped[list['User']] = relationship(
        back_populates='friends',
        secondary='friend',
    )
    __table_args__ = (
        CheckConstraint("cash >= 0", name="check_cash_positive"),
        PrimaryKeyConstraint('id'),
    )
class Friend(Base):
    __tablename__ = 'friend'
    sender_id: Mapped[str_50] = mapped_column(ForeignKey(column='user.id', ondelete='CASCADE'),
                                              primary_key=True,
                                              nullable=False)
    addressee_id: Mapped[str_50] = mapped_column(ForeignKey(column='user.id', ondelete='CASCADE'),
                                                 primary_key=True,
                                                 nullable=False)
    type_of: Mapped[Type_of_friendship] = mapped_column(nullable=False)
    __table_args__ = (
        PrimaryKeyConstraint('sender_id', 'addressee_id'),
    )

Как вы видите, таблица User соединена сама с собой через таблицу Friend.

Мои запросы:

async def main():
    from schemas.enum_models import Type_of_friendship
    from schemas.pydantic_models import User_friends
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
    async with async_session_factory() as session:
        query = insert(User).values({'id': 1})
        await session.execute(query)
        query = insert(User).values({'id': 2})
        await session.execute(query)
        query = insert(Friend).values({'sender_id': 1,
                                       'addressee_id': 2,
                                       'type_of': Type_of_friendship.friend_request_from_me.value})
        await session.execute(query)
        query = select(User).options(selectinload(User.friends))
        result = await session.execute(query)
        print(User_friends.model_validate(result.scalars().all(), from_attributes=True))


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Появляется ошибка, как можно правильно написать m2m связь одной таблицы.

str_50 - VARCHAR(50)


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

Автор решения: Мирон Климов

Нужно поменять:

friends: Mapped[list['User']] = relationship(
        back_populates='friends',
        secondary='friend',
    )

На:

friends: Mapped[list['User']] = relationship(
        back_populates='friends',
        secondary='friend',
        primaryjoin='user.c.id==friend.c.sender_id',
        secondaryjoin='user.c.id==friend.c.sender_id',
    )

нужно добавить primaryjoin и secondaryjoin в relationship

→ Ссылка