diff --git a/client/README.md b/client/README.md index 34d729a..960359e 100644 --- a/client/README.md +++ b/client/README.md @@ -23,12 +23,12 @@ python3 -m pip install --upgrade dolbyio-rest-apis ```python import asyncio from dolbyio_rest_apis.streaming import publish_token -from dolbyio_rest_apis.streaming.models.publish_token import CreatePublishToken, CreateUpdatePublishTokenStream +from dolbyio_rest_apis.streaming.models.publish_token import CreatePublishToken, TokenStreamName API_SECRET = '' # Retrieve your API Secret from the dashboard create_token = CreatePublishToken('my_token') -create_token.streams.append(CreateUpdatePublishTokenStream('feed1', False)) +create_token.streams.append(TokenStreamName('feed1', False)) loop = asyncio.get_event_loop() @@ -43,12 +43,13 @@ print(token) ```python import asyncio from dolbyio_rest_apis.streaming import subscribe_token -from dolbyio_rest_apis.streaming.models.subscribe_token import CreateSubscribeToken, CreateUpdateSubscribeTokenStream +from dolbyio_rest_apis.streaming.models.publish_token import TokenStreamName +from dolbyio_rest_apis.streaming.models.subscribe_token import CreateSubscribeToken API_SECRET = '' # Retrieve your API Secret from the dashboard create_token = CreateSubscribeToken('my_token') -create_token.streams.append(CreateUpdateSubscribeTokenStream('feed1', False)) +create_token.streams.append(TokenStreamName('feed1', False)) loop = asyncio.get_event_loop() diff --git a/client/requirements.txt b/client/requirements.txt index 157acd2..ddeaea1 100644 --- a/client/requirements.txt +++ b/client/requirements.txt @@ -1,4 +1,5 @@ aiohttp>=3.7.4 aiofiles>=0.7.0 aiohttp-retry>=2.4.6 -certifi>=2022.12.7 +certifi>=2024.7.4 +dataclasses-json>=0.6.7 diff --git a/client/setup.py b/client/setup.py index 1aea7bb..78f81ae 100644 --- a/client/setup.py +++ b/client/setup.py @@ -43,10 +43,8 @@ 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3 :: Only', 'Operating System :: OS Independent', 'Intended Audience :: Developers', diff --git a/client/src/dolbyio_rest_apis/core/http_context.py b/client/src/dolbyio_rest_apis/core/http_context.py index b0d5948..17ebff5 100644 --- a/client/src/dolbyio_rest_apis/core/http_context.py +++ b/client/src/dolbyio_rest_apis/core/http_context.py @@ -146,7 +146,7 @@ async def _send_request( params: Mapping[str, str]=None, auth: BasicAuth=None, data: Any=None, - ) -> Any or None: + ) -> Any | None: if params is None: self._logger.debug('%s %s', method, url) else: diff --git a/client/src/dolbyio_rest_apis/streaming/account.py b/client/src/dolbyio_rest_apis/streaming/account.py new file mode 100644 index 0000000..66c6b28 --- /dev/null +++ b/client/src/dolbyio_rest_apis/streaming/account.py @@ -0,0 +1,68 @@ +""" +dolbyio_rest_apis.streaming.account +~~~~~~~~~~~~~~~ + +This module contains the functions to work with the Account APIs. +""" + +from dolbyio_rest_apis.core.helpers import add_if_not_none +from dolbyio_rest_apis.core.urls import get_rts_url +from dolbyio_rest_apis.streaming.internal.http_context import StreamingHttpContext +from dolbyio_rest_apis.streaming.models.account import AccountGeoCascade, AccountGeoRestrictions + +async def read_geo_cascade( + api_secret: str, + ) -> AccountGeoCascade: + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_get( + api_secret=api_secret, + url=f'{get_rts_url()}/api/account/geo_cascade', + ) + + return AccountGeoCascade.from_dict(dict_data) + +async def update_geo_cascade( + api_secret: str, + geo_cascade: AccountGeoCascade, + ) -> AccountGeoCascade: + payload = { + 'isEnabled': geo_cascade.is_enabled, + } + add_if_not_none(payload, 'clusters', geo_cascade.clusters) + + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_put( + api_secret=api_secret, + url=f'{get_rts_url()}/api/account/geo_cascade', + payload=payload, + ) + + return AccountGeoCascade.from_dict(dict_data) + +async def read_geo_restrictions( + api_secret: str, + ) -> AccountGeoRestrictions: + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_get( + api_secret=api_secret, + url=f'{get_rts_url()}/api/geo/account', + ) + + return AccountGeoRestrictions.from_dict(dict_data) + +async def update_geo_restrictions( + api_secret: str, + geo_restrictions: AccountGeoRestrictions, + ) -> AccountGeoRestrictions: + payload = {} + add_if_not_none(payload, 'allowedCountries', geo_restrictions.allowed_countries) + add_if_not_none(payload, 'deniedCountries', geo_restrictions.denied_countries) + + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_post( + api_secret=api_secret, + url=f'{get_rts_url()}/api/geo/account', + payload=payload, + ) + + return AccountGeoRestrictions.from_dict(dict_data) diff --git a/client/src/dolbyio_rest_apis/streaming/cluster.py b/client/src/dolbyio_rest_apis/streaming/cluster.py index 1f855ca..78c6ceb 100644 --- a/client/src/dolbyio_rest_apis/streaming/cluster.py +++ b/client/src/dolbyio_rest_apis/streaming/cluster.py @@ -13,12 +13,12 @@ async def read( api_secret: str, ) -> ClusterResponse: async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/cluster', ) - return ClusterResponse(json_response) + return ClusterResponse.from_dict(dict_data) async def update( api_secret: str, @@ -29,10 +29,10 @@ async def update( } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_put( + dict_data = await http_context.requests_put( api_secret=api_secret, url=f'{get_rts_url()}/api/cluster', payload=payload, ) - return ClusterResponse(json_response) + return ClusterResponse.from_dict(dict_data) diff --git a/client/src/dolbyio_rest_apis/streaming/internal/http_context.py b/client/src/dolbyio_rest_apis/streaming/internal/http_context.py index 62bab54..edf5389 100644 --- a/client/src/dolbyio_rest_apis/streaming/internal/http_context.py +++ b/client/src/dolbyio_rest_apis/streaming/internal/http_context.py @@ -14,14 +14,14 @@ from typing import Any, Mapping class StreamingHttpContext(HttpContext): - """HTTP Context class for Real-time Streaming APIs""" + """HTTP Context class for Dolby Millicast APIs""" def __init__(self): super().__init__() self._logger = logging.getLogger(StreamingHttpContext.__name__) - async def _requests_post_put( + async def _requests_with_payload( self, api_secret: str, url: str, @@ -30,12 +30,12 @@ async def _requests_post_put( params: Mapping[str, str]=None, ) -> dict: r""" - Sends a POST or PUT request. + Sends a request with a payload. Args: api_secret: API secret to use for authentication. url: Where to send the request to. - method: HTTP method, POST or PUT. + method: HTTP method, POST, PUT or PATCH. payload: (Optional) Content of the request. params: (Optional) URL query parameters. @@ -64,8 +64,7 @@ async def _requests_post_put( data=payload, ) - base = BaseResponse(json_response) - return base.data + return BaseResponse.from_dict(json_response).data async def requests_post( self, @@ -91,7 +90,7 @@ async def requests_post( HTTPError: If one occurred. """ - return await self._requests_post_put( + return await self._requests_with_payload( api_secret=api_secret, url=url, method='POST', @@ -123,7 +122,7 @@ async def requests_put( HTTPError: If one occurred. """ - return await self._requests_post_put( + return await self._requests_with_payload( api_secret=api_secret, url=url, method='PUT', @@ -131,6 +130,38 @@ async def requests_put( params=params, ) + async def requests_patch( + self, + api_secret: str, + url: str, + payload: Any=None, + params: Mapping[str, str]=None, + ) -> dict: + r""" + Sends a PATCH request. + + Args: + api_secret: API secret to use for authentication. + url: Where to send the request to. + payload: (Optional) Content of the request. + params: (Optional) URL query parameters. + + Returns: + The JSON response. + + Raises: + HttpRequestError: If a client error one occurred. + HTTPError: If one occurred. + """ + + return await self._requests_with_payload( + api_secret=api_secret, + url=url, + method='PATCH', + payload=payload, + params=params, + ) + async def requests_get( self, api_secret: str, @@ -165,15 +196,14 @@ async def requests_get( headers=headers, ) - base = BaseResponse(json_response) - return base.data + return BaseResponse.from_dict(json_response).data async def requests_delete( self, - access_token: str, + api_secret: str, url: str, params: Mapping[str, str]=None, - ) -> None: + ) -> dict: r""" Sends a DELETE request. @@ -189,16 +219,18 @@ async def requests_delete( headers = { 'Accept': 'application/json', - 'Authorization': f'Bearer {access_token}', + 'Authorization': f'Bearer {api_secret}', } - return await self._send_request( + json_response = await self._send_request( method='DELETE', url=url, params=params, headers=headers, ) + return BaseResponse.from_dict(json_response).data + async def _raise_for_status(self, http_response: ClientResponse): r"""Raises :class:`HttpRequestError` or :class:`ClientResponseError`, if one occurred.""" @@ -212,8 +244,8 @@ async def _raise_for_status(self, http_response: ClientResponse): try: json_response = await http_response.json() - base_response = BaseResponse(json_response) - err = Error(base_response.data) + base_response = BaseResponse.from_dict(json_response) + err = Error.from_dict(base_response.data) raise HttpRequestError(http_response, '', http_response.status, base_response.status, err.message) except (ValueError, ContentTypeError): # If the response body does not contain valid json. diff --git a/client/src/dolbyio_rest_apis/streaming/models/account.py b/client/src/dolbyio_rest_apis/streaming/models/account.py new file mode 100644 index 0000000..058b375 --- /dev/null +++ b/client/src/dolbyio_rest_apis/streaming/models/account.py @@ -0,0 +1,25 @@ +""" +dolbyio_rest_apis.streaming.models.account +~~~~~~~~~~~~~~~ + +This module contains the models used by the Account module. +""" + +from dataclasses import dataclass, field +from dataclasses_json import LetterCase, dataclass_json + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class AccountGeoCascade: + """The :class:`AccountGeoCascade` object, the definition of the geo cascading rules for the account.""" + + is_enabled: bool = False + clusters: list[str] = field(default_factory=lambda: []) + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class AccountGeoRestrictions: + """The :class:`AccountGeoRestrictions` object, the definition of the geo restrictions rules for the account.""" + + allowed_countries: list[str] = field(default_factory=lambda: []) + denied_countries: list[str] = field(default_factory=lambda: []) diff --git a/client/src/dolbyio_rest_apis/streaming/models/cluster.py b/client/src/dolbyio_rest_apis/streaming/models/cluster.py index e04847b..98bf3a1 100644 --- a/client/src/dolbyio_rest_apis/streaming/models/cluster.py +++ b/client/src/dolbyio_rest_apis/streaming/models/cluster.py @@ -2,28 +2,34 @@ dolbyio_rest_apis.streaming.models.cluster ~~~~~~~~~~~~~~~ -This module contains the models used by the Cluster model. +This module contains the models used by the Cluster module. """ -from dolbyio_rest_apis.core.helpers import get_value_or_default +from dataclasses import dataclass +from dataclasses_json import LetterCase, dataclass_json -class Cluster(dict): - """The :class:`Cluster` object, which represents a cluster.""" +@dataclass +class ClusterLocation: + """The :class:`ClusterLocation` object, which represents the location of a cluster.""" + + city: str | None = None + region: str | None = None + country: str | None = None - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) +@dataclass +class Cluster: + """The :class:`Cluster` object, which represents a cluster.""" - self.id = get_value_or_default(self, 'id', None) - self.name = get_value_or_default(self, 'name', None) - self.rtmp = get_value_or_default(self, 'rtmp', None) + id: str + name: str + rtmp: str + srt: str + location: ClusterLocation -class ClusterResponse(dict): +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class ClusterResponse: """The :class:`ClusterResponse` object, which represents a cluster response.""" - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.default_cluster = get_value_or_default(self, 'defaultCluster', None) - self.available_clusters = [] - for cluster in self['availableClusters']: - self.available_clusters.append(Cluster(cluster)) + default_cluster: str + available_clusters: list[Cluster] diff --git a/client/src/dolbyio_rest_apis/streaming/models/core.py b/client/src/dolbyio_rest_apis/streaming/models/core.py index a33c140..1743a42 100644 --- a/client/src/dolbyio_rest_apis/streaming/models/core.py +++ b/client/src/dolbyio_rest_apis/streaming/models/core.py @@ -3,17 +3,16 @@ ~~~~~~~~~~~~~~~ """ -from dolbyio_rest_apis.core.helpers import get_value_or_default +from dataclasses import dataclass +from dataclasses_json import dataclass_json -class BaseResponse(dict): - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) +@dataclass_json +@dataclass +class BaseResponse(): + status: str + data: dict | list[dict] - self.status = get_value_or_default(self, 'status', None) - self.data = get_value_or_default(self, 'data', None) - -class Error(dict): - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.message = get_value_or_default(self, 'message', None) +@dataclass_json +@dataclass +class Error: + message: str diff --git a/client/src/dolbyio_rest_apis/streaming/models/publish_token.py b/client/src/dolbyio_rest_apis/streaming/models/publish_token.py index 242b959..75c51d8 100644 --- a/client/src/dolbyio_rest_apis/streaming/models/publish_token.py +++ b/client/src/dolbyio_rest_apis/streaming/models/publish_token.py @@ -2,115 +2,143 @@ dolbyio_rest_apis.streaming.models.publish_token ~~~~~~~~~~~~~~~ -This module contains the models used by the Publish Token models. +This module contains the models used by the Publish Token module. """ -from typing import List -from dolbyio_rest_apis.core.helpers import get_value_or_default - -class PublishTokenStream(dict): - """The :class:`PublishTokenStream` object, which represents a Publish Token Stream.""" - - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.stream_name = get_value_or_default(self, 'streamName', None) - self.is_regex = get_value_or_default(self, 'isRegex', False) - -class PublishToken(dict): - """The :class:`PublishToken` object, which represents a Publish Token.""" - - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.id = get_value_or_default(self, 'id', None) - self.label = get_value_or_default(self, 'label', None) - self.token = get_value_or_default(self, 'token', None) - self.added_on = get_value_or_default(self, 'addedOn', None) - self.expires_on = get_value_or_default(self, 'expiresOn', None) - self.is_active = get_value_or_default(self, 'isActive', None) - self.streams = [] - for stream in self['streams']: - self.streams.append(PublishTokenStream(stream)) - self.allowed_origins = get_value_or_default(self, 'allowedOrigins', None) - self.allowed_ip_addresses = get_value_or_default(self, 'allowedIpAddresses', None) - self.bind_ips_on_usage = get_value_or_default(self, 'bindIpsOnUsage', None) - self.allowed_countries = get_value_or_default(self, 'allowedCountries', None) - self.denied_countries = get_value_or_default(self, 'deniedCountries', None) - self.origin_cluster = get_value_or_default(self, 'originCluster', None) - self.subscribe_requires_auth = get_value_or_default(self, 'subscribeRequiresAuth', False) - self.record = get_value_or_default(self, 'record', False) - self.multisource = get_value_or_default(self, 'multisource', False) - -class CreateUpdatePublishTokenStream(): - """The :class:`CreateUpdatePublishTokenStream` object, which represents a Publish Token Stream.""" - - def __init__(self, stream_name: str, is_regex: bool): - self.stream_name = stream_name - self.is_regex = is_regex - -class UpdatePublishToken(): +from dataclasses import dataclass, field +from dataclasses_json import LetterCase, dataclass_json + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class TokenGeoCascade: + """The :class:`TokenGeoCascade` object, the definition of the geo cascading rules for a publish token.""" + + is_enabled: bool = False + clusters: list[str] = field(default_factory=lambda: []) + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class TokenStreamName: + """The :class:`TokenStreamName` object, which represents a token stream name.""" + + stream_name: str + is_regex: bool = False + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class PublishTokenRestream: + """The :class:`PublishTokenRestream` object, the definition of a restream endpoint.""" + + url: str + key: str + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class TokenEffectiveSettings: + """The :class:`TokenEffectiveSettings` object, which represents the effective settings for a publish token.""" + + origin_cluster: str | None = None + allowed_countries: list[str] = field(default_factory=lambda: []) + denied_countries: list[str] = field(default_factory=lambda: []) + geo_cascade: TokenGeoCascade | None = None + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class PublishToken: + """The :class:`PublishToken` object, which represents a publish token.""" + + id: int | None = None + label: str | None = None + token: str | None = None + added_on: str | None = None + expires_on: str | None = None + is_active: bool | None = None + streams: list[TokenStreamName] = field(default_factory=lambda: []) + allowed_origins: list[str] = field(default_factory=lambda: []) + allowed_ip_addresses: list[str] = field(default_factory=lambda: []) + bind_ips_on_usage: int | None = None + allowed_countries: list[str] = field(default_factory=lambda: []) + denied_countries: list[str] = field(default_factory=lambda: []) + origin_cluster: str | None = None + subscribe_requires_auth: bool = False + record: bool = False + clip: bool = False + multisource: bool = False + low_latency_rtmp: bool = False + enable_thumbnails: bool = False + display_srt_passphrase: bool = False + srt_passphrase: bool = False + geo_cascade: TokenGeoCascade | None = None + restream: list[PublishTokenRestream] = field(default_factory=lambda: []) + effective_settings: TokenEffectiveSettings | None = None + +class UpdatePublishToken: """The :class:`UpdatePublishToken` object, which represents an Update Publish Token request.""" def __init__(self): self.label: str | None = None self.refresh_token: bool | None = None self.is_active: bool | None = None - self.add_token_streams: List[CreateUpdatePublishTokenStream] | None = None - self.remove_token_streams: List[CreateUpdatePublishTokenStream] | None = None - self.update_allowed_origins: List[str] | None = None - self.update_allowed_ip_addresses: List[str] | None = None + self.add_token_streams: list[TokenStreamName] | None = None + self.remove_token_streams: list[TokenStreamName] | None = None + self.update_allowed_origins: list[str] | None = None + self.update_allowed_ip_addresses: list[str] | None = None self.update_bind_ips_on_usage: int | None = None - self.update_allowed_countries: List[str] | None = None - self.update_denied_countries: List[str] | None = None + self.update_allowed_countries: list[str] | None = None + self.update_denied_countries: list[str] | None = None self.update_origin_cluster: str | None = None self.subscribe_requires_auth: bool | None = None self.record: bool | None = None self.multisource: bool | None = None - -class CreatePublishToken(): + self.enable_thumbnails: bool | None = None + self.display_srt_passphrase: bool | None = None + self.low_latency_rtmp: bool | None = None + self.clip: bool = False + self.update_geo_cascade: TokenGeoCascade | None = None + self.update_restream: list[PublishTokenRestream] | None = None + +class CreatePublishToken: """The :class:`CreatePublishToken` object, which represents a Create Publish Token request.""" def __init__(self, label: str): self.label = label self.expires_on: str | None = None - self.streams: List[CreateUpdatePublishTokenStream] = [] - self.allowed_origins: List[str] | None = None - self.allowed_ip_addresses: List[str] | None = None + self.streams: list[TokenStreamName] = [] + self.allowed_origins: list[str] | None = None + self.allowed_ip_addresses: list[str] | None = None self.bind_ips_on_usage: int | None = None - self.allowed_countries: List[str] | None = None - self.denied_countries: List[str] | None = None + self.allowed_countries: list[str] | None = None + self.denied_countries: list[str] | None = None self.origin_cluster: str | None = None self.subscribe_requires_auth: bool = False self.record: bool = False self.multisource: bool = False - -class ActivePublishToken(dict): + self.enable_thumbnails: bool = False + self.display_srt_passphrase: bool = False + self.low_latency_rtmp: bool = True + self.geo_cascade: TokenGeoCascade | None = None + self.clip: bool = False + self.restream: list[PublishTokenRestream] = [] + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class ActivePublishToken: """The :class:`ActivePublishToken` object.""" - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.token_ids: List[int] = [] - for tid in self['tokenIds']: - self.token_ids.append(tid) + token_ids: list[int] -class FailedToken(dict): +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class FailedToken: """The :class:`FailedToken` object, which represents a Failed Token.""" - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) + token_id: str | None = None + error_message: str | None = None - self.token_id = get_value_or_default(self, 'tokenId', None) - self.error_message = get_value_or_default(self, 'errorMessage', None) - -class DisablePublishTokenResponse(dict): +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class DisablePublishTokenResponse: """The :class:`DisablePublishTokenResponse` object, which represents a the response to the disable publish token.""" - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.successful_tokens = get_value_or_default(self, 'successfulTokens', None) - self.failed_tokens: List[int] = [] - for ft in self['failedTokens']: - self.failed_tokens.append(FailedToken(ft)) + successful_tokens: list[int] + failed_tokens: list[FailedToken] diff --git a/client/src/dolbyio_rest_apis/streaming/models/stream.py b/client/src/dolbyio_rest_apis/streaming/models/stream.py new file mode 100644 index 0000000..9bd2bab --- /dev/null +++ b/client/src/dolbyio_rest_apis/streaming/models/stream.py @@ -0,0 +1,16 @@ +""" +dolbyio_rest_apis.streaming.models.stream +~~~~~~~~~~~~~~~ + +This module contains the models used by the Stream module. +""" + +from dataclasses import dataclass +from dataclasses_json import LetterCase, dataclass_json + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class StreamStoppingLevel: + """The :class:`StreamStoppingLevel` object, which represents the response to stopping a stream.""" + + stopping_level: str diff --git a/client/src/dolbyio_rest_apis/streaming/models/subscribe_token.py b/client/src/dolbyio_rest_apis/streaming/models/subscribe_token.py index d494433..5c3df50 100644 --- a/client/src/dolbyio_rest_apis/streaming/models/subscribe_token.py +++ b/client/src/dolbyio_rest_apis/streaming/models/subscribe_token.py @@ -2,64 +2,47 @@ dolbyio_rest_apis.streaming.models.subscribe_token ~~~~~~~~~~~~~~~ -This module contains the models used by the Subscribe Token models. +This module contains the models used by the Subscribe Token module. """ -from typing import List -from dolbyio_rest_apis.core.helpers import get_value_or_default +from dataclasses import dataclass, field +from dataclasses_json import LetterCase, dataclass_json +from dolbyio_rest_apis.streaming.models.publish_token import TokenEffectiveSettings, TokenStreamName -class SubscribeTokenStream(dict): - """The :class:`SubscribeTokenStream` object, which represents a Subscribe Token Stream.""" - - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.stream_name = get_value_or_default(self, 'streamName', None) - self.is_regex = get_value_or_default(self, 'isRegex', False) - -class SubscribeToken(dict): +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class SubscribeToken: """The :class:`SubscribeToken` object, which represents a Subscribe Token.""" - def __init__(self, dictionary: dict): - dict.__init__(self, dictionary) - - self.id = get_value_or_default(self, 'id', None) - self.label = get_value_or_default(self, 'label', None) - self.token = get_value_or_default(self, 'token', None) - self.added_on = get_value_or_default(self, 'addedOn', None) - self.expires_on = get_value_or_default(self, 'expiresOn', None) - self.is_active = get_value_or_default(self, 'isActive', None) - self.streams = [] - for stream in self['streams']: - self.streams.append(SubscribeTokenStream(stream)) - self.allowed_origins = get_value_or_default(self, 'allowedOrigins', None) - self.allowed_ip_addresses = get_value_or_default(self, 'allowedIpAddresses', None) - self.bind_ips_on_usage = get_value_or_default(self, 'bindIpsOnUsage', None) - self.allowed_countries = get_value_or_default(self, 'allowedCountries', None) - self.denied_countries = get_value_or_default(self, 'deniedCountries', None) - self.origin_cluster = get_value_or_default(self, 'originCluster', None) - -class CreateUpdateSubscribeTokenStream(): - """The :class:`UpdateSubscribeTokenStream` object, which represents an update Subscribe Token Stream.""" - - def __init__(self, stream_name: str, is_regex: bool): - self.stream_name = stream_name - self.is_regex = is_regex - -class UpdateSubscribeToken(): + id: int | None = None + label: str | None = None + token: str | None = None + added_on: str | None = None + expires_on: str | None = None + is_active: bool | None = None + streams: list[TokenStreamName] = field(default_factory=lambda: []) + allowed_origins: list[str] = field(default_factory=lambda: []) + allowed_ip_addresses: list[str] = field(default_factory=lambda: []) + bind_ips_on_usage: int | None = None + allowed_countries: list[str] = field(default_factory=lambda: []) + denied_countries: list[str] = field(default_factory=lambda: []) + origin_cluster: str | None = None + effective_settings: TokenEffectiveSettings | None = None + +class UpdateSubscribeToken: """The :class:`UpdateSubscribeToken` object, which represents an Update Subscribe Token request.""" def __init__(self): self.label: str | None = None self.refresh_token: bool | None = None self.is_active: bool | None = None - self.add_token_streams: List[CreateUpdateSubscribeTokenStream] | None = None - self.remove_token_streams: List[CreateUpdateSubscribeTokenStream] | None = None - self.update_allowed_origins: List[str] | None = None - self.update_allowed_ip_addresses: List[str] | None = None + self.add_token_streams: list[TokenStreamName] | None = None + self.remove_token_streams: list[TokenStreamName] | None = None + self.update_allowed_origins: list[str] | None = None + self.update_allowed_ip_addresses: list[str] | None = None self.update_bind_ips_on_usage: int | None = None - self.update_allowed_countries: List[str] | None = None - self.update_denied_countries: List[str] | None = None + self.update_allowed_countries: list[str] | None = None + self.update_denied_countries: list[str] | None = None self.update_origin_cluster: str | None = None class CreateSubscribeToken(): @@ -68,10 +51,11 @@ class CreateSubscribeToken(): def __init__(self, label: str): self.label = label self.expires_on: str | None = None - self.streams: List[CreateUpdateSubscribeTokenStream] = [] - self.allowed_origins: List[str] | None = None - self.allowed_ip_addresses: List[str] | None = None + self.streams: list[TokenStreamName] = [] + self.allowed_origins: list[str] | None = None + self.allowed_ip_addresses: list[str] | None = None self.bind_ips_on_usage: int | None = None - self.allowed_countries: List[str] | None = None - self.denied_countries: List[str] | None = None + self.allowed_countries: list[str] | None = None + self.denied_countries: list[str] | None = None self.origin_cluster: str | None = None + self.tracking_id: str | None = None diff --git a/client/src/dolbyio_rest_apis/streaming/models/webhooks.py b/client/src/dolbyio_rest_apis/streaming/models/webhooks.py new file mode 100644 index 0000000..5b6ab77 --- /dev/null +++ b/client/src/dolbyio_rest_apis/streaming/models/webhooks.py @@ -0,0 +1,45 @@ +""" +dolbyio_rest_apis.streaming.models.webhooks +~~~~~~~~~~~~~~~ + +This module contains the models used by the Webhooks module. +""" + +from dataclasses import dataclass +from dataclasses_json import LetterCase, dataclass_json + +@dataclass_json(letter_case=LetterCase.CAMEL) +@dataclass +class Webhook: + """The :class:`Webhook` object, which represents a webhook.""" + + id: int | None = None + url: str | None = None + secret: str | None = None + is_feed_hooks: bool | None = None + is_recording_hooks: bool | None = None + is_thumbnail_hooks: bool | None = None + is_transcoder_hooks: bool | None = None + is_clip_hooks: bool | None = None + +class UpdateWebhook: + """The :class:`UpdateWebhook` object, which represents an Update webhook request.""" + + def __init__(self): + self.url: str | None = None + self.is_feed_hooks: bool | None = None + self.is_recording_hooks: bool | None = None + self.is_thumbnail_hooks: bool | None = None + self.is_transcoder_hooks: bool | None = None + self.is_clip_hooks: bool | None = None + +class CreateWebhook(): + """The :class:`CreateWebhook` object, which represents a Create webhook request.""" + + def __init__(self, url: str, is_feed_hooks: bool, is_recording_hooks: bool): + self.url = url + self.is_feed_hooks = is_feed_hooks + self.is_recording_hooks = is_recording_hooks + self.is_thumbnail_hooks = False + self.is_transcoder_hooks = False + self.is_clip_hooks = False diff --git a/client/src/dolbyio_rest_apis/streaming/publish_token.py b/client/src/dolbyio_rest_apis/streaming/publish_token.py index b31acb6..40f3f07 100644 --- a/client/src/dolbyio_rest_apis/streaming/publish_token.py +++ b/client/src/dolbyio_rest_apis/streaming/publish_token.py @@ -5,7 +5,6 @@ This module contains the functions to work with the Publish Token APIs. """ -from typing import List from dolbyio_rest_apis.core.helpers import add_if_not_none from dolbyio_rest_apis.core.urls import get_rts_url from dolbyio_rest_apis.streaming.internal.http_context import StreamingHttpContext @@ -16,12 +15,12 @@ async def read( token_id: int, ) -> PublishToken: async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token/{token_id}', ) - return PublishToken(json_response) + return PublishToken.from_dict(dict_data) async def delete( api_secret: str, @@ -67,23 +66,40 @@ async def update( add_if_not_none(payload, 'subscribeRequiresAuth', token.subscribe_requires_auth) add_if_not_none(payload, 'record', token.record) add_if_not_none(payload, 'multisource', token.multisource) + add_if_not_none(payload, 'enableThumbnails', token.enable_thumbnails) + add_if_not_none(payload, 'displaySrtPassphrase', token.display_srt_passphrase) + add_if_not_none(payload, 'lowLatencyRtmp', token.low_latency_rtmp) + add_if_not_none(payload, 'clip', token.clip) + if token.update_geo_cascade is not None: + payload['updateGeoCascade'] = { + 'isEnabled': token.update_geo_cascade.is_enabled, + 'clusters': token.update_geo_cascade.clusters, + } + if token.update_restream is not None: + payload['updateRestream'] = [] + for restream in token.update_restream: + restream_obj = { + 'url': restream.url, + 'key': restream.key, + } + payload['updateRestream'].append(restream_obj) async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_put( + dict_data = await http_context.requests_put( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token/{token_id}', payload=payload, ) - return PublishToken(json_response) + return PublishToken.from_dict(dict_data) async def list_tokens( api_secret: str, sort_by: str, page: int, items_on_page: int, - is_descending: bool, - ) -> List[PublishToken]: + is_descending: bool = False, + ) -> list[PublishToken]: params = { 'sortBy': sort_by, 'page': str(page), @@ -92,16 +108,16 @@ async def list_tokens( } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token/list', params=params, ) - publish_tokens = [] - for token in json_response: - publish_tokens.append(PublishToken(token)) - return publish_tokens + tokens = [] + for token in dict_data: + tokens.append(PublishToken.from_dict(token)) + return tokens async def create( api_secret: str, @@ -113,6 +129,11 @@ async def create( 'subscribeRequiresAuth': token.subscribe_requires_auth, 'record': token.record, 'multisource': token.multisource, + 'enableThumbnails': token.enable_thumbnails, + 'displaySrtPassphrase': token.display_srt_passphrase, + 'lowLatencyRtmp': token.low_latency_rtmp, + 'clip': token.clip, + 'restream': [], } add_if_not_none(payload, 'expiresOn', token.expires_on) for stream in token.streams: @@ -127,56 +148,65 @@ async def create( add_if_not_none(payload, 'allowedCountries', token.allowed_countries) add_if_not_none(payload, 'deniedCountries', token.denied_countries) add_if_not_none(payload, 'originCluster', token.origin_cluster) + if not token.geo_cascade is None: + payload['geoCascade'] = { + 'isEnabled': token.geo_cascade.is_enabled, + 'clusters': token.geo_cascade.clusters, + } + for restream in token.restream: + payload['restream'] = { + 'url': restream.url, + 'key': restream.key, + } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_post( + dict_data = await http_context.requests_post( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token', payload=payload, ) - return PublishToken(json_response) + return PublishToken.from_dict(dict_data) async def get_active_publish_token_id( api_secret: str, - account_id: str, - stream_name: str, + stream_id: str, ) -> ActivePublishToken: params = { - 'streamId': f'{account_id}/{stream_name}', + 'streamId': stream_id, } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token/active', params=params, ) - return ActivePublishToken(json_response) + return ActivePublishToken.from_dict(dict_data) async def get_all_active_publish_token_id( api_secret: str, ) -> ActivePublishToken: async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token/active/all', ) - return ActivePublishToken(json_response) + return ActivePublishToken.from_dict(dict_data) async def disable( api_secret: str, - token_ids: List[int], + token_ids: list[int], ) -> DisablePublishTokenResponse: payload = { 'tokenIds': token_ids, } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_patch( + dict_data = await http_context.requests_patch( api_secret=api_secret, url=f'{get_rts_url()}/api/publish_token/disable', payload=payload, ) - return DisablePublishTokenResponse(json_response) + return DisablePublishTokenResponse.from_dict(dict_data) diff --git a/client/src/dolbyio_rest_apis/streaming/stream.py b/client/src/dolbyio_rest_apis/streaming/stream.py index 6ba6061..075da4d 100644 --- a/client/src/dolbyio_rest_apis/streaming/stream.py +++ b/client/src/dolbyio_rest_apis/streaming/stream.py @@ -7,28 +7,32 @@ from dolbyio_rest_apis.core.urls import get_rts_url from dolbyio_rest_apis.streaming.internal.http_context import StreamingHttpContext +from dolbyio_rest_apis.streaming.models.stream import StreamStoppingLevel async def stop( api_secret: str, - account_id: str, - stream_name: str, + stream_id: str, ) -> None: payload = { - 'streamId': f'{account_id}/{stream_name}', + 'streamId': stream_id, } async with StreamingHttpContext() as http_context: - await http_context.requests_post( + dict_data = await http_context.requests_post( api_secret=api_secret, url=f'{get_rts_url()}/api/stream/stop', payload=payload, ) + return StreamStoppingLevel.from_dict(dict_data) + async def stop_all( api_secret: str, ) -> None: async with StreamingHttpContext() as http_context: - await http_context.requests_post( + dict_data = await http_context.requests_post( api_secret=api_secret, url=f'{get_rts_url()}/api/stream/stop/all', ) + + return StreamStoppingLevel.from_dict(dict_data) diff --git a/client/src/dolbyio_rest_apis/streaming/subscribe_token.py b/client/src/dolbyio_rest_apis/streaming/subscribe_token.py index 55bb4c1..dd51e17 100644 --- a/client/src/dolbyio_rest_apis/streaming/subscribe_token.py +++ b/client/src/dolbyio_rest_apis/streaming/subscribe_token.py @@ -5,7 +5,6 @@ This module contains the functions to work with the Subscribe Token APIs. """ -from typing import List from dolbyio_rest_apis.core.helpers import add_if_not_none from dolbyio_rest_apis.core.urls import get_rts_url from dolbyio_rest_apis.streaming.internal.http_context import StreamingHttpContext @@ -16,12 +15,12 @@ async def read( token_id: int, ) -> SubscribeToken: async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/subscribe_token/{token_id}', ) - return SubscribeToken(json_response) + return SubscribeToken.from_dict(dict_data) async def delete( api_secret: str, @@ -64,22 +63,23 @@ async def update( add_if_not_none(payload, 'updateAllowedCountries', token.update_allowed_countries) add_if_not_none(payload, 'updateDeniedCountries', token.update_denied_countries) add_if_not_none(payload, 'updateOriginCluster', token.update_origin_cluster) + async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_put( + dict_data = await http_context.requests_put( api_secret=api_secret, url=f'{get_rts_url()}/api/subscribe_token/{token_id}', payload=payload, ) - return SubscribeToken(json_response) + return SubscribeToken.from_dict(dict_data) async def list_tokens( api_secret: str, sort_by: str, page: int, items_on_page: int, - is_descending: bool, - ) -> List[SubscribeToken]: + is_descending: bool = False, + ) -> list[SubscribeToken]: params = { 'sortBy': sort_by, 'page': str(page), @@ -88,16 +88,16 @@ async def list_tokens( } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_get( + dict_data = await http_context.requests_get( api_secret=api_secret, url=f'{get_rts_url()}/api/subscribe_token/list', params=params, ) - publish_tokens = [] - for token in json_response: - publish_tokens.append(SubscribeToken(token)) - return publish_tokens + tokens = [] + for token in dict_data: + tokens.append(SubscribeToken.from_dict(token)) + return tokens async def create( api_secret: str, @@ -120,12 +120,16 @@ async def create( add_if_not_none(payload, 'allowedCountries', token.allowed_countries) add_if_not_none(payload, 'deniedCountries', token.denied_countries) add_if_not_none(payload, 'originCluster', token.origin_cluster) + if not token.tracking_id is None: + payload['tracking'] = { + 'trackingId': token.tracking_id, + } async with StreamingHttpContext() as http_context: - json_response = await http_context.requests_post( + dict_data = await http_context.requests_post( api_secret=api_secret, url=f'{get_rts_url()}/api/subscribe_token', - payload=token, + payload=payload, ) - return SubscribeToken(json_response) + return SubscribeToken.from_dict(dict_data) diff --git a/client/src/dolbyio_rest_apis/streaming/webhooks.py b/client/src/dolbyio_rest_apis/streaming/webhooks.py new file mode 100644 index 0000000..13125f3 --- /dev/null +++ b/client/src/dolbyio_rest_apis/streaming/webhooks.py @@ -0,0 +1,101 @@ +""" +dolbyio_rest_apis.streaming.webhooks +~~~~~~~~~~~~~~~ + +This module contains the functions to work with the Webhooks APIs. +""" + +from dolbyio_rest_apis.core.helpers import add_if_not_none +from dolbyio_rest_apis.core.urls import get_rts_url +from dolbyio_rest_apis.streaming.internal.http_context import StreamingHttpContext +from dolbyio_rest_apis.streaming.models.webhooks import CreateWebhook, UpdateWebhook, Webhook + +async def read( + api_secret: str, + webhook_id: int, + ) -> Webhook: + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_get( + api_secret=api_secret, + url=f'{get_rts_url()}/api/webhooks/{webhook_id}', + ) + + return Webhook.from_dict(dict_data) + +async def delete( + api_secret: str, + webhook_id: int, + ) -> None: + async with StreamingHttpContext() as http_context: + await http_context.requests_delete( + api_secret=api_secret, + url=f'{get_rts_url()}/api/webhooks/{webhook_id}', + ) + +async def update( + api_secret: str, + webhook_id: int, + webhook: UpdateWebhook, + ) -> Webhook: + payload = {} + add_if_not_none(payload, 'url', webhook.url) + add_if_not_none(payload, 'isFeedHooks', webhook.is_feed_hooks) + add_if_not_none(payload, 'isRecordingHooks', webhook.is_recording_hooks) + add_if_not_none(payload, 'isThumbnailHooks', webhook.is_thumbnail_hooks) + add_if_not_none(payload, 'isTranscoderHooks', webhook.is_transcoder_hooks) + add_if_not_none(payload, 'isClipHooks', webhook.is_clip_hooks) + + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_put( + api_secret=api_secret, + url=f'{get_rts_url()}/api/webhooks/{webhook_id}', + payload=payload, + ) + + return Webhook.from_dict(dict_data) + +async def list_webhooks( + api_secret: str, + starting_id: int, + item_count: int = 10, + is_descending: bool = False, + ) -> list[Webhook]: + params = { + 'startingId': str(starting_id), + 'item_count': str(item_count), + 'isDescending': str(is_descending), + } + + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_get( + api_secret=api_secret, + url=f'{get_rts_url()}/api/webhooks/list', + params=params, + ) + + webhooks = [] + for webhook in dict_data: + webhooks.append(Webhook.from_dict(webhook)) + return webhooks + +async def create( + api_secret: str, + webhook: CreateWebhook, + ) -> Webhook: + payload = { + 'url': webhook.url, + 'isFeedHooks': webhook.is_feed_hooks, + 'isRecordingHooks': webhook.is_recording_hooks, + } + add_if_not_none(payload, 'isThumbnailHooks', webhook.is_thumbnail_hooks) + add_if_not_none(payload, 'isTranscoderHooks', webhook.is_transcoder_hooks) + add_if_not_none(payload, 'isClipHooks', webhook.is_clip_hooks) + + async with StreamingHttpContext() as http_context: + dict_data = await http_context.requests_post( + api_secret=api_secret, + url=f'{get_rts_url()}/api/webhooks', + payload=payload, + ) + + return Webhook.from_dict(dict_data)