Skip to content

Commit

Permalink
Add Millicast APIs, use dataclasses to parse objects
Browse files Browse the repository at this point in the history
  • Loading branch information
FabienLavocat committed Aug 2, 2024
1 parent 69427b6 commit 77583b8
Show file tree
Hide file tree
Showing 18 changed files with 576 additions and 234 deletions.
9 changes: 5 additions & 4 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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()

Expand Down
3 changes: 2 additions & 1 deletion client/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
4 changes: 1 addition & 3 deletions client/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion client/src/dolbyio_rest_apis/core/http_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
68 changes: 68 additions & 0 deletions client/src/dolbyio_rest_apis/streaming/account.py
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 4 additions & 4 deletions client/src/dolbyio_rest_apis/streaming/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
64 changes: 48 additions & 16 deletions client/src/dolbyio_rest_apis/streaming/internal/http_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -123,14 +122,46 @@ 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',
payload=payload,
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,
Expand Down Expand Up @@ -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.
Expand All @@ -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."""

Expand All @@ -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.
Expand Down
25 changes: 25 additions & 0 deletions client/src/dolbyio_rest_apis/streaming/models/account.py
Original file line number Diff line number Diff line change
@@ -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: [])
40 changes: 23 additions & 17 deletions client/src/dolbyio_rest_apis/streaming/models/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Loading

0 comments on commit 77583b8

Please sign in to comment.