Skip to content
Merged
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
12 changes: 10 additions & 2 deletions fishjam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# pylint: disable=locally-disabled, no-name-in-module, import-error

# Exceptions and Server Messages
from fishjam import agent, errors, events, integrations, peer, room, version
from fishjam import agent, errors, events, integrations, peer, recording, room, version
from fishjam._openapi_client.models import PeerMetadata

# API
Expand All @@ -26,10 +26,15 @@
Peer,
PeerOptions,
PeerOptionsVapi,
Recording,
Room,
RoomOptions,
)
from fishjam.errors import InvalidFishjamCredentialsError, MissingFishjamIdError
from fishjam.errors import (
InvalidFishjamCredentialsError,
MissingFishjamIdError,
StaleSdkError,
)

__version__ = version.__version__

Expand All @@ -47,13 +52,16 @@
"AgentOutputOptions",
"Room",
"Peer",
"Recording",
"MoqAccess",
"MissingFishjamIdError",
"InvalidFishjamCredentialsError",
"StaleSdkError",
"events",
"errors",
"room",
"peer",
"recording",
"agent",
"integrations",
]
Expand Down
21 changes: 17 additions & 4 deletions fishjam/_openapi_client/api/recordings/delete_recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ def _parse_response(

return response_401

if response.status_code == 409:
response_409 = Error.from_dict(response.json())

return response_409

if response.status_code == 503:
response_503 = Error.from_dict(response.json())

Expand Down Expand Up @@ -64,7 +69,9 @@ def sync_detailed(
) -> Response[Any | Error]:
"""Delete a recording

Delete a recording by id.
Delete a recording by id. The recording disappears from the API immediately; its stored media is
removed asynchronously by a background job. A recording that is still `active` cannot be deleted —
wait for it to finish or fail.

Args:
recording_id (str):
Expand Down Expand Up @@ -95,7 +102,9 @@ def sync(
) -> Any | Error | None:
"""Delete a recording

Delete a recording by id.
Delete a recording by id. The recording disappears from the API immediately; its stored media is
removed asynchronously by a background job. A recording that is still `active` cannot be deleted —
wait for it to finish or fail.

Args:
recording_id (str):
Expand All @@ -121,7 +130,9 @@ async def asyncio_detailed(
) -> Response[Any | Error]:
"""Delete a recording

Delete a recording by id.
Delete a recording by id. The recording disappears from the API immediately; its stored media is
removed asynchronously by a background job. A recording that is still `active` cannot be deleted —
wait for it to finish or fail.

Args:
recording_id (str):
Expand Down Expand Up @@ -150,7 +161,9 @@ async def asyncio(
) -> Any | Error | None:
"""Delete a recording

Delete a recording by id.
Delete a recording by id. The recording disappears from the API immediately; its stored media is
removed asynchronously by a background job. A recording that is still `active` cannot be deleted —
wait for it to finish or fail.

Args:
recording_id (str):
Expand Down
186 changes: 186 additions & 0 deletions fishjam/_openapi_client/api/recordings/stop_recording.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
from http import HTTPStatus
from typing import Any
from urllib.parse import quote

import httpx

from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.error import Error
from ...models.recording_details_response import RecordingDetailsResponse
from ...types import Response


def _get_kwargs(
recording_id: str,
) -> dict[str, Any]:
_kwargs: dict[str, Any] = {
"method": "post",
"url": "/recordings/{recording_id}/stop".format(
recording_id=quote(str(recording_id), safe=""),
),
}

return _kwargs


def _parse_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Error | RecordingDetailsResponse | None:
if response.status_code == 200:
response_200 = RecordingDetailsResponse.from_dict(response.json())

return response_200

if response.status_code == 401:
response_401 = Error.from_dict(response.json())

return response_401

if response.status_code == 404:
response_404 = Error.from_dict(response.json())

return response_404

if response.status_code == 503:
response_503 = Error.from_dict(response.json())

return response_503

if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None


def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
) -> Response[Error | RecordingDetailsResponse]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)


def sync_detailed(
recording_id: str,
*,
client: AuthenticatedClient,
) -> Response[Error | RecordingDetailsResponse]:
"""Stop a recording

Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active`
until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer
active is a no-op.

Args:
recording_id (str):

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Response[Error | RecordingDetailsResponse]
"""

kwargs = _get_kwargs(
recording_id=recording_id,
)

response = client.get_httpx_client().request(
**kwargs,
)

return _build_response(client=client, response=response)


def sync(
recording_id: str,
*,
client: AuthenticatedClient,
) -> Error | RecordingDetailsResponse | None:
"""Stop a recording

Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active`
until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer
active is a no-op.

Args:
recording_id (str):

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Error | RecordingDetailsResponse
"""

return sync_detailed(
recording_id=recording_id,
client=client,
).parsed


async def asyncio_detailed(
recording_id: str,
*,
client: AuthenticatedClient,
) -> Response[Error | RecordingDetailsResponse]:
"""Stop a recording

Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active`
until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer
active is a no-op.

Args:
recording_id (str):

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Response[Error | RecordingDetailsResponse]
"""

kwargs = _get_kwargs(
recording_id=recording_id,
)

response = await client.get_async_httpx_client().request(**kwargs)

return _build_response(client=client, response=response)


async def asyncio(
recording_id: str,
*,
client: AuthenticatedClient,
) -> Error | RecordingDetailsResponse | None:
"""Stop a recording

Request the recorder to stop capturing. Finalization is asynchronous: the recording stays `active`
until the capture is finalized, then becomes `finished`. Stopping a recording that is no longer
active is a no-op.

Args:
recording_id (str):

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Error | RecordingDetailsResponse
"""

return (
await asyncio_detailed(
recording_id=recording_id,
client=client,
)
).parsed
1 change: 1 addition & 0 deletions fishjam/_openapi_client/models/recording_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class RecordingStatus(str, Enum):
ACTIVE = "active"
AVAILABLE = "available"
FAILED = "failed"
FINISHED = "finished"

def __str__(self) -> str:
return str(self.value)
8 changes: 8 additions & 0 deletions fishjam/_webhook_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
from fishjam.events.allowed_notifications import (
ALLOWED_NOTIFICATIONS,
AllowedNotification,
validate_notification,
)


def _content_of(message: ServerMessage) -> Union[AllowedNotification, None]:
"""Return the message's `content` oneof if it is a supported notification."""
_which, content = betterproto.which_one_of(message, "content")
if isinstance(content, ALLOWED_NOTIFICATIONS):
validate_notification(content)
return content
return None

Expand Down Expand Up @@ -57,6 +59,10 @@ def decode_server_notifications(binary: bytes) -> List[AllowedNotification]:
Returns:
list[AllowedNotification]: The decoded notifications, in order. Empty
when the payload carries no supported notification.

Raises:
fishjam.errors.StaleSdkError: When a notification carries a value this
SDK cannot parse, which likely means the SDK is outdated.
"""
message = ServerMessage().parse(binary)
_which, content = betterproto.which_one_of(message, "content")
Expand All @@ -65,6 +71,7 @@ def decode_server_notifications(binary: bytes) -> List[AllowedNotification]:
return _unpack_batch(content)

if isinstance(content, ALLOWED_NOTIFICATIONS):
validate_notification(content)
return [content]

return []
Expand Down Expand Up @@ -123,6 +130,7 @@ def receive_binary(
return _unpack_batch(content)

if isinstance(content, ALLOWED_NOTIFICATIONS):
validate_notification(content)
return content

return None
2 changes: 2 additions & 0 deletions fishjam/_ws_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from fishjam.events.allowed_notifications import (
ALLOWED_NOTIFICATIONS,
AllowedNotification,
validate_notification,
)
from fishjam.utils import get_fishjam_url

Expand Down Expand Up @@ -135,6 +136,7 @@ async def _receive_loop(self):
_which, message = betterproto.which_one_of(message, "content")

if isinstance(message, ALLOWED_NOTIFICATIONS):
validate_notification(message)
res = self._notification_handler(message)
if inspect.isawaitable(res):
await res
Expand Down
9 changes: 8 additions & 1 deletion fishjam/api/_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import warnings
from http import HTTPStatus
from typing import cast

from fishjam._openapi_client.client import AuthenticatedClient
Expand All @@ -24,7 +25,13 @@ def _request(self, method, **kwargs):
response = method.sync_detailed(client=self.client, **kwargs)
self._handle_deprecation_header(response.headers)

if isinstance(response.parsed, Error):
# `parsed` is None for error statuses the endpoint spec doesn't
# document, so check the status code as well to never report
# success for a failed request
if (
isinstance(response.parsed, Error)
or response.status_code >= HTTPStatus.BAD_REQUEST
):
response = cast(Response[Error], response)
raise HTTPError.from_response(response)

Expand Down
Loading
Loading