ARTICLE AD BOX
I have users table and they have friends using many to many relationship with association table. So the problem is when I insert frienship with user_id = 1 and friend_id = 2 the relation will be created, but if I insert same friendship user_id = 2 and friend_id = 1 it still will be created (in my code user_id = 1 friend_id = 2 and user_id = 2 friend_id = 1 is same friendship but with opposite ids) and I need to get an error if I create existing friendship, I think the problem in my UniqueConstraint my models:
class FriendshipAssociation(Base): __tablename__ = "users_friendship_associations" __table_args__ = ( UniqueConstraint( "user_id", "friend_id", name="unique_users_friendship" ), ) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), primary_key=True) friend_id: Mapped[int] = mapped_column(ForeignKey("users.id"), primary_key=True) status: Mapped[FriendshipAssociationStatus] = mapped_column( Enum(FriendshipAssociationStatus), default=FriendshipAssociationStatus.REQUESTED ) requsted_by_user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) requsted_by_user: Mapped["User"] = relationship(foreign_keys=[requsted_by_user_id]) read: Mapped[bool] = mapped_column(Boolean, default=False, nullable=True) def get_friend_id(self, user_id: int): if self.user_id == user_id: return self.friend_id return self.user_id def __repr__(self): return f"{self.user_id=} {self.friend_id=} {self.status=} {self.requsted_by_user_id=}" class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(BigInteger, primary_key=True) name: Mapped[str] = mapped_column(String(64)) username: Mapped[str] = mapped_column(String(32), nullable=True) status: Mapped[UserStatus] = mapped_column(Enum(UserStatus), default=UserStatus.USER) lang_code: Mapped[str | None] = mapped_column(String(35), nullable=True) timezone: Mapped[str] = mapped_column( String(50), default=DEFAULT_DB_USER_TIMEZONE, server_default=text(f"'{DEFAULT_DB_USER_TIMEZONE}'") ) # permissions can_write_admin_reports: Mapped[bool] = mapped_column(Boolean, default=True) # relationships admin_reports: Mapped[list["AdminReport"]] = relationship( # type: ignore back_populates="user", cascade="all, delete", passive_deletes=True, order_by="AdminReport.created_at.desc()" ) played_games: Mapped[list["PlayedGameAssociation"]] = relationship( # type: ignore back_populates="user" ) friends: Mapped[list["User"]] = relationship( # type: ignore secondary="users_friendship_associations", primaryjoin=FriendshipAssociation.user_id == id, secondaryjoin=FriendshipAssociation.friend_id == id, lazy="selectin", back_populates="friends" ) def __str__(self): return f"{self.id}, {self.name}, {self.username}, {self.created_at}, {self.lang_code}, {self.timezone}" def __repr__(self): return f"{self.id}, {self.name}, {self.username}, {self.created_at}, {self.lang_code}, {self.timezone}"Or maybe the problem is how I insert relationships:
async def create_requested_users_friendship( session: AsyncSession, user_id: int, friend_id: int ) -> FriendshipAssociation: try: friendship = FriendshipAssociation( user_id=user_id, friend_id=friend_id, requsted_by_user_id=user_id ) session.add(friendship) await session.commit() return friendship except IntegrityError: raise exceptions.DBUsersFriendhipAlreadyExistsError(f"FriendShip with {user_id=} and {friend_id=} already exists")