diff --git a/http_client/CHANGES.md b/http_client/CHANGES.md index 528d4668..bf6dde61 100644 --- a/http_client/CHANGES.md +++ b/http_client/CHANGES.md @@ -1,3 +1,6 @@ +# 1.3.0 +- Add new PUT method + # 1.2.1 - Expose classes and errors at the package level diff --git a/http_client/pyproject.toml b/http_client/pyproject.toml index 683dc16c..25e3fbe9 100644 --- a/http_client/pyproject.toml +++ b/http_client/pyproject.toml @@ -1,16 +1,16 @@ [project] name = "vonage-http-client" -version = "1.2.1" +version = "1.3.0" description = "An HTTP client for making requests to Vonage APIs." readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-utils>=1.0.1", + "vonage-utils>=1.1.0", "vonage-jwt>=1.1.0", "requests>=2.27.0", + "typing-extensions>=4.9.0", "pydantic>=2.6.1", - "typing_extensions>=4.9.0", ] classifiers = [ "Programming Language :: Python", diff --git a/messages/CHANGES.md b/messages/CHANGES.md index be516a55..2d2c13ed 100644 --- a/messages/CHANGES.md +++ b/messages/CHANGES.md @@ -1,2 +1,5 @@ +# 1.1.0 +- Add `http_client` property + # 1.0.0 - Initial upload diff --git a/messages/pyproject.toml b/messages/pyproject.toml index be11ac64..8d74d007 100644 --- a/messages/pyproject.toml +++ b/messages/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-messages' -version = '1.0.0' +version = '1.1.0' description = 'Vonage messages package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.1", - "vonage-utils>=1.0.1", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/messages/src/vonage_messages/messages.py b/messages/src/vonage_messages/messages.py index e1d32d35..874c9b2c 100644 --- a/messages/src/vonage_messages/messages.py +++ b/messages/src/vonage_messages/messages.py @@ -17,6 +17,15 @@ class Messages: def __init__(self, http_client: HttpClient) -> None: self._http_client = http_client + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Messages API. + + Returns: + HttpClient: The HTTP client used to make requests to the Messages API. + """ + return self._http_client + @validate_call def send(self, message: BaseMessage) -> SendMessageResponse: """Send a message using Vonage's Messages API. diff --git a/messages/tests/test_messages.py b/messages/tests/test_messages.py index a57ca024..3bc2e01a 100644 --- a/messages/tests/test_messages.py +++ b/messages/tests/test_messages.py @@ -75,3 +75,9 @@ def test_send_message_invalid_error(): assert e.value.response.status_code == 422 assert e.value.response.json()['title'] == 'Invalid params' + + +def test_http_client_property(): + http_client = HttpClient(get_mock_jwt_auth()) + messages = Messages(http_client) + assert messages.http_client == http_client diff --git a/number_insight_v2/CHANGES.md b/number_insight_v2/CHANGES.md new file mode 100644 index 00000000..6e846e2d --- /dev/null +++ b/number_insight_v2/CHANGES.md @@ -0,0 +1,2 @@ +# 0.1.0b0 +- Beta release \ No newline at end of file diff --git a/number_insight_v2/pyproject.toml b/number_insight_v2/pyproject.toml index 73f68e4b..e200b817 100644 --- a/number_insight_v2/pyproject.toml +++ b/number_insight_v2/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-number-insight-v2' -version = '0.1.0' +version = '0.1.0b0' description = 'Vonage Number Insight v2 package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.0.0", - "vonage-utils>=1.0.0", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/number_insight_v2/src/vonage_number_insight_v2/number_insight_v2.py b/number_insight_v2/src/vonage_number_insight_v2/number_insight_v2.py index 8836f802..6f6721b2 100644 --- a/number_insight_v2/src/vonage_number_insight_v2/number_insight_v2.py +++ b/number_insight_v2/src/vonage_number_insight_v2/number_insight_v2.py @@ -59,6 +59,15 @@ def __init__(self, http_client: HttpClient) -> None: self._http_client = deepcopy(http_client) self._auth_type = 'basic' + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Number Insight V2 API. + + Returns: + HttpClient: The HTTP client used to make requests to the Number Insight V2 API. + """ + return self._http_client + @validate_call def fraud_check(self, request: FraudCheckRequest) -> FraudCheckResponse: """Initiate a fraud check request.""" diff --git a/number_insight_v2/tests/test_number_insight_v2.py b/number_insight_v2/tests/test_number_insight_v2.py index c63982a0..31a89181 100644 --- a/number_insight_v2/tests/test_number_insight_v2.py +++ b/number_insight_v2/tests/test_number_insight_v2.py @@ -92,3 +92,7 @@ def test_ni2_sim_swap_only(): assert 'fraud_score' not in clear_response assert 'sim_swap' in clear_response assert 'reason' not in clear_response['sim_swap'] + + +def test_number_insight_v2_http_client(): + assert type(ni2.http_client) == HttpClient diff --git a/requirements.txt b/requirements.txt index 458067d5..d0f9aefb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ pytest>=8.0.0 requests>=2.31.0 responses>=0.24.1 pydantic>=2.6.1 -typing_extensions>=4.9.0 +typing-extensions>=4.9.0 vonage-jwt>=1.1.0 \ No newline at end of file diff --git a/sms/CHANGES.md b/sms/CHANGES.md index 336bbca8..d1ba477e 100644 --- a/sms/CHANGES.md +++ b/sms/CHANGES.md @@ -1,3 +1,6 @@ +# 1.1.0 +- Add `http_client` property + # 1.0.2 - Internal refactoring diff --git a/sms/pyproject.toml b/sms/pyproject.toml index 1e20fdac..b2c52642 100644 --- a/sms/pyproject.toml +++ b/sms/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-sms' -version = '1.0.2' +version = '1.1.0' description = 'Vonage SMS package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.1.1", - "vonage-utils>=1.0.0", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/sms/src/vonage_sms/sms.py b/sms/src/vonage_sms/sms.py index be5839ca..b18a434a 100644 --- a/sms/src/vonage_sms/sms.py +++ b/sms/src/vonage_sms/sms.py @@ -19,6 +19,15 @@ def __init__(self, http_client: HttpClient) -> None: else: self._auth_type = 'basic' + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the SMS API. + + Returns: + HttpClient: The HTTP client used to make requests to the SMS API. + """ + return self._http_client + @validate_call def send(self, message: SmsMessage) -> SmsResponse: """Send an SMS message.""" diff --git a/sms/tests/test_sms.py b/sms/tests/test_sms.py index c6cb21ac..520d5c15 100644 --- a/sms/tests/test_sms.py +++ b/sms/tests/test_sms.py @@ -171,3 +171,8 @@ def test_submit_sms_conversion_402(): sms.submit_sms_conversion('3295d748-4e14-4681-af78-166dca3c5aab') except HttpRequestError as err: assert err.message == '402 response from https://api.nexmo.com/conversions/sms.' + + +def test_http_client_property(): + sms = Sms(HttpClient(Auth(api_key='qwerasdf', api_secret='1234qwerasdfzxcv'))) + assert isinstance(sms.http_client, HttpClient) diff --git a/users/CHANGES.md b/users/CHANGES.md index a28aa741..368fd9f2 100644 --- a/users/CHANGES.md +++ b/users/CHANGES.md @@ -1,3 +1,8 @@ +# 1.1.0 +- Add `http_client` property +- Rename `ListUsersRequest` -> `ListUsersFilter` +- Internal refactoring + # 1.0.1 - Internal refactoring diff --git a/users/pyproject.toml b/users/pyproject.toml index 38337222..3a261fe0 100644 --- a/users/pyproject.toml +++ b/users/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-users' -version = '1.0.1' +version = '1.1.0' description = 'Vonage Users package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.0", - "vonage-utils>=1.0.1", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/users/src/vonage_users/users.py b/users/src/vonage_users/users.py index 110dcbb9..3f685ac9 100644 --- a/users/src/vonage_users/users.py +++ b/users/src/vonage_users/users.py @@ -20,6 +20,15 @@ def __init__(self, http_client: HttpClient) -> None: self._http_client = http_client self._auth_type = 'jwt' + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Users API. + + Returns: + HttpClient: The HTTP client used to make requests to the Users API. + """ + return self._http_client + @validate_call def list_users( self, filter: ListUsersFilter = ListUsersFilter() diff --git a/users/tests/test_users.py b/users/tests/test_users.py index 5aec3e71..0112b705 100644 --- a/users/tests/test_users.py +++ b/users/tests/test_users.py @@ -251,3 +251,8 @@ def test_delete_user(): status=204, ) assert users.delete_user('USR-87e3e6b0-cd7b-45ef-a0a7-bcd5566a672b') is None + + +def test_http_client_property(): + http_client = users.http_client + assert isinstance(http_client, HttpClient) diff --git a/verify/CHANGES.md b/verify/CHANGES.md index a28aa741..09439bfb 100644 --- a/verify/CHANGES.md +++ b/verify/CHANGES.md @@ -1,3 +1,6 @@ +# 1.1.0 +- Add `http_client` property + # 1.0.1 - Internal refactoring diff --git a/verify/pyproject.toml b/verify/pyproject.toml index 01ba042d..ba5df186 100644 --- a/verify/pyproject.toml +++ b/verify/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-verify' -version = '1.0.1' +version = '1.1.0' description = 'Vonage verify package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.1", - "vonage-utils>=1.0.1", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/verify/src/vonage_verify/verify.py b/verify/src/vonage_verify/verify.py index b26b4c66..e13e3fdb 100644 --- a/verify/src/vonage_verify/verify.py +++ b/verify/src/vonage_verify/verify.py @@ -26,6 +26,15 @@ def __init__(self, http_client: HttpClient) -> None: self._sent_data_type = 'form' self._auth_type = 'body' + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Verify API. + + Returns: + HttpClient: The HTTP client used to make requests to the Verify API. + """ + return self._http_client + @validate_call def start_verification( self, verify_request: VerifyRequest diff --git a/verify/tests/test_verify.py b/verify/tests/test_verify.py index d8a5af19..80a58ace 100644 --- a/verify/tests/test_verify.py +++ b/verify/tests/test_verify.py @@ -297,3 +297,8 @@ def test_request_network_unblock_error(): == 'The network you provided does not have an active block.' ) assert e.response.json()['title'] == 'Not Found' + + +def test_http_client_property(): + verify = Verify(HttpClient(get_mock_api_key_auth())) + assert isinstance(verify.http_client, HttpClient) diff --git a/verify_v2/CHANGES.md b/verify_v2/CHANGES.md index be516a55..2d2c13ed 100644 --- a/verify_v2/CHANGES.md +++ b/verify_v2/CHANGES.md @@ -1,2 +1,5 @@ +# 1.1.0 +- Add `http_client` property + # 1.0.0 - Initial upload diff --git a/verify_v2/pyproject.toml b/verify_v2/pyproject.toml index 1b4894dc..b4c774ea 100644 --- a/verify_v2/pyproject.toml +++ b/verify_v2/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-verify-v2' -version = '1.0.0' +version = '1.1.0' description = 'Vonage verify v2 package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.1", - "vonage-utils>=1.0.1", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/verify_v2/src/vonage_verify_v2/verify_v2.py b/verify_v2/src/vonage_verify_v2/verify_v2.py index 472033a4..98b56fa6 100644 --- a/verify_v2/src/vonage_verify_v2/verify_v2.py +++ b/verify_v2/src/vonage_verify_v2/verify_v2.py @@ -11,6 +11,15 @@ class VerifyV2: def __init__(self, http_client: HttpClient) -> None: self._http_client = http_client + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Verify V2 API. + + Returns: + HttpClient: The HTTP client used to make requests to the Verify V2 API. + """ + return self._http_client + @validate_call def start_verification( self, verify_request: VerifyRequest diff --git a/verify_v2/tests/test_verify_v2.py b/verify_v2/tests/test_verify_v2.py index 57da7c89..a17a3832 100644 --- a/verify_v2/tests/test_verify_v2.py +++ b/verify_v2/tests/test_verify_v2.py @@ -182,3 +182,8 @@ def test_trigger_next_event_error(): assert ( e.value.response.json()['detail'] == 'There are no more events left to trigger.' ) + + +def test_http_client_property(): + http_client = verify.http_client + assert isinstance(http_client, HttpClient) diff --git a/voice/README.md b/voice/README.md index cf2e611b..dec31846 100644 --- a/voice/README.md +++ b/voice/README.md @@ -2,23 +2,3 @@ This package contains the code to use [Vonage's Voice API](https://developer.vonage.com/en/voice/voice-api/overview) in Python. This package includes methods for working with the Voice API. It also contains an NCCO (Call Control Object) builder to help you to control call flow. -## Usage - -It is recommended to use this as part of the main `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`. - -### Create a Call - - -### Note on URLs - -The Voice API requires most URLs to be passed in a list with only one element. When creating models, simply pass the url and the model will marshal it into the correct structure for you. - -e.g. - -```python -# Don't do this - - -# Do this - -``` \ No newline at end of file diff --git a/voice/pyproject.toml b/voice/pyproject.toml index 1de9acac..3f422d70 100644 --- a/voice/pyproject.toml +++ b/voice/pyproject.toml @@ -1,13 +1,13 @@ [project] name = 'vonage-voice' -version = '1.0.0' +version = '1.0.1' description = 'Vonage voice package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.1", - "vonage-utils>=1.0.1", + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", "pydantic>=2.6.1", ] classifiers = [ diff --git a/voice/src/vonage_voice/__init__.py b/voice/src/vonage_voice/__init__.py index 2df5dea0..b73b81f1 100644 --- a/voice/src/vonage_voice/__init__.py +++ b/voice/src/vonage_voice/__init__.py @@ -1,3 +1,4 @@ +from . import errors, models from .voice import Voice -__all__ = ['Voice'] +__all__ = ['Voice', 'errors', 'models'] diff --git a/voice/src/vonage_voice/models/__init__.py b/voice/src/vonage_voice/models/__init__.py index 5b138b6c..55121a7a 100644 --- a/voice/src/vonage_voice/models/__init__.py +++ b/voice/src/vonage_voice/models/__init__.py @@ -1,4 +1,4 @@ -from .common import AdvancedMachineDetection +from .common import AdvancedMachineDetection, Phone, Sip, Vbc, Websocket from .connect_endpoints import ( AppEndpoint, OnAnswer, @@ -7,39 +7,67 @@ VbcEndpoint, WebsocketEndpoint, ) -from .enums import Channel, ConnectEndpointType, NccoActionType +from .enums import ( + CallState, + Channel, + ConnectEndpointType, + NccoActionType, + TtsLanguageCode, +) from .input_types import Dtmf, Speech from .ncco import Connect, Conversation, Input, NccoAction, Notify, Record, Stream, Talk -from .requests import CreateCallRequest, Phone, Sip, ToPhone, Vbc, Websocket -from .responses import CallStatus, CreateCallResponse +from .requests import ( + AudioStreamOptions, + CreateCallRequest, + ListCallsFilter, + ToPhone, + TtsStreamOptions, +) +from .responses import ( + CallInfo, + CallList, + CallMessage, + CreateCallResponse, + Embedded, + Links, +) __all__ = [ 'AdvancedMachineDetection', + 'AppEndpoint', + 'AudioStreamOptions', + 'CallInfo', + 'CallList', + 'CallMessage', + 'CallState', + 'Channel', + 'Connect', + 'ConnectEndpointType', + 'Conversation', 'CreateCallRequest', - 'ToPhone', - 'Sip', - 'Websocket', - 'Vbc', - 'Phone', + 'CreateCallResponse', + 'Dtmf', + 'Embedded', + 'Input', + 'ListCallsFilter', + 'Links', 'NccoAction', - 'Channel', 'NccoActionType', - 'ConnectEndpointType', + 'Notify', 'OnAnswer', + 'Phone', 'PhoneEndpoint', - 'AppEndpoint', - 'WebsocketEndpoint', + 'Record', + 'Sip', 'SipEndpoint', - 'VbcEndpoint', - 'Dtmf', 'Speech', - 'CreateCallResponse', - 'CallStatus', - 'Record', - 'Conversation', - 'Connect', - 'Talk', 'Stream', - 'Input', - 'Notify', + 'Talk', + 'ToPhone', + 'TtsLanguageCode', + 'TtsStreamOptions', + 'Vbc', + 'VbcEndpoint', + 'Websocket', + 'WebsocketEndpoint', ] diff --git a/voice/src/vonage_voice/models/connect_endpoints.py b/voice/src/vonage_voice/models/connect_endpoints.py index 711cd91a..b5c65e58 100644 --- a/voice/src/vonage_voice/models/connect_endpoints.py +++ b/voice/src/vonage_voice/models/connect_endpoints.py @@ -1,7 +1,6 @@ -from typing import Optional +from typing import Literal, Optional from pydantic import BaseModel, Field -from typing_extensions import Literal from vonage_utils.types import Dtmf, PhoneNumber, SipUri from .enums import ConnectEndpointType diff --git a/voice/src/vonage_voice/models/enums.py b/voice/src/vonage_voice/models/enums.py index a6183664..faa007ca 100644 --- a/voice/src/vonage_voice/models/enums.py +++ b/voice/src/vonage_voice/models/enums.py @@ -38,3 +38,52 @@ class CallState(str, Enum): REJECTED = 'rejected' TIMEOUT = 'timeout' UNANSWERED = 'unanswered' + + +class TtsLanguageCode(str, Enum): + AR = 'ar' + CA_ES = 'ca-ES' + CMN_CN = 'cmn-CN' + CMN_TW = 'cmn-TW' + CS_CZ = 'cs-CZ' + CY_GB = 'cy-GB' + DA_DK = 'da-DK' + DE_DE = 'de-DE' + EL_GR = 'el-GR' + EN_AU = 'en-AU' + EN_GB = 'en-GB' + EN_GB_WLS = 'en-GB-WLS' + EN_IN = 'en-IN' + EN_US = 'en-US' + EN_ZA = 'en-ZA' + ES_ES = 'es-ES' + ES_MX = 'es-MX' + ES_US = 'es-US' + EU_ES = 'eu-ES' + FI_FI = 'fi-FI' + FIL_PH = 'fil-PH' + FR_CA = 'fr-CA' + FR_FR = 'fr-FR' + HE_IL = 'he-IL' + HI_IN = 'hi-IN' + HU_HU = 'hu-HU' + ID_ID = 'id-ID' + IS_IS = 'is-IS' + IT_IT = 'it-IT' + JA_JP = 'ja-JP' + KO_KR = 'ko-KR' + NB_NO = 'nb-NO' + NL_NL = 'nl-NL' + NO_NO = 'no-NO' + PL_PL = 'pl-PL' + PT_BR = 'pt-BR' + PT_PT = 'pt-PT' + RO_RO = 'ro-RO' + RU_RU = 'ru-RU' + SK_SK = 'sk-SK' + SV_SE = 'sv-SE' + TH_TH = 'th-TH' + TR_TR = 'tr-TR' + UK_UA = 'uk-UA' + VI_VN = 'vi-VN' + YUE_CN = 'yue-CN' diff --git a/voice/src/vonage_voice/models/ncco.py b/voice/src/vonage_voice/models/ncco.py index fb2607f3..875e4d0b 100644 --- a/voice/src/vonage_voice/models/ncco.py +++ b/voice/src/vonage_voice/models/ncco.py @@ -1,7 +1,6 @@ -from typing import List, Optional, Union +from typing import List, Literal, Optional, Union from pydantic import BaseModel, Field, model_validator -from typing_extensions import Literal from vonage_utils.types import PhoneNumber from vonage_voice.errors import NccoActionError from vonage_voice.models.common import AdvancedMachineDetection diff --git a/voice/src/vonage_voice/models/requests.py b/voice/src/vonage_voice/models/requests.py index 09a4c0d1..ef8b90f7 100644 --- a/voice/src/vonage_voice/models/requests.py +++ b/voice/src/vonage_voice/models/requests.py @@ -1,12 +1,11 @@ from typing import List, Literal, Optional, Union from pydantic import BaseModel, Field, model_validator - from vonage_utils.types import Dtmf from ..errors import VoiceError from .common import AdvancedMachineDetection, Phone, Sip, Vbc, Websocket -from .enums import CallState +from .enums import CallState, TtsLanguageCode from .ncco import Connect, Conversation, Input, Notify, Record, Stream, Talk @@ -54,3 +53,18 @@ class ListCallsFilter(BaseModel): record_index: Optional[int] = None order: Optional[Literal['asc', 'desc']] = None conversation_uuid: Optional[str] = None + + +class AudioStreamOptions(BaseModel): + stream_url: List[str] + loop: Optional[int] = Field(None, ge=0) + level: Optional[float] = Field(None, ge=-1, le=1) + + +class TtsStreamOptions(BaseModel): + text: str + language: Optional[TtsLanguageCode] = None + style: Optional[int] = None + premium: Optional[bool] = None + loop: Optional[int] = Field(None, ge=0) + level: Optional[float] = Field(None, ge=-1, le=1) diff --git a/voice/src/vonage_voice/voice.py b/voice/src/vonage_voice/voice.py index 9f62bc3e..f134210c 100644 --- a/voice/src/vonage_voice/voice.py +++ b/voice/src/vonage_voice/voice.py @@ -2,9 +2,15 @@ from pydantic import validate_call from vonage_http_client.http_client import HttpClient +from vonage_utils.types import Dtmf from vonage_voice.models.ncco import NccoAction -from .models.requests import CreateCallRequest, ListCallsFilter +from .models.requests import ( + AudioStreamOptions, + CreateCallRequest, + ListCallsFilter, + TtsStreamOptions, +) from .models.responses import CallInfo, CallList, CallMessage, CreateCallResponse @@ -14,6 +20,15 @@ class Voice: def __init__(self, http_client: HttpClient) -> None: self._http_client = http_client + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Voice API. + + Returns: + HttpClient: The HTTP client used to make requests to the Voice API. + """ + return self._http_client + @validate_call def create_call(self, params: CreateCallRequest) -> CreateCallResponse: """Creates a new call using the Vonage Voice API. @@ -157,3 +172,86 @@ def unearmuff(self, uuid: str) -> None: self._http_client.put( self._http_client.api_host, f'/v1/calls/{uuid}', {'action': 'unearmuff'} ) + + @validate_call + def play_audio_into_call( + self, uuid: str, audio_stream_options: AudioStreamOptions + ) -> CallMessage: + """Plays an audio stream into a call. + + Args: + uuid (str): The UUID of the call to stream audio into. + stream_audio_options (StreamAudioOptions): The options for streaming audio. + + Returns: + CallMessage: Object with information about the call. + """ + response = self._http_client.put( + self._http_client.api_host, + f'/v1/calls/{uuid}/stream', + audio_stream_options.model_dump(by_alias=True, exclude_none=True), + ) + + return CallMessage(**response) + + def stop_audio_stream(self, uuid: str) -> CallMessage: + """Stops streaming audio into a call. + + Args: + uuid (str): The UUID of the call to stop streaming audio into. + """ + response = self._http_client.delete( + self._http_client.api_host, f'/v1/calls/{uuid}/stream' + ) + + return CallMessage(**response) + + @validate_call + def play_tts_into_call(self, uuid: str, tts_options: TtsStreamOptions) -> CallMessage: + """Plays text-to-speech into a call. + + Args: + uuid (str): The UUID of the call to play text-to-speech into. + tts_options (TtsStreamOptions): The options for playing text-to-speech. + + Returns: + CallMessage: Object with information about the call. + """ + response = self._http_client.put( + self._http_client.api_host, + f'/v1/calls/{uuid}/talk', + tts_options.model_dump(by_alias=True, exclude_none=True), + ) + + return CallMessage(**response) + + def stop_tts(self, uuid: str) -> CallMessage: + """Stops playing text-to-speech into a call. + + Args: + uuid (str): The UUID of the call to stop playing text-to-speech into. + """ + response = self._http_client.delete( + self._http_client.api_host, f'/v1/calls/{uuid}/talk' + ) + + return CallMessage(**response) + + @validate_call + def play_dtmf_into_call(self, uuid: str, dtmf: Dtmf) -> CallMessage: + """Plays DTMF tones into a call. + + Args: + uuid (str): The UUID of the call to play DTMF tones into. + dtmf (Dtmf): The DTMF tones to play. + + Returns: + CallMessage: Object with information about the call. + """ + response = self._http_client.put( + self._http_client.api_host, + f'/v1/calls/{uuid}/dtmf', + {'digits': dtmf}, + ) + + return CallMessage(**response) diff --git a/voice/tests/data/play_audio_into_call.json b/voice/tests/data/play_audio_into_call.json new file mode 100644 index 00000000..e85d9856 --- /dev/null +++ b/voice/tests/data/play_audio_into_call.json @@ -0,0 +1,4 @@ +{ + "message": "Stream started", + "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" +} \ No newline at end of file diff --git a/voice/tests/data/play_dtmf_into_call.json b/voice/tests/data/play_dtmf_into_call.json new file mode 100644 index 00000000..4fdf22fb --- /dev/null +++ b/voice/tests/data/play_dtmf_into_call.json @@ -0,0 +1,4 @@ +{ + "message": "DTMF sent", + "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" +} \ No newline at end of file diff --git a/voice/tests/data/play_tts_into_call.json b/voice/tests/data/play_tts_into_call.json new file mode 100644 index 00000000..b30449dd --- /dev/null +++ b/voice/tests/data/play_tts_into_call.json @@ -0,0 +1,4 @@ +{ + "message": "Talk started", + "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" +} \ No newline at end of file diff --git a/voice/tests/data/stop_audio_stream.json b/voice/tests/data/stop_audio_stream.json new file mode 100644 index 00000000..8741edb2 --- /dev/null +++ b/voice/tests/data/stop_audio_stream.json @@ -0,0 +1,4 @@ +{ + "message": "Stream stopped", + "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" +} \ No newline at end of file diff --git a/voice/tests/data/stop_tts.json b/voice/tests/data/stop_tts.json new file mode 100644 index 00000000..c351661e --- /dev/null +++ b/voice/tests/data/stop_tts.json @@ -0,0 +1,4 @@ +{ + "message": "Talk stopped", + "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" +} \ No newline at end of file diff --git a/voice/tests/test_voice.py b/voice/tests/test_voice.py index ad7d2d96..313751c8 100644 --- a/voice/tests/test_voice.py +++ b/voice/tests/test_voice.py @@ -1,12 +1,17 @@ from os.path import abspath import responses -from responses.matchers import json_params_matcher from pytest import raises +from responses.matchers import json_params_matcher from vonage_http_client.http_client import HttpClient from vonage_voice.errors import VoiceError from vonage_voice.models.ncco import Talk -from vonage_voice.models.requests import CreateCallRequest, ListCallsFilter +from vonage_voice.models.requests import ( + AudioStreamOptions, + CreateCallRequest, + ListCallsFilter, + TtsStreamOptions, +) from vonage_voice.models.responses import CreateCallResponse from vonage_voice.voice import Voice @@ -18,6 +23,10 @@ voice = Voice(HttpClient(get_mock_jwt_auth())) +def test_http_client_property(): + assert type(voice.http_client) == HttpClient + + @responses.activate def test_create_call_basic_ncco(): build_response( @@ -318,3 +327,84 @@ def test_unearmuff(): voice.unearmuff('e154eb57-2962-41e7-baf4-90f63e25e439') assert voice._http_client.last_response.status_code == 204 + + +@responses.activate +def test_play_audio_into_call(): + uuid = 'e154eb57-2962-41e7-baf4-90f63e25e439' + build_response( + path, + 'PUT', + f'https://api.nexmo.com/v1/calls/{uuid}/stream', + 'play_audio_into_call.json', + ) + + options = AudioStreamOptions( + stream_url=['https://example.com/audio'], loop=2, level=0.5 + ) + response = voice.play_audio_into_call(uuid, options) + assert response.message == 'Stream started' + assert response.uuid == uuid + + +@responses.activate +def test_stop_audio_stream(): + uuid = 'e154eb57-2962-41e7-baf4-90f63e25e439' + build_response( + path, + 'DELETE', + f'https://api.nexmo.com/v1/calls/{uuid}/stream', + 'stop_audio_stream.json', + ) + + response = voice.stop_audio_stream(uuid) + assert response.message == 'Stream stopped' + assert response.uuid == uuid + + +@responses.activate +def test_play_tts_into_call(): + uuid = 'e154eb57-2962-41e7-baf4-90f63e25e439' + build_response( + path, + 'PUT', + f'https://api.nexmo.com/v1/calls/{uuid}/talk', + 'play_tts_into_call.json', + ) + + options = TtsStreamOptions( + text='Hello world', language='en-ZA', style=1, premium=False, loop=2, level=0.5 + ) + response = voice.play_tts_into_call(uuid, options) + assert response.message == 'Talk started' + assert response.uuid == uuid + + +@responses.activate +def test_stop_tts(): + uuid = 'e154eb57-2962-41e7-baf4-90f63e25e439' + build_response( + path, + 'DELETE', + f'https://api.nexmo.com/v1/calls/{uuid}/talk', + 'stop_tts.json', + ) + + response = voice.stop_tts(uuid) + assert response.message == 'Talk stopped' + assert response.uuid == uuid + + +@responses.activate +def test_play_dtmf_into_call(): + uuid = 'e154eb57-2962-41e7-baf4-90f63e25e439' + build_response( + path, + 'PUT', + f'https://api.nexmo.com/v1/calls/{uuid}/dtmf', + 'play_dtmf_into_call.json', + ) + + response = voice.play_dtmf_into_call(uuid, dtmf='1234*#') + assert response.message == 'DTMF sent' + assert response.uuid == uuid diff --git a/vonage/CHANGES.md b/vonage/CHANGES.md index 40b182e4..95acc56d 100644 --- a/vonage/CHANGES.md +++ b/vonage/CHANGES.md @@ -1,5 +1,6 @@ # 3.99.0a7 - Add support for the [Vonage Voice API](https://developer.vonage.com/en/voice/voice-api/overview). +- Add `http_client` property to each module that has an HTTP Client, e.g. Voice, Sms, Verify. # 3.99.0a6 - Add support for the [Vonage Messages API](https://developer.vonage.com/en/messages/overview). diff --git a/vonage/pyproject.toml b/vonage/pyproject.toml index 20b8ba97..9b1680c5 100644 --- a/vonage/pyproject.toml +++ b/vonage/pyproject.toml @@ -6,14 +6,14 @@ readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-utils>=1.0.1", - "vonage-http-client>=1.2.1", - "vonage-messages>=1.0.0", - "vonage-number-insight-v2>=0.1.0", - "vonage-sms>=1.0.2", - "vonage-users>=1.0.1", - "vonage-verify>=1.0.1", - "vonage-verify-v2>=1.0.0", + "vonage-utils>=1.1.0", + "vonage-http-client>=1.3.0", + "vonage-messages>=1.1.0", + "vonage-number-insight-v2>=0.1.0b0", + "vonage-sms>=1.1.0", + "vonage-users>=1.1.0", + "vonage-verify>=1.1.0", + "vonage-verify-v2>=1.1.0", "vonage-voice>=1.0.1", ] classifiers = [ diff --git a/vonage/src/vonage/_version.py b/vonage/src/vonage/_version.py index e04178b8..5038f475 100644 --- a/vonage/src/vonage/_version.py +++ b/vonage/src/vonage/_version.py @@ -1 +1 @@ -__version__ = '3.99.0a6' +__version__ = '3.99.0a7' diff --git a/vonage_utils/CHANGES.md b/vonage_utils/CHANGES.md index bc23e4c1..4fcf86af 100644 --- a/vonage_utils/CHANGES.md +++ b/vonage_utils/CHANGES.md @@ -1,3 +1,8 @@ +# 1.1.0 +- Add `Dtmf` and `SipUri` types +- Add `Link` model +- Internal refactoring + # 1.0.1 - Add `PhoneNumber` type diff --git a/vonage_utils/pyproject.toml b/vonage_utils/pyproject.toml index 75fd4474..26a14932 100644 --- a/vonage_utils/pyproject.toml +++ b/vonage_utils/pyproject.toml @@ -1,10 +1,10 @@ [project] name = 'vonage-utils' -version = '1.0.1' +version = '1.1.0' description = 'Utils package containing objects for use with Vonage APIs' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] -dependencies = ["pydantic>=2.6.1"] +dependencies = ["typing_extensions>=4.9.0", "pydantic>=2.6.1"] requires-python = ">=3.8" classifiers = [ "Programming Language :: Python", diff --git a/vonage_utils/src/vonage_utils/types.py b/vonage_utils/src/vonage_utils/types.py index b3d37bfc..63bd319c 100644 --- a/vonage_utils/src/vonage_utils/types.py +++ b/vonage_utils/src/vonage_utils/types.py @@ -1,6 +1,5 @@ -from typing import Annotated - from pydantic import Field +from typing_extensions import Annotated PhoneNumber = Annotated[str, Field(pattern=r'^[1-9]\d{6,14}$')] Dtmf = Annotated[str, Field(pattern=r'^[0-9#*p]+$')]