Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/multimodal/multimodal/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from .config import FISHJAM_ID, FISHJAM_TOKEN
from .worker import BackgroundWorker

fishjam = FishjamClient(FISHJAM_ID, FISHJAM_TOKEN)
fishjam = FishjamClient.create_and_verify(
fishjam_id=FISHJAM_ID, management_token=FISHJAM_TOKEN
)


class RoomService:
Expand Down
4 changes: 3 additions & 1 deletion examples/poet_chat/poet_chat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@
with open(GREET_PATH) as prompt:
OPENAI_GREET = prompt.read()

fishjam_client = FishjamClient(FISHJAM_ID, FISHJAM_TOKEN)
fishjam_client = FishjamClient.create_and_verify(
fishjam_id=FISHJAM_ID, management_token=FISHJAM_TOKEN
)
2 changes: 1 addition & 1 deletion examples/room_manager/room_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class PeerAccess:

class RoomService:
def __init__(self, args: Namespace, logger: Logger):
self.fishjam_client = FishjamClient(
self.fishjam_client = FishjamClient.create_and_verify(
fishjam_id=args.fishjam_id,
management_token=args.management_token,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class RoomService:
def __init__(self):
self.fishjam = FishjamClient(
self.fishjam = FishjamClient.create_and_verify(
fishjam_id=FISHJAM_ID, management_token=FISHJAM_TOKEN
)
self.room = self.fishjam.create_room(
Expand Down
4 changes: 3 additions & 1 deletion examples/transcription/transcription/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from .agent import TranscriptionAgent
from .config import FISHJAM_ID, FISHJAM_TOKEN

fishjam = FishjamClient(FISHJAM_ID, FISHJAM_TOKEN)
fishjam = FishjamClient.create_and_verify(
fishjam_id=FISHJAM_ID, management_token=FISHJAM_TOKEN
)


class RoomService:
Expand Down
3 changes: 3 additions & 0 deletions fishjam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Room,
RoomOptions,
)
from fishjam.errors import MissingFishjamIdError, MissingManagementTokenError

__version__ = version.__version__

Expand All @@ -39,6 +40,8 @@
"AgentOutputOptions",
"Room",
"Peer",
"MissingFishjamIdError",
"MissingManagementTokenError",
"events",
"errors",
"room",
Expand Down
3 changes: 2 additions & 1 deletion fishjam/_ws_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
ALLOWED_NOTIFICATIONS,
AllowedNotification,
)
from fishjam.utils import get_fishjam_url
from fishjam.utils import get_fishjam_url, validate_fishjam_config

NotificationHandler = (
Callable[[AllowedNotification], None]
Expand All @@ -38,6 +38,7 @@ def __init__(
management_token: str,
):
"""Create a FishjamNotifier instance with an ID and management token."""
validate_fishjam_config(fishjam_id, management_token)
websocket_url = get_fishjam_url(fishjam_id).replace("http", "ws")
self._fishjam_url = f"{websocket_url}/socket/server/websocket"
self._management_token: str = management_token
Expand Down
3 changes: 2 additions & 1 deletion fishjam/api/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from fishjam._openapi_client.models import Error
from fishjam._openapi_client.types import Response
from fishjam.errors import HTTPError
from fishjam.utils import get_fishjam_url
from fishjam.utils import get_fishjam_url, validate_fishjam_config
from fishjam.version import get_version


class Client:
def __init__(self, fishjam_id: str, management_token: str):
validate_fishjam_config(fishjam_id, management_token)
self._fishjam_url = get_fishjam_url(fishjam_id)
self.client = AuthenticatedClient(
self._fishjam_url,
Expand Down
44 changes: 44 additions & 0 deletions fishjam/api/_fishjam_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,56 @@ def __init__(
):
"""Create a FishjamClient instance.

Performs only required-field shape validation on the provided
credentials. The constructor does NOT contact the Fishjam backend.
Use :meth:`create_and_verify` or :meth:`check_credentials` to verify
the credentials against the backend.

Args:
fishjam_id: The unique identifier for the Fishjam instance.
management_token: The token used for authenticating management operations.

Raises:
MissingFishjamIdError: If ``fishjam_id`` is empty.
MissingManagementTokenError: If ``management_token`` is empty.
"""
super().__init__(fishjam_id=fishjam_id, management_token=management_token)

@classmethod
def create_and_verify(
cls, *, fishjam_id: str, management_token: str
) -> "FishjamClient":
"""Construct a FishjamClient and verify credentials against the backend.

Args:
fishjam_id: The unique identifier for the Fishjam instance.
management_token: The token used for authenticating management operations.

Returns:
FishjamClient: A client whose credentials have been verified.

Raises:
MissingFishjamIdError: If ``fishjam_id`` is empty.
MissingManagementTokenError: If ``management_token`` is empty.
UnauthorizedError: If the credentials are rejected by the backend.
NotFoundError: If ``fishjam_id`` does not refer to a known Fishjam.
"""
client = cls(fishjam_id=fishjam_id, management_token=management_token)
client.check_credentials()
return client

def check_credentials(self) -> None:
"""Verify configured credentials by pinging the backend.

Performs a single lightweight call (``get_all_rooms``) and lets the
normal error translation surface any HTTP errors.

Raises:
UnauthorizedError: On 401.
NotFoundError: On 404.
"""
self.get_all_rooms()

def create_peer(
self,
room_id: str,
Expand Down
10 changes: 10 additions & 0 deletions fishjam/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
from fishjam._openapi_client.types import Response


class MissingFishjamIdError(ValueError):
def __init__(self) -> None:
super().__init__("Fishjam ID is required")


class MissingManagementTokenError(ValueError):
def __init__(self) -> None:
super().__init__("Management Token is required")


class HTTPError(Exception):
""""""

Expand Down
9 changes: 9 additions & 0 deletions fishjam/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
from urllib.parse import urlparse

from fishjam.errors import MissingFishjamIdError, MissingManagementTokenError


def validate_fishjam_config(fishjam_id: str, management_token: str) -> None:
if not fishjam_id:
raise MissingFishjamIdError()
if not management_token:
raise MissingManagementTokenError()


def validate_url(url: str) -> bool:
try:
Expand Down
75 changes: 75 additions & 0 deletions tests/test_config_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# pylint: disable=missing-class-docstring, missing-function-docstring, missing-module-docstring

from unittest.mock import patch

import pytest

from fishjam import FishjamClient
from fishjam.errors import (
MissingFishjamIdError,
MissingManagementTokenError,
UnauthorizedError,
)

VALID_FISHJAM_ID = "fjm_test"
VALID_MANAGEMENT_TOKEN = "tok_test"


class TestSyncValidation:
def test_empty_fishjam_id_raises(self):
with pytest.raises(MissingFishjamIdError):
FishjamClient(fishjam_id="", management_token=VALID_MANAGEMENT_TOKEN)

def test_empty_management_token_raises(self):
with pytest.raises(MissingManagementTokenError):
FishjamClient(fishjam_id=VALID_FISHJAM_ID, management_token="")

def test_both_provided_does_not_raise(self):
FishjamClient(
fishjam_id=VALID_FISHJAM_ID, management_token=VALID_MANAGEMENT_TOKEN
)


class TestLiveCheck:
def test_create_and_verify_raises_unauthorized_on_401(self):
with patch.object(
FishjamClient,
"get_all_rooms",
side_effect=UnauthorizedError("Invalid token"),
):
with pytest.raises(UnauthorizedError):
FishjamClient.create_and_verify(
fishjam_id=VALID_FISHJAM_ID,
management_token=VALID_MANAGEMENT_TOKEN,
)

def test_create_and_verify_returns_client_and_pings_once(self):
with patch.object(
FishjamClient, "get_all_rooms", return_value=[]
) as mock_get_all:
client = FishjamClient.create_and_verify(
fishjam_id=VALID_FISHJAM_ID,
management_token=VALID_MANAGEMENT_TOKEN,
)

assert isinstance(client, FishjamClient)
assert mock_get_all.call_count == 1

def test_check_credentials_raises_unauthorized_on_401(self):
client = FishjamClient(
fishjam_id=VALID_FISHJAM_ID, management_token=VALID_MANAGEMENT_TOKEN
)
with patch.object(
FishjamClient,
"get_all_rooms",
side_effect=UnauthorizedError("Invalid token"),
):
with pytest.raises(UnauthorizedError):
client.check_credentials()

def test_check_credentials_returns_none_on_success(self):
client = FishjamClient(
fishjam_id=VALID_FISHJAM_ID, management_token=VALID_MANAGEMENT_TOKEN
)
with patch.object(FishjamClient, "get_all_rooms", return_value=[]):
assert client.check_credentials() is None
Loading