diff --git a/jellyfish/__init__.py b/jellyfish/__init__.py index 30b31d6..2bc93fd 100644 --- a/jellyfish/__init__.py +++ b/jellyfish/__init__.py @@ -8,6 +8,7 @@ # API from jellyfish._room_api import RoomApi +from jellyfish._recording_api import RecordingApi from jellyfish._ws_notifier import Notifier from jellyfish._webhook_notifier import receive_json @@ -38,6 +39,7 @@ __all__ = [ "RoomApi", + "RecordingApi", "Notifier", "receive_json", "Room", diff --git a/jellyfish/_openapi_client/__init__.py b/jellyfish/_openapi_client/__init__.py index f450a36..34855c8 100644 --- a/jellyfish/_openapi_client/__init__.py +++ b/jellyfish/_openapi_client/__init__.py @@ -16,6 +16,7 @@ # import apis into sdk package from jellyfish._openapi_client.api.hls_api import HlsApi +from jellyfish._openapi_client.api.recording_api import RecordingApi from jellyfish._openapi_client.api.room_api import RoomApi # import ApiClient @@ -55,6 +56,9 @@ from jellyfish._openapi_client.models.peer_options import PeerOptions from jellyfish._openapi_client.models.peer_options_web_rtc import PeerOptionsWebRTC from jellyfish._openapi_client.models.peer_status import PeerStatus +from jellyfish._openapi_client.models.recording_list_response import ( + RecordingListResponse, +) from jellyfish._openapi_client.models.room import Room from jellyfish._openapi_client.models.room_config import RoomConfig from jellyfish._openapi_client.models.room_create_details_response import ( diff --git a/jellyfish/_openapi_client/api/__init__.py b/jellyfish/_openapi_client/api/__init__.py index 480f08c..4d0419d 100644 --- a/jellyfish/_openapi_client/api/__init__.py +++ b/jellyfish/_openapi_client/api/__init__.py @@ -2,4 +2,5 @@ # import apis into api package from jellyfish._openapi_client.api.hls_api import HlsApi +from jellyfish._openapi_client.api.recording_api import RecordingApi from jellyfish._openapi_client.api.room_api import RoomApi diff --git a/jellyfish/_openapi_client/api/recording_api.py b/jellyfish/_openapi_client/api/recording_api.py new file mode 100644 index 0000000..bfa91a9 --- /dev/null +++ b/jellyfish/_openapi_client/api/recording_api.py @@ -0,0 +1,484 @@ +# coding: utf-8 + +""" + Python API wrapper for Jellyfish Media Server + + The version of the OpenAPI document: 0.2.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import re # noqa: F401 +import io +import warnings + +from pydantic import validate_arguments, ValidationError +from typing_extensions import Annotated + +from pydantic import Field, StrictStr + +from jellyfish._openapi_client.models.recording_list_response import ( + RecordingListResponse, +) + +from jellyfish._openapi_client.api_client import ApiClient +from jellyfish._openapi_client.api_response import ApiResponse +from jellyfish._openapi_client.exceptions import ( # noqa: F401 + ApiTypeError, + ApiValueError, +) + + +class RecordingApi(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + @validate_arguments + def delete_recording( + self, + recording_id: Annotated[StrictStr, Field(..., description="Recording id")], + **kwargs + ) -> None: # noqa: E501 + """Deletes the recording # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.delete_recording(recording_id, async_req=True) + >>> result = thread.get() + + :param recording_id: Recording id (required) + :type recording_id: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + kwargs["_return_http_data_only"] = True + if "_preload_content" in kwargs: + raise ValueError( + "Error! Please call the delete_recording_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data" + ) + return self.delete_recording_with_http_info( + recording_id, **kwargs + ) # noqa: E501 + + @validate_arguments + def delete_recording_with_http_info( + self, + recording_id: Annotated[StrictStr, Field(..., description="Recording id")], + **kwargs + ) -> ApiResponse: # noqa: E501 + """Deletes the recording # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.delete_recording_with_http_info(recording_id, async_req=True) + >>> result = thread.get() + + :param recording_id: Recording id (required) + :type recording_id: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the ApiResponse.data will + be set to none and raw_data will store the + HTTP response body without reading/decoding. + Default is True. + :type _preload_content: bool, optional + :param _return_http_data_only: response data instead of ApiResponse + object with status code, headers, etc + :type _return_http_data_only: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: None + """ + + _params = locals() + + _all_params = ["recording_id"] + _all_params.extend( + [ + "async_req", + "_return_http_data_only", + "_preload_content", + "_request_timeout", + "_request_auth", + "_content_type", + "_headers", + ] + ) + + # validate the arguments + for _key, _val in _params["kwargs"].items(): + if _key not in _all_params: + raise ApiTypeError( + "Got an unexpected keyword argument '%s'" + " to method delete_recording" % _key + ) + _params[_key] = _val + del _params["kwargs"] + + _collection_formats = {} + + # process the path parameters + _path_params = {} + if _params["recording_id"]: + _path_params["recording_id"] = _params["recording_id"] + + # process the query parameters + _query_params = [] + # process the header parameters + _header_params = dict(_params.get("_headers", {})) + # process the form parameters + _form_params = [] + _files = {} + # process the body parameter + _body_params = None + # set the HTTP header `Accept` + _header_params["Accept"] = self.api_client.select_header_accept( + ["application/json"] + ) # noqa: E501 + + # authentication setting + _auth_settings = ["authorization"] # noqa: E501 + + _response_types_map = {} + + return self.api_client.call_api( + "/recording/{recording_id}", + "DELETE", + _path_params, + _query_params, + _header_params, + body=_body_params, + post_params=_form_params, + files=_files, + response_types_map=_response_types_map, + auth_settings=_auth_settings, + async_req=_params.get("async_req"), + _return_http_data_only=_params.get("_return_http_data_only"), # noqa: E501 + _preload_content=_params.get("_preload_content", True), + _request_timeout=_params.get("_request_timeout"), + collection_formats=_collection_formats, + _request_auth=_params.get("_request_auth"), + ) + + @validate_arguments + def get_recording_content( + self, + recording_id: Annotated[StrictStr, Field(..., description="Recording id")], + filename: Annotated[StrictStr, Field(..., description="Name of the file")], + **kwargs + ) -> str: # noqa: E501 + """Retrieve Recording (HLS) Content # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.get_recording_content(recording_id, filename, async_req=True) + >>> result = thread.get() + + :param recording_id: Recording id (required) + :type recording_id: str + :param filename: Name of the file (required) + :type filename: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: str + """ + kwargs["_return_http_data_only"] = True + if "_preload_content" in kwargs: + raise ValueError( + "Error! Please call the get_recording_content_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data" + ) + return self.get_recording_content_with_http_info( + recording_id, filename, **kwargs + ) # noqa: E501 + + @validate_arguments + def get_recording_content_with_http_info( + self, + recording_id: Annotated[StrictStr, Field(..., description="Recording id")], + filename: Annotated[StrictStr, Field(..., description="Name of the file")], + **kwargs + ) -> ApiResponse: # noqa: E501 + """Retrieve Recording (HLS) Content # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.get_recording_content_with_http_info(recording_id, filename, async_req=True) + >>> result = thread.get() + + :param recording_id: Recording id (required) + :type recording_id: str + :param filename: Name of the file (required) + :type filename: str + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the ApiResponse.data will + be set to none and raw_data will store the + HTTP response body without reading/decoding. + Default is True. + :type _preload_content: bool, optional + :param _return_http_data_only: response data instead of ApiResponse + object with status code, headers, etc + :type _return_http_data_only: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(str, status_code(int), headers(HTTPHeaderDict)) + """ + + _params = locals() + + _all_params = ["recording_id", "filename"] + _all_params.extend( + [ + "async_req", + "_return_http_data_only", + "_preload_content", + "_request_timeout", + "_request_auth", + "_content_type", + "_headers", + ] + ) + + # validate the arguments + for _key, _val in _params["kwargs"].items(): + if _key not in _all_params: + raise ApiTypeError( + "Got an unexpected keyword argument '%s'" + " to method get_recording_content" % _key + ) + _params[_key] = _val + del _params["kwargs"] + + _collection_formats = {} + + # process the path parameters + _path_params = {} + if _params["recording_id"]: + _path_params["recording_id"] = _params["recording_id"] + + if _params["filename"]: + _path_params["filename"] = _params["filename"] + + # process the query parameters + _query_params = [] + # process the header parameters + _header_params = dict(_params.get("_headers", {})) + # process the form parameters + _form_params = [] + _files = {} + # process the body parameter + _body_params = None + # set the HTTP header `Accept` + _header_params["Accept"] = self.api_client.select_header_accept( + ["application/json"] + ) # noqa: E501 + + # authentication setting + _auth_settings = ["authorization"] # noqa: E501 + + _response_types_map = { + "200": "str", + "404": "Error", + } + + return self.api_client.call_api( + "/recording/{recording_id}/{filename}", + "GET", + _path_params, + _query_params, + _header_params, + body=_body_params, + post_params=_form_params, + files=_files, + response_types_map=_response_types_map, + auth_settings=_auth_settings, + async_req=_params.get("async_req"), + _return_http_data_only=_params.get("_return_http_data_only"), # noqa: E501 + _preload_content=_params.get("_preload_content", True), + _request_timeout=_params.get("_request_timeout"), + collection_formats=_collection_formats, + _request_auth=_params.get("_request_auth"), + ) + + @validate_arguments + def get_recordings(self, **kwargs) -> RecordingListResponse: # noqa: E501 + """Lists all available recordings # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.get_recordings(async_req=True) + >>> result = thread.get() + + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: RecordingListResponse + """ + kwargs["_return_http_data_only"] = True + if "_preload_content" in kwargs: + raise ValueError( + "Error! Please call the get_recordings_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data" + ) + return self.get_recordings_with_http_info(**kwargs) # noqa: E501 + + @validate_arguments + def get_recordings_with_http_info(self, **kwargs) -> ApiResponse: # noqa: E501 + """Lists all available recordings # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.get_recordings_with_http_info(async_req=True) + >>> result = thread.get() + + :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the ApiResponse.data will + be set to none and raw_data will store the + HTTP response body without reading/decoding. + Default is True. + :type _preload_content: bool, optional + :param _return_http_data_only: response data instead of ApiResponse + object with status code, headers, etc + :type _return_http_data_only: bool, optional + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: tuple(RecordingListResponse, status_code(int), headers(HTTPHeaderDict)) + """ + + _params = locals() + + _all_params = [] + _all_params.extend( + [ + "async_req", + "_return_http_data_only", + "_preload_content", + "_request_timeout", + "_request_auth", + "_content_type", + "_headers", + ] + ) + + # validate the arguments + for _key, _val in _params["kwargs"].items(): + if _key not in _all_params: + raise ApiTypeError( + "Got an unexpected keyword argument '%s'" + " to method get_recordings" % _key + ) + _params[_key] = _val + del _params["kwargs"] + + _collection_formats = {} + + # process the path parameters + _path_params = {} + + # process the query parameters + _query_params = [] + # process the header parameters + _header_params = dict(_params.get("_headers", {})) + # process the form parameters + _form_params = [] + _files = {} + # process the body parameter + _body_params = None + # set the HTTP header `Accept` + _header_params["Accept"] = self.api_client.select_header_accept( + ["application/json"] + ) # noqa: E501 + + # authentication setting + _auth_settings = ["authorization"] # noqa: E501 + + _response_types_map = { + "200": "RecordingListResponse", + "404": "Error", + } + + return self.api_client.call_api( + "/recording", + "GET", + _path_params, + _query_params, + _header_params, + body=_body_params, + post_params=_form_params, + files=_files, + response_types_map=_response_types_map, + auth_settings=_auth_settings, + async_req=_params.get("async_req"), + _return_http_data_only=_params.get("_return_http_data_only"), # noqa: E501 + _preload_content=_params.get("_preload_content", True), + _request_timeout=_params.get("_request_timeout"), + collection_formats=_collection_formats, + _request_auth=_params.get("_request_auth"), + ) diff --git a/jellyfish/_openapi_client/models/__init__.py b/jellyfish/_openapi_client/models/__init__.py index 91a7a66..dc1acea 100644 --- a/jellyfish/_openapi_client/models/__init__.py +++ b/jellyfish/_openapi_client/models/__init__.py @@ -37,6 +37,9 @@ from jellyfish._openapi_client.models.peer_options import PeerOptions from jellyfish._openapi_client.models.peer_options_web_rtc import PeerOptionsWebRTC from jellyfish._openapi_client.models.peer_status import PeerStatus +from jellyfish._openapi_client.models.recording_list_response import ( + RecordingListResponse, +) from jellyfish._openapi_client.models.room import Room from jellyfish._openapi_client.models.room_config import RoomConfig from jellyfish._openapi_client.models.room_create_details_response import ( diff --git a/jellyfish/_openapi_client/models/recording_list_response.py b/jellyfish/_openapi_client/models/recording_list_response.py new file mode 100644 index 0000000..1378b7e --- /dev/null +++ b/jellyfish/_openapi_client/models/recording_list_response.py @@ -0,0 +1,65 @@ +# coding: utf-8 + +""" + Python API wrapper for Jellyfish Media Server + + The version of the OpenAPI document: 0.2.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + + +from typing import List +from pydantic import BaseModel, Field, StrictStr, conlist + + +class RecordingListResponse(BaseModel): + """ + Response containing list of all recording + """ + + data: conlist(StrictStr) = Field(...) + __properties = ["data"] + + class Config: + """Pydantic configuration""" + + allow_population_by_field_name = True + validate_assignment = True + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> RecordingListResponse: + """Create an instance of RecordingListResponse from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, exclude={}, exclude_none=True) + return _dict + + @classmethod + def from_dict(cls, obj: dict) -> RecordingListResponse: + """Create an instance of RecordingListResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return RecordingListResponse.parse_obj(obj) + + _obj = RecordingListResponse.parse_obj({"data": obj.get("data")}) + return _obj diff --git a/jellyfish/_recording_api.py b/jellyfish/_recording_api.py new file mode 100644 index 0000000..b6fbe0d --- /dev/null +++ b/jellyfish/_recording_api.py @@ -0,0 +1,38 @@ +""" +RecordingApi used to manage rooms +""" + +from jellyfish import _openapi_client as jellyfish_api + + +class RecordingApi: + """Allows for managing recordings""" + + def __init__( + self, + server_address: str = "localhost:5002", + server_api_token: str = "development", + secure: bool = False, + ): + """ + Create RecordingApi instance, providing the jellyfish address and api token. + Set secure to `True` for `https` and `False` for `http` connection (default). + """ + + protocol = "https" if secure else "http" + self._configuration = jellyfish_api.Configuration( + host=f"{protocol}://{server_address}", access_token=server_api_token + ) + + self._api_client = jellyfish_api.ApiClient(self._configuration) + self._recording_api = jellyfish_api.RecordingApi(self._api_client) + + def get_list(self) -> list: + """Returns a list of available recordings""" + + return self._recording_api.get_recordings().data + + def delete(self, recording_id: str): + """Deletes recording with given id""" + + return self._recording_api.delete_recording(recording_id) diff --git a/tests/test_recording_api.py b/tests/test_recording_api.py new file mode 100644 index 0000000..30b1fe4 --- /dev/null +++ b/tests/test_recording_api.py @@ -0,0 +1,32 @@ +# pylint: disable=locally-disabled, missing-class-docstring, missing-function-docstring, redefined-outer-name, too-few-public-methods, missing-module-docstring + +import os + +import pytest + +from jellyfish import RecordingApi +from jellyfish import NotFoundException + + +HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost" +SERVER_ADDRESS = f"{HOST}:5002" +SERVER_API_TOKEN = "development" + + +@pytest.fixture +def recording_api(): + return RecordingApi( + server_address=SERVER_ADDRESS, server_api_token=SERVER_API_TOKEN + ) + + +class TestGetList: + def test_valid(self, recording_api: RecordingApi): + all_rooms = recording_api.get_list() + assert isinstance(all_rooms, list) + + +class TestDelete: + def test_invalid_recording(self, recording_api: RecordingApi): + with pytest.raises(NotFoundException): + recording_api.delete("invalid-id")