diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 407856a..ae1619f 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -21,6 +21,8 @@ services: JF_SERVER_API_TOKEN: "development" JF_PORT: 5002 JF_SECRET_KEY_BASE: "super-secret-key" + JF_SIP_USED: "true" + JF_SIP_IP: "127.0.0.1" ports: - "5002:5002" - "49999:49999" diff --git a/jellyfish/__init__.py b/jellyfish/__init__.py index fbba6e4..57d9a3f 100644 --- a/jellyfish/__init__.py +++ b/jellyfish/__init__.py @@ -15,17 +15,22 @@ ComponentOptionsHLS, ComponentOptionsHLSSubscribeMode, ComponentOptionsRTSP, + ComponentOptionsSIP, ComponentPropertiesFile, ComponentPropertiesHLS, ComponentPropertiesHLSSubscribeMode, ComponentPropertiesRTSP, + ComponentPropertiesSIP, + ComponentPropertiesSIPSIPCredentials, ComponentRTSP, + ComponentSIP, Peer, PeerOptionsWebRTC, PeerStatus, Room, RoomConfig, RoomConfigVideoCodec, + SIPCredentials, ) # API @@ -50,6 +55,11 @@ "ComponentOptionsHLSSubscribeMode", "ComponentPropertiesHLS", "ComponentPropertiesHLSSubscribeMode", + "ComponentSIP", + "ComponentOptionsSIP", + "ComponentPropertiesSIP", + "ComponentPropertiesSIPSIPCredentials", + "ComponentFile", "ComponentRTSP", "ComponentOptionsRTSP", "ComponentPropertiesRTSP", @@ -58,5 +68,6 @@ "ComponentPropertiesFile", "events", "errors", + "SIPCredentials", ] __docformat__ = "restructuredtext" diff --git a/jellyfish/_openapi_client/api/default/__init__.py b/jellyfish/_openapi_client/api/health/__init__.py similarity index 100% rename from jellyfish/_openapi_client/api/default/__init__.py rename to jellyfish/_openapi_client/api/health/__init__.py diff --git a/jellyfish/_openapi_client/api/default/healthcheck.py b/jellyfish/_openapi_client/api/health/healthcheck.py similarity index 81% rename from jellyfish/_openapi_client/api/default/healthcheck.py rename to jellyfish/_openapi_client/api/health/healthcheck.py index 2cef558..c66453b 100644 --- a/jellyfish/_openapi_client/api/default/healthcheck.py +++ b/jellyfish/_openapi_client/api/health/healthcheck.py @@ -5,6 +5,7 @@ from ... import errors from ...client import AuthenticatedClient, Client +from ...models.error import Error from ...models.healthcheck_response import HealthcheckResponse from ...types import Response @@ -18,15 +19,15 @@ def _get_kwargs() -> Dict[str, Any]: def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[HealthcheckResponse]: +) -> Optional[Union[Error, HealthcheckResponse]]: if response.status_code == HTTPStatus.OK: response_200 = HealthcheckResponse.from_dict(response.json()) return response_200 - if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR: - response_500 = HealthcheckResponse.from_dict(response.json()) + if response.status_code == HTTPStatus.UNAUTHORIZED: + response_401 = Error.from_dict(response.json()) - return response_500 + return response_401 if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: @@ -35,7 +36,7 @@ def _parse_response( def _build_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[HealthcheckResponse]: +) -> Response[Union[Error, HealthcheckResponse]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -47,7 +48,7 @@ def _build_response( def sync_detailed( *, client: Union[AuthenticatedClient, Client], -) -> Response[HealthcheckResponse]: +) -> Response[Union[Error, HealthcheckResponse]]: """Describes the health of Jellyfish Raises: @@ -55,7 +56,7 @@ def sync_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[HealthcheckResponse] + Response[Union[Error, HealthcheckResponse]] """ kwargs = _get_kwargs() @@ -70,7 +71,7 @@ def sync_detailed( def sync( *, client: Union[AuthenticatedClient, Client], -) -> Optional[HealthcheckResponse]: +) -> Optional[Union[Error, HealthcheckResponse]]: """Describes the health of Jellyfish Raises: @@ -78,7 +79,7 @@ def sync( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - HealthcheckResponse + Union[Error, HealthcheckResponse] """ return sync_detailed( @@ -89,7 +90,7 @@ def sync( async def asyncio_detailed( *, client: Union[AuthenticatedClient, Client], -) -> Response[HealthcheckResponse]: +) -> Response[Union[Error, HealthcheckResponse]]: """Describes the health of Jellyfish Raises: @@ -97,7 +98,7 @@ async def asyncio_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[HealthcheckResponse] + Response[Union[Error, HealthcheckResponse]] """ kwargs = _get_kwargs() @@ -110,7 +111,7 @@ async def asyncio_detailed( async def asyncio( *, client: Union[AuthenticatedClient, Client], -) -> Optional[HealthcheckResponse]: +) -> Optional[Union[Error, HealthcheckResponse]]: """Describes the health of Jellyfish Raises: @@ -118,7 +119,7 @@ async def asyncio( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - HealthcheckResponse + Union[Error, HealthcheckResponse] """ return ( diff --git a/jellyfish/_openapi_client/api/hls/subscribe_hls_to.py b/jellyfish/_openapi_client/api/hls/subscribe_hls_to.py index 1f00bde..ea1b7e9 100644 --- a/jellyfish/_openapi_client/api/hls/subscribe_hls_to.py +++ b/jellyfish/_openapi_client/api/hls/subscribe_hls_to.py @@ -36,6 +36,10 @@ def _parse_response( response_400 = Error.from_dict(response.json()) return response_400 + if response.status_code == HTTPStatus.UNAUTHORIZED: + response_401 = Error.from_dict(response.json()) + + return response_401 if response.status_code == HTTPStatus.NOT_FOUND: response_404 = Error.from_dict(response.json()) diff --git a/jellyfish/_openapi_client/api/recording/delete_recording.py b/jellyfish/_openapi_client/api/recording/delete_recording.py index ae6ba21..ea03030 100644 --- a/jellyfish/_openapi_client/api/recording/delete_recording.py +++ b/jellyfish/_openapi_client/api/recording/delete_recording.py @@ -30,6 +30,10 @@ def _parse_response( response_400 = Error.from_dict(response.json()) return response_400 + if response.status_code == HTTPStatus.UNAUTHORIZED: + response_401 = Error.from_dict(response.json()) + + return response_401 if response.status_code == HTTPStatus.NOT_FOUND: response_404 = Error.from_dict(response.json()) diff --git a/jellyfish/_openapi_client/api/recording/get_recordings.py b/jellyfish/_openapi_client/api/recording/get_recordings.py index 8855ab9..8174071 100644 --- a/jellyfish/_openapi_client/api/recording/get_recordings.py +++ b/jellyfish/_openapi_client/api/recording/get_recordings.py @@ -24,6 +24,10 @@ def _parse_response( response_200 = RecordingListResponse.from_dict(response.json()) return response_200 + if response.status_code == HTTPStatus.UNAUTHORIZED: + response_401 = Error.from_dict(response.json()) + + return response_401 if response.status_code == HTTPStatus.NOT_FOUND: response_404 = Error.from_dict(response.json()) diff --git a/jellyfish/_openapi_client/api/sip/__init__.py b/jellyfish/_openapi_client/api/sip/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jellyfish/_openapi_client/api/sip/dial.py b/jellyfish/_openapi_client/api/sip/dial.py new file mode 100644 index 0000000..c7c9edb --- /dev/null +++ b/jellyfish/_openapi_client/api/sip/dial.py @@ -0,0 +1,193 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.dial_config import DialConfig +from ...models.error import Error +from ...types import Response + + +def _get_kwargs( + room_id: str, + component_id: str, + *, + json_body: DialConfig, +) -> Dict[str, Any]: + json_json_body = json_body.to_dict() + + return { + "method": "post", + "url": "/sip/{room_id}/{component_id}/call".format( + room_id=room_id, + component_id=component_id, + ), + "json": json_json_body, + } + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Any, Error]]: + if response.status_code == HTTPStatus.CREATED: + response_201 = cast(Any, None) + return response_201 + if response.status_code == HTTPStatus.BAD_REQUEST: + response_400 = Error.from_dict(response.json()) + + return response_400 + if response.status_code == HTTPStatus.UNAUTHORIZED: + response_401 = Error.from_dict(response.json()) + + return response_401 + if response.status_code == HTTPStatus.NOT_FOUND: + response_404 = Error.from_dict(response.json()) + + return response_404 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Any, Error]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], + json_body: DialConfig, +) -> Response[Union[Any, Error]]: + """Make a call from the SIP component to the provided phone number + + Args: + room_id (str): + component_id (str): + json_body (DialConfig): Dial config + + 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[Union[Any, Error]] + """ + + kwargs = _get_kwargs( + room_id=room_id, + component_id=component_id, + json_body=json_body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], + json_body: DialConfig, +) -> Optional[Union[Any, Error]]: + """Make a call from the SIP component to the provided phone number + + Args: + room_id (str): + component_id (str): + json_body (DialConfig): Dial config + + 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: + Union[Any, Error] + """ + + return sync_detailed( + room_id=room_id, + component_id=component_id, + client=client, + json_body=json_body, + ).parsed + + +async def asyncio_detailed( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], + json_body: DialConfig, +) -> Response[Union[Any, Error]]: + """Make a call from the SIP component to the provided phone number + + Args: + room_id (str): + component_id (str): + json_body (DialConfig): Dial config + + 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[Union[Any, Error]] + """ + + kwargs = _get_kwargs( + room_id=room_id, + component_id=component_id, + json_body=json_body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], + json_body: DialConfig, +) -> Optional[Union[Any, Error]]: + """Make a call from the SIP component to the provided phone number + + Args: + room_id (str): + component_id (str): + json_body (DialConfig): Dial config + + 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: + Union[Any, Error] + """ + + return ( + await asyncio_detailed( + room_id=room_id, + component_id=component_id, + client=client, + json_body=json_body, + ) + ).parsed diff --git a/jellyfish/_openapi_client/api/sip/end_call.py b/jellyfish/_openapi_client/api/sip/end_call.py new file mode 100644 index 0000000..772fc0f --- /dev/null +++ b/jellyfish/_openapi_client/api/sip/end_call.py @@ -0,0 +1,175 @@ +from http import HTTPStatus +from typing import Any, Dict, Optional, Union, cast + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error import Error +from ...types import Response + + +def _get_kwargs( + room_id: str, + component_id: str, +) -> Dict[str, Any]: + return { + "method": "delete", + "url": "/sip/{room_id}/{component_id}/call".format( + room_id=room_id, + component_id=component_id, + ), + } + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Any, Error]]: + if response.status_code == HTTPStatus.CREATED: + response_201 = cast(Any, None) + return response_201 + if response.status_code == HTTPStatus.BAD_REQUEST: + response_400 = Error.from_dict(response.json()) + + return response_400 + if response.status_code == HTTPStatus.UNAUTHORIZED: + response_401 = Error.from_dict(response.json()) + + return response_401 + if response.status_code == HTTPStatus.NOT_FOUND: + response_404 = Error.from_dict(response.json()) + + return response_404 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Any, Error]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], +) -> Response[Union[Any, Error]]: + """Finish call made by SIP component + + Args: + room_id (str): + component_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[Union[Any, Error]] + """ + + kwargs = _get_kwargs( + room_id=room_id, + component_id=component_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], +) -> Optional[Union[Any, Error]]: + """Finish call made by SIP component + + Args: + room_id (str): + component_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: + Union[Any, Error] + """ + + return sync_detailed( + room_id=room_id, + component_id=component_id, + client=client, + ).parsed + + +async def asyncio_detailed( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], +) -> Response[Union[Any, Error]]: + """Finish call made by SIP component + + Args: + room_id (str): + component_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[Union[Any, Error]] + """ + + kwargs = _get_kwargs( + room_id=room_id, + component_id=component_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + room_id: str, + component_id: str, + *, + client: Union[AuthenticatedClient, Client], +) -> Optional[Union[Any, Error]]: + """Finish call made by SIP component + + Args: + room_id (str): + component_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: + Union[Any, Error] + """ + + return ( + await asyncio_detailed( + room_id=room_id, + component_id=component_id, + client=client, + ) + ).parsed diff --git a/jellyfish/_openapi_client/models/__init__.py b/jellyfish/_openapi_client/models/__init__.py index a7de09e..de23bcb 100644 --- a/jellyfish/_openapi_client/models/__init__.py +++ b/jellyfish/_openapi_client/models/__init__.py @@ -9,11 +9,19 @@ from .component_options_hls import ComponentOptionsHLS from .component_options_hls_subscribe_mode import ComponentOptionsHLSSubscribeMode from .component_options_rtsp import ComponentOptionsRTSP +from .component_options_sip import ComponentOptionsSIP +from .component_options_sipsip_credentials import ComponentOptionsSIPSIPCredentials from .component_properties_file import ComponentPropertiesFile from .component_properties_hls import ComponentPropertiesHLS from .component_properties_hls_subscribe_mode import ComponentPropertiesHLSSubscribeMode from .component_properties_rtsp import ComponentPropertiesRTSP +from .component_properties_sip import ComponentPropertiesSIP +from .component_properties_sipsip_credentials import ( + ComponentPropertiesSIPSIPCredentials, +) from .component_rtsp import ComponentRTSP +from .component_sip import ComponentSIP +from .dial_config import DialConfig from .error import Error from .health_report import HealthReport from .health_report_distribution import HealthReportDistribution @@ -33,6 +41,7 @@ from .room_details_response import RoomDetailsResponse from .rooms_listing_response import RoomsListingResponse from .s3_credentials import S3Credentials +from .sip_credentials import SIPCredentials from .subscription_config import SubscriptionConfig from .track import Track from .track_type import TrackType @@ -47,11 +56,17 @@ "ComponentOptionsHLS", "ComponentOptionsHLSSubscribeMode", "ComponentOptionsRTSP", + "ComponentOptionsSIP", + "ComponentOptionsSIPSIPCredentials", "ComponentPropertiesFile", "ComponentPropertiesHLS", "ComponentPropertiesHLSSubscribeMode", "ComponentPropertiesRTSP", + "ComponentPropertiesSIP", + "ComponentPropertiesSIPSIPCredentials", "ComponentRTSP", + "ComponentSIP", + "DialConfig", "Error", "HealthcheckResponse", "HealthReport", @@ -71,6 +86,7 @@ "RoomDetailsResponse", "RoomsListingResponse", "S3Credentials", + "SIPCredentials", "SubscriptionConfig", "Track", "TrackType", diff --git a/jellyfish/_openapi_client/models/add_component_json_body.py b/jellyfish/_openapi_client/models/add_component_json_body.py index 82c7699..c3e0408 100644 --- a/jellyfish/_openapi_client/models/add_component_json_body.py +++ b/jellyfish/_openapi_client/models/add_component_json_body.py @@ -9,6 +9,7 @@ from ..models.component_options_file import ComponentOptionsFile from ..models.component_options_hls import ComponentOptionsHLS from ..models.component_options_rtsp import ComponentOptionsRTSP + from ..models.component_options_sip import ComponentOptionsSIP T = TypeVar("T", bound="AddComponentJsonBody") @@ -21,7 +22,11 @@ class AddComponentJsonBody: type: str """Component type""" options: Union[ - "ComponentOptionsFile", "ComponentOptionsHLS", "ComponentOptionsRTSP", Unset + "ComponentOptionsFile", + "ComponentOptionsHLS", + "ComponentOptionsRTSP", + "ComponentOptionsSIP", + Unset, ] = UNSET """Component-specific options""" additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -29,6 +34,7 @@ class AddComponentJsonBody: def to_dict(self) -> Dict[str, Any]: """@private""" + from ..models.component_options_file import ComponentOptionsFile from ..models.component_options_hls import ComponentOptionsHLS from ..models.component_options_rtsp import ComponentOptionsRTSP @@ -43,6 +49,9 @@ def to_dict(self) -> Dict[str, Any]: elif isinstance(self.options, ComponentOptionsRTSP): options = self.options.to_dict() + elif isinstance(self.options, ComponentOptionsFile): + options = self.options.to_dict() + else: options = self.options.to_dict() @@ -64,6 +73,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: from ..models.component_options_file import ComponentOptionsFile from ..models.component_options_hls import ComponentOptionsHLS from ..models.component_options_rtsp import ComponentOptionsRTSP + from ..models.component_options_sip import ComponentOptionsSIP d = src_dict.copy() type = d.pop("type") @@ -71,7 +81,11 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: def _parse_options( data: object, ) -> Union[ - "ComponentOptionsFile", "ComponentOptionsHLS", "ComponentOptionsRTSP", Unset + "ComponentOptionsFile", + "ComponentOptionsHLS", + "ComponentOptionsRTSP", + "ComponentOptionsSIP", + Unset, ]: if isinstance(data, Unset): return data @@ -95,13 +109,23 @@ def _parse_options( return componentsschemas_component_options_type_1 except: # noqa: E722 pass + try: + if not isinstance(data, dict): + raise TypeError() + componentsschemas_component_options_type_2 = ( + ComponentOptionsFile.from_dict(data) + ) + + return componentsschemas_component_options_type_2 + except: # noqa: E722 + pass if not isinstance(data, dict): raise TypeError() - componentsschemas_component_options_type_2 = ComponentOptionsFile.from_dict( + componentsschemas_component_options_type_3 = ComponentOptionsSIP.from_dict( data ) - return componentsschemas_component_options_type_2 + return componentsschemas_component_options_type_3 options = _parse_options(d.pop("options", UNSET)) diff --git a/jellyfish/_openapi_client/models/component_details_response.py b/jellyfish/_openapi_client/models/component_details_response.py index 959e0bc..91de1a9 100644 --- a/jellyfish/_openapi_client/models/component_details_response.py +++ b/jellyfish/_openapi_client/models/component_details_response.py @@ -7,6 +7,7 @@ from ..models.component_file import ComponentFile from ..models.component_hls import ComponentHLS from ..models.component_rtsp import ComponentRTSP + from ..models.component_sip import ComponentSIP T = TypeVar("T", bound="ComponentDetailsResponse") @@ -16,13 +17,14 @@ class ComponentDetailsResponse: """Response containing component details""" - data: Union["ComponentFile", "ComponentHLS", "ComponentRTSP"] + data: Union["ComponentFile", "ComponentHLS", "ComponentRTSP", "ComponentSIP"] """Describes component""" additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) """@private""" def to_dict(self) -> Dict[str, Any]: """@private""" + from ..models.component_file import ComponentFile from ..models.component_hls import ComponentHLS from ..models.component_rtsp import ComponentRTSP @@ -34,6 +36,9 @@ def to_dict(self) -> Dict[str, Any]: elif isinstance(self.data, ComponentRTSP): data = self.data.to_dict() + elif isinstance(self.data, ComponentFile): + data = self.data.to_dict() + else: data = self.data.to_dict() @@ -53,12 +58,13 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: from ..models.component_file import ComponentFile from ..models.component_hls import ComponentHLS from ..models.component_rtsp import ComponentRTSP + from ..models.component_sip import ComponentSIP d = src_dict.copy() def _parse_data( data: object, - ) -> Union["ComponentFile", "ComponentHLS", "ComponentRTSP"]: + ) -> Union["ComponentFile", "ComponentHLS", "ComponentRTSP", "ComponentSIP"]: try: if not isinstance(data, dict): raise TypeError() @@ -75,11 +81,19 @@ def _parse_data( return componentsschemas_component_type_1 except: # noqa: E722 pass + try: + if not isinstance(data, dict): + raise TypeError() + componentsschemas_component_type_2 = ComponentFile.from_dict(data) + + return componentsschemas_component_type_2 + except: # noqa: E722 + pass if not isinstance(data, dict): raise TypeError() - componentsschemas_component_type_2 = ComponentFile.from_dict(data) + componentsschemas_component_type_3 = ComponentSIP.from_dict(data) - return componentsschemas_component_type_2 + return componentsschemas_component_type_3 data = _parse_data(d.pop("data")) diff --git a/jellyfish/_openapi_client/models/component_options_sip.py b/jellyfish/_openapi_client/models/component_options_sip.py new file mode 100644 index 0000000..9bdc819 --- /dev/null +++ b/jellyfish/_openapi_client/models/component_options_sip.py @@ -0,0 +1,72 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.component_options_sipsip_credentials import ( + ComponentOptionsSIPSIPCredentials, + ) + + +T = TypeVar("T", bound="ComponentOptionsSIP") + + +@_attrs_define +class ComponentOptionsSIP: + """Options specific to the SIP component""" + + registrar_credentials: "ComponentOptionsSIPSIPCredentials" + """Credentials used to authorize in SIP Provider service""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + registrar_credentials = self.registrar_credentials.to_dict() + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "registrarCredentials": registrar_credentials, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + from ..models.component_options_sipsip_credentials import ( + ComponentOptionsSIPSIPCredentials, + ) + + d = src_dict.copy() + registrar_credentials = ComponentOptionsSIPSIPCredentials.from_dict( + d.pop("registrarCredentials") + ) + + component_options_sip = cls( + registrar_credentials=registrar_credentials, + ) + + component_options_sip.additional_properties = d + return component_options_sip + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/_openapi_client/models/component_options_sipsip_credentials.py b/jellyfish/_openapi_client/models/component_options_sipsip_credentials.py new file mode 100644 index 0000000..4cf83a3 --- /dev/null +++ b/jellyfish/_openapi_client/models/component_options_sipsip_credentials.py @@ -0,0 +1,74 @@ +from typing import Any, Dict, List, Type, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="ComponentOptionsSIPSIPCredentials") + + +@_attrs_define +class ComponentOptionsSIPSIPCredentials: + """Credentials used to authorize in SIP Provider service""" + + address: str + """SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed""" + password: str + """Password in SIP service provider""" + username: str + """Username in SIP service provider""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + address = self.address + password = self.password + username = self.username + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "address": address, + "password": password, + "username": username, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + d = src_dict.copy() + address = d.pop("address") + + password = d.pop("password") + + username = d.pop("username") + + component_options_sipsip_credentials = cls( + address=address, + password=password, + username=username, + ) + + component_options_sipsip_credentials.additional_properties = d + return component_options_sipsip_credentials + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/_openapi_client/models/component_properties_sip.py b/jellyfish/_openapi_client/models/component_properties_sip.py new file mode 100644 index 0000000..adb6b1c --- /dev/null +++ b/jellyfish/_openapi_client/models/component_properties_sip.py @@ -0,0 +1,72 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.component_properties_sipsip_credentials import ( + ComponentPropertiesSIPSIPCredentials, + ) + + +T = TypeVar("T", bound="ComponentPropertiesSIP") + + +@_attrs_define +class ComponentPropertiesSIP: + """Properties specific to the SIP component""" + + registrar_credentials: "ComponentPropertiesSIPSIPCredentials" + """Credentials used to authorize in SIP Provider service""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + registrar_credentials = self.registrar_credentials.to_dict() + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "registrarCredentials": registrar_credentials, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + from ..models.component_properties_sipsip_credentials import ( + ComponentPropertiesSIPSIPCredentials, + ) + + d = src_dict.copy() + registrar_credentials = ComponentPropertiesSIPSIPCredentials.from_dict( + d.pop("registrarCredentials") + ) + + component_properties_sip = cls( + registrar_credentials=registrar_credentials, + ) + + component_properties_sip.additional_properties = d + return component_properties_sip + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/_openapi_client/models/component_properties_sipsip_credentials.py b/jellyfish/_openapi_client/models/component_properties_sipsip_credentials.py new file mode 100644 index 0000000..2757aea --- /dev/null +++ b/jellyfish/_openapi_client/models/component_properties_sipsip_credentials.py @@ -0,0 +1,74 @@ +from typing import Any, Dict, List, Type, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="ComponentPropertiesSIPSIPCredentials") + + +@_attrs_define +class ComponentPropertiesSIPSIPCredentials: + """Credentials used to authorize in SIP Provider service""" + + address: str + """SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed""" + password: str + """Password in SIP service provider""" + username: str + """Username in SIP service provider""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + address = self.address + password = self.password + username = self.username + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "address": address, + "password": password, + "username": username, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + d = src_dict.copy() + address = d.pop("address") + + password = d.pop("password") + + username = d.pop("username") + + component_properties_sipsip_credentials = cls( + address=address, + password=password, + username=username, + ) + + component_properties_sipsip_credentials.additional_properties = d + return component_properties_sipsip_credentials + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/_openapi_client/models/component_sip.py b/jellyfish/_openapi_client/models/component_sip.py new file mode 100644 index 0000000..7cbab9d --- /dev/null +++ b/jellyfish/_openapi_client/models/component_sip.py @@ -0,0 +1,100 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.component_properties_sip import ComponentPropertiesSIP + from ..models.track import Track + + +T = TypeVar("T", bound="ComponentSIP") + + +@_attrs_define +class ComponentSIP: + """Describes the SIP component""" + + id: str + """Assigned component ID""" + properties: "ComponentPropertiesSIP" + """Properties specific to the SIP component""" + tracks: List["Track"] + """List of all component's tracks""" + type: str + """Component type""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + id = self.id + properties = self.properties.to_dict() + + tracks = [] + for tracks_item_data in self.tracks: + tracks_item = tracks_item_data.to_dict() + + tracks.append(tracks_item) + + type = self.type + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "id": id, + "properties": properties, + "tracks": tracks, + "type": type, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + from ..models.component_properties_sip import ComponentPropertiesSIP + from ..models.track import Track + + d = src_dict.copy() + id = d.pop("id") + + properties = ComponentPropertiesSIP.from_dict(d.pop("properties")) + + tracks = [] + _tracks = d.pop("tracks") + for tracks_item_data in _tracks: + tracks_item = Track.from_dict(tracks_item_data) + + tracks.append(tracks_item) + + type = d.pop("type") + + component_sip = cls( + id=id, + properties=properties, + tracks=tracks, + type=type, + ) + + component_sip.additional_properties = d + return component_sip + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/_openapi_client/models/dial_config.py b/jellyfish/_openapi_client/models/dial_config.py new file mode 100644 index 0000000..4b6b021 --- /dev/null +++ b/jellyfish/_openapi_client/models/dial_config.py @@ -0,0 +1,60 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="DialConfig") + + +@_attrs_define +class DialConfig: + """Dial config""" + + phone_number: Union[Unset, str] = UNSET + """Phone number on which SIP Component will call""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + phone_number = self.phone_number + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if phone_number is not UNSET: + field_dict["phoneNumber"] = phone_number + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + d = src_dict.copy() + phone_number = d.pop("phoneNumber", UNSET) + + dial_config = cls( + phone_number=phone_number, + ) + + dial_config.additional_properties = d + return dial_config + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/_openapi_client/models/room.py b/jellyfish/_openapi_client/models/room.py index 62fcbae..4b73ad6 100644 --- a/jellyfish/_openapi_client/models/room.py +++ b/jellyfish/_openapi_client/models/room.py @@ -7,6 +7,7 @@ from ..models.component_file import ComponentFile from ..models.component_hls import ComponentHLS from ..models.component_rtsp import ComponentRTSP + from ..models.component_sip import ComponentSIP from ..models.peer import Peer from ..models.room_config import RoomConfig @@ -18,7 +19,9 @@ class Room: """Description of the room state""" - components: List[Union["ComponentFile", "ComponentHLS", "ComponentRTSP"]] + components: List[ + Union["ComponentFile", "ComponentHLS", "ComponentRTSP", "ComponentSIP"] + ] """List of all components""" config: "RoomConfig" """Room configuration""" @@ -31,6 +34,7 @@ class Room: def to_dict(self) -> Dict[str, Any]: """@private""" + from ..models.component_file import ComponentFile from ..models.component_hls import ComponentHLS from ..models.component_rtsp import ComponentRTSP @@ -44,6 +48,9 @@ def to_dict(self) -> Dict[str, Any]: elif isinstance(components_item_data, ComponentRTSP): components_item = components_item_data.to_dict() + elif isinstance(components_item_data, ComponentFile): + components_item = components_item_data.to_dict() + else: components_item = components_item_data.to_dict() @@ -77,6 +84,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: from ..models.component_file import ComponentFile from ..models.component_hls import ComponentHLS from ..models.component_rtsp import ComponentRTSP + from ..models.component_sip import ComponentSIP from ..models.peer import Peer from ..models.room_config import RoomConfig @@ -87,7 +95,9 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: def _parse_components_item( data: object, - ) -> Union["ComponentFile", "ComponentHLS", "ComponentRTSP"]: + ) -> Union[ + "ComponentFile", "ComponentHLS", "ComponentRTSP", "ComponentSIP" + ]: try: if not isinstance(data, dict): raise TypeError() @@ -104,11 +114,19 @@ def _parse_components_item( return componentsschemas_component_type_1 except: # noqa: E722 pass + try: + if not isinstance(data, dict): + raise TypeError() + componentsschemas_component_type_2 = ComponentFile.from_dict(data) + + return componentsschemas_component_type_2 + except: # noqa: E722 + pass if not isinstance(data, dict): raise TypeError() - componentsschemas_component_type_2 = ComponentFile.from_dict(data) + componentsschemas_component_type_3 = ComponentSIP.from_dict(data) - return componentsschemas_component_type_2 + return componentsschemas_component_type_3 components_item = _parse_components_item(components_item_data) diff --git a/jellyfish/_openapi_client/models/room_config.py b/jellyfish/_openapi_client/models/room_config.py index 8a940ec..6f0985e 100644 --- a/jellyfish/_openapi_client/models/room_config.py +++ b/jellyfish/_openapi_client/models/room_config.py @@ -15,6 +15,8 @@ class RoomConfig: max_peers: Union[Unset, None, int] = UNSET """Maximum amount of peers allowed into the room""" + peerless_purge_timeout: Union[Unset, None, int] = UNSET + """Duration (in seconds) after which the room will be removed if no peers are connected. If not provided, this feature is disabled.""" room_id: Union[Unset, None, str] = UNSET """Custom id used for identifying room within Jellyfish. Must be unique across all rooms. If not provided, random UUID is generated.""" video_codec: Union[Unset, None, RoomConfigVideoCodec] = UNSET @@ -27,6 +29,7 @@ class RoomConfig: def to_dict(self) -> Dict[str, Any]: """@private""" max_peers = self.max_peers + peerless_purge_timeout = self.peerless_purge_timeout room_id = self.room_id video_codec: Union[Unset, None, str] = UNSET if not isinstance(self.video_codec, Unset): @@ -39,6 +42,8 @@ def to_dict(self) -> Dict[str, Any]: field_dict.update({}) if max_peers is not UNSET: field_dict["maxPeers"] = max_peers + if peerless_purge_timeout is not UNSET: + field_dict["peerlessPurgeTimeout"] = peerless_purge_timeout if room_id is not UNSET: field_dict["roomId"] = room_id if video_codec is not UNSET: @@ -54,6 +59,8 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() max_peers = d.pop("maxPeers", UNSET) + peerless_purge_timeout = d.pop("peerlessPurgeTimeout", UNSET) + room_id = d.pop("roomId", UNSET) _video_codec = d.pop("videoCodec", UNSET) @@ -69,6 +76,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: room_config = cls( max_peers=max_peers, + peerless_purge_timeout=peerless_purge_timeout, room_id=room_id, video_codec=video_codec, webhook_url=webhook_url, diff --git a/jellyfish/_openapi_client/models/sip_credentials.py b/jellyfish/_openapi_client/models/sip_credentials.py new file mode 100644 index 0000000..9b8678c --- /dev/null +++ b/jellyfish/_openapi_client/models/sip_credentials.py @@ -0,0 +1,74 @@ +from typing import Any, Dict, List, Type, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="SIPCredentials") + + +@_attrs_define +class SIPCredentials: + """Credentials used to authorize in SIP Provider service""" + + address: str + """SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed""" + password: str + """Password in SIP service provider""" + username: str + """Username in SIP service provider""" + additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) + """@private""" + + def to_dict(self) -> Dict[str, Any]: + """@private""" + address = self.address + password = self.password + username = self.username + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "address": address, + "password": password, + "username": username, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + """@private""" + d = src_dict.copy() + address = d.pop("address") + + password = d.pop("password") + + username = d.pop("username") + + sip_credentials = cls( + address=address, + password=password, + username=username, + ) + + sip_credentials.additional_properties = d + return sip_credentials + + @property + def additional_keys(self) -> List[str]: + """@private""" + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/jellyfish/api/_room_api.py b/jellyfish/api/_room_api.py index 9ab1b72..4f4f4fb 100644 --- a/jellyfish/api/_room_api.py +++ b/jellyfish/api/_room_api.py @@ -13,6 +13,8 @@ from jellyfish._openapi_client.api.room import delete_room as room_delete_room from jellyfish._openapi_client.api.room import get_all_rooms as room_get_all_rooms from jellyfish._openapi_client.api.room import get_room as room_get_room +from jellyfish._openapi_client.api.sip import dial as sip_dial +from jellyfish._openapi_client.api.sip import end_call as sip_end_call from jellyfish._openapi_client.models import ( AddComponentJsonBody, AddPeerJsonBody, @@ -21,7 +23,10 @@ ComponentOptionsFile, ComponentOptionsHLS, ComponentOptionsRTSP, + ComponentOptionsSIP, ComponentRTSP, + ComponentSIP, + DialConfig, Peer, PeerOptionsWebRTC, Room, @@ -123,8 +128,13 @@ def delete_peer(self, room_id: str, peer_id: str) -> None: def add_component( self, room_id: str, - options: Union[ComponentOptionsFile, ComponentOptionsHLS, ComponentOptionsRTSP], - ) -> Union[ComponentFile, ComponentHLS, ComponentRTSP]: + options: Union[ + ComponentOptionsFile, + ComponentOptionsHLS, + ComponentOptionsRTSP, + ComponentOptionsSIP, + ], + ) -> Union[ComponentFile, ComponentHLS, ComponentRTSP, ComponentSIP]: """Creates component in the room""" if isinstance(options, ComponentOptionsFile): @@ -133,10 +143,12 @@ def add_component( component_type = "hls" elif isinstance(options, ComponentOptionsRTSP): component_type = "rtsp" + elif isinstance(options, ComponentOptionsSIP): + component_type = "sip" else: raise ValueError( - "options must be ComponentFile, ComponentOptionsHLS" - "or ComponentOptionsRTSP" + "options must be ComponentFile, ComponentOptionsHLS," + "ComponentOptionsRTSP or ComponentOptionsSIP" ) json_body = AddComponentJsonBody(type=component_type, options=options) @@ -166,3 +178,32 @@ def hls_subscribe(self, room_id: str, origins: [str]): room_id=room_id, json_body=SubscriptionConfig(origins=origins), ) + + def sip_dial(self, room_id: str, component_id: str, phone_number: str): + """ + Starts a phone call from a specified component to a provided phone number. + + This is asynchronous operation. + In case of providing incorrect phone number you will receive + notification `ComponentCrashed`. + """ + + return self._request( + sip_dial, + room_id=room_id, + component_id=component_id, + json_body=DialConfig(phone_number=phone_number), + ) + + def sip_end_call(self, room_id: str, component_id: str): + """ + End a phone call on a specified SIP component. + + This is asynchronous operation. + """ + + return self._request( + sip_end_call, + room_id=room_id, + component_id=component_id, + ) diff --git a/tests/test_room_api.py b/tests/test_room_api.py index 21ff8df..0bb4cff 100644 --- a/tests/test_room_api.py +++ b/tests/test_room_api.py @@ -14,11 +14,15 @@ ComponentOptionsHLS, ComponentOptionsHLSSubscribeMode, ComponentOptionsRTSP, + ComponentOptionsSIP, ComponentPropertiesFile, ComponentPropertiesHLS, ComponentPropertiesHLSSubscribeMode, ComponentPropertiesRTSP, + ComponentPropertiesSIP, + ComponentPropertiesSIPSIPCredentials, ComponentRTSP, + ComponentSIP, Peer, PeerOptionsWebRTC, PeerStatus, @@ -26,6 +30,7 @@ RoomApi, RoomConfig, RoomConfigVideoCodec, + SIPCredentials, ) from jellyfish.errors import ( BadRequestError, @@ -62,6 +67,20 @@ pierce_nat=True, ) +SIP_PHONE_NUMBER = "1234" + +SIP_CREDENTIALS = SIPCredentials( + address="my-sip-registrar.net", username="user-name", password="pass-word" +) + +SIP_OPTIONS = ComponentOptionsSIP(registrar_credentials=SIP_CREDENTIALS) + +SIP_PROPERTIES = ComponentPropertiesSIP( + registrar_credentials=ComponentPropertiesSIPSIPCredentials( + address="my-sip-registrar.net", username="user-name", password="pass-word" + ) +) + FILE_OPTIONS = ComponentOptionsFile(file_path="video.h264") FILE_PROPERTIES = ComponentPropertiesFile( file_path=FILE_OPTIONS.file_path, framerate=30 @@ -108,7 +127,11 @@ def test_no_params(self, room_api): assert room == Room( components=[], config=RoomConfig( - room_id=room.id, max_peers=None, video_codec=None, webhook_url=None + room_id=room.id, + max_peers=None, + video_codec=None, + webhook_url=None, + peerless_purge_timeout=None, ), id=room.id, peers=[], @@ -128,6 +151,7 @@ def test_valid_params(self, room_api): max_peers=MAX_PEERS, video_codec=RoomConfigVideoCodec(CODEC_H264), webhook_url=None, + peerless_purge_timeout=None, ), id=room.id, peers=[], @@ -155,6 +179,7 @@ def test_valid_room_id(self, room_api): max_peers=None, video_codec=None, webhook_url=None, + peerless_purge_timeout=None, ), id=room_id, peers=[], @@ -204,7 +229,11 @@ def test_valid(self, room_api: RoomApi): peers=[], id=room.id, config=RoomConfig( - room_id=room.id, max_peers=None, video_codec=None, webhook_url=None + room_id=room.id, + max_peers=None, + video_codec=None, + webhook_url=None, + peerless_purge_timeout=None, ), ) == room_api.get_room(room.id) @@ -230,6 +259,10 @@ def test_with_options_rtsp(self, room_api): data = ComponentTestData(ComponentRTSP, "rtsp", RTSP_OPTIONS, RTSP_PROPERTIES) self._test_component(room_api, data) + def test_with_options_sip(self, room_api): + data = ComponentTestData(ComponentSIP, "sip", SIP_OPTIONS, SIP_PROPERTIES) + self._test_component(room_api, data) + @pytest.mark.file_component_sources def test_with_options_file(self, room_api): data = ComponentTestData(ComponentFile, "file", FILE_OPTIONS, FILE_PROPERTIES) @@ -296,6 +329,18 @@ def test_invalid_subscription_in_auto_mode(self, room_api: RoomApi): ) +class TestSIPCall: + def test_happy_path(self, room_api: RoomApi): + _, room = room_api.create_room(video_codec=CODEC_H264) + component = room_api.add_component( + room.id, + options=ComponentOptionsSIP(registrar_credentials=SIP_CREDENTIALS), + ) + assert room_api.sip_dial(room.id, component.id, SIP_PHONE_NUMBER) is None + + assert room_api.sip_end_call(room.id, component.id) is None + + class TestAddPeer: def _assert_peer_created(self, room_api, webrtc_peer, room_id): peer = Peer(