async def METHOD_NAME(self, recipient: Player, sender: Player):
    party = self.player_parties.get(sender)
    if (
        not party or
        recipient not in party.invited_players or
        party.invited_players[recipient].is_expired()
    ):
        # TODO: Localize with a proper message
        raise ClientError("You are not invited to that party (anymore)", recoverable=True)
    if sender.state is PlayerState.SEARCHING_LADDER:
        # TODO: Localize with a proper message
        raise ClientError("That party is already in queue", recoverable=True)
    old_party = self.player_parties.get(recipient)
    if old_party is not None:
        # Preserve state (like faction selection) from the old party
        member = old_party.get_member_by_player(recipient)
        assert member is not None
        await self.leave_party(recipient)
        party.add_member(member)
    else:
        party.add_player(recipient)
    self.player_parties[recipient] = party
    self.mark_dirty(party)