diff --git a/http_client/CHANGES.md b/http_client/CHANGES.md index 2e3462b8..528d4668 100644 --- a/http_client/CHANGES.md +++ b/http_client/CHANGES.md @@ -1,3 +1,6 @@ +# 1.2.1 +- Expose classes and errors at the package level + # 1.2.0 - Add `last_request` and `last_response` properties - Add new `Forbidden` error diff --git a/http_client/README.md b/http_client/README.md index a422bf2e..9ed14438 100644 --- a/http_client/README.md +++ b/http_client/README.md @@ -69,4 +69,17 @@ The `HttpClient` class automatically handles JWT and basic authentication based ```python # Use basic authentication for this request response = client.get(host='api.nexmo.com', request_path='/v1/messages', auth_type='basic') +``` + +### Catching errors + +Error objects are exposed in the package scope, so you can catch errors like this: + +```python +from vonage_http_client import HttpRequestError + +try: + client.post(...) +except HttpRequestError: + ... ``` \ No newline at end of file diff --git a/http_client/pyproject.toml b/http_client/pyproject.toml index d8f02731..683dc16c 100644 --- a/http_client/pyproject.toml +++ b/http_client/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vonage-http-client" -version = "1.2.0" +version = "1.2.1" description = "An HTTP client for making requests to Vonage APIs." readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] diff --git a/http_client/src/vonage_http_client/__init__.py b/http_client/src/vonage_http_client/__init__.py index e69de29b..88e8a9b7 100644 --- a/http_client/src/vonage_http_client/__init__.py +++ b/http_client/src/vonage_http_client/__init__.py @@ -0,0 +1,28 @@ +from .auth import Auth +from .errors import ( + AuthenticationError, + ForbiddenError, + HttpRequestError, + InvalidAuthError, + InvalidHttpClientOptionsError, + JWTGenerationError, + NotFoundError, + RateLimitedError, + ServerError, +) +from .http_client import HttpClient, HttpClientOptions + +__all__ = [ + 'Auth', + 'AuthenticationError', + 'ForbiddenError', + 'HttpRequestError', + 'InvalidAuthError', + 'InvalidHttpClientOptionsError', + 'JWTGenerationError', + 'NotFoundError', + 'RateLimitedError', + 'ServerError', + 'HttpClient', + 'HttpClientOptions', +] diff --git a/verify/CHANGES.md b/verify/CHANGES.md index be516a55..a28aa741 100644 --- a/verify/CHANGES.md +++ b/verify/CHANGES.md @@ -1,2 +1,5 @@ +# 1.0.1 +- Internal refactoring + # 1.0.0 - Initial upload diff --git a/verify/pyproject.toml b/verify/pyproject.toml index 7994d8b8..01ba042d 100644 --- a/verify/pyproject.toml +++ b/verify/pyproject.toml @@ -1,12 +1,12 @@ [project] name = 'vonage-verify' -version = '1.0.0' +version = '1.0.1' description = 'Vonage verify package' readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.0", + "vonage-http-client>=1.2.1", "vonage-utils>=1.0.1", "pydantic>=2.6.1", ] diff --git a/verify_v2/README.md b/verify_v2/README.md index e81d0584..79a4f7f4 100644 --- a/verify_v2/README.md +++ b/verify_v2/README.md @@ -6,59 +6,49 @@ This package contains the code to use [Vonage's Verify v2 API](https://developer 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`. - -###################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################### - ### Make a Verify Request ```python -from vonage_verify import VerifyRequest -params = {'number': '1234567890', 'brand': 'Acme Inc.'} -request = VerifyRequest(**params) -response = vonage_client.verify.start_verification(request) +from vonage_verify_v2 import VerifyRequest, SmsChannel +# All channels have associated models +sms_channel = SmsChannel(to='1234567890') +params = { + 'brand': 'Vonage', + 'workflow': [sms_channel], +} +verify_request = VerifyRequest(**params) + +response = vonage_client.verify_v2.start_verification(verify_request) ``` -### Make a PSD2 (Payment Services Directive v2) Request +If using silent authentication, the response will include a `check_url` field with a url that should be accessed on the user's device to proceed with silent authentication. If used, silent auth must be the first element in the `workflow` list. ```python -from vonage_verify import Psd2Request -params = {'number': '1234567890', 'payee': 'Acme Inc.', 'amount': 99.99} -request = VerifyRequest(**params) -response = vonage_client.verify.start_verification(request) +silent_auth_channel = SilentAuthChannel(channel=ChannelType.SILENT_AUTH, to='1234567890') +sms_channel = SmsChannel(to='1234567890') +params = { + 'brand': 'Vonage', + 'workflow': [silent_auth_channel, sms_channel], +} +verify_request = VerifyRequest(**params) + +response = vonage_client.verify_v2.start_verification(verify_request) ``` ### Check a Verification Code ```python -vonage_client.verify.check_code(request_id='my_request_id', code='1234') -``` - -### Search Verification Requests - -```python -# Search for single request -response = vonage_client.verify.search('my_request_id') - -# Search for multiple requests -response = vonage_client.verify.search(['my_request_id_1', 'my_request_id_2']) +vonage_client.verify_v2.check_code(request_id='my_request_id', code='1234') ``` ### Cancel a Verification ```python -response = vonage_client.verify.cancel_verification('my_request_id') +vonage_client.verify_v2.cancel_verification('my_request_id') ``` ### Trigger the Next Workflow Event ```python -response = vonage_client.verify.trigger_next_event('my_request_id') -``` - -### Request a Network Unblock - -Note: Network Unblock is switched off by default. Contact Sales to enable the Network Unblock API for your account. - -```python -response = vonage_client.verify.request_network_unblock('23410') -``` +vonage_client.verify_v2.trigger_next_workflow('my_request_id') +``` \ No newline at end of file diff --git a/verify_v2/pyproject.toml b/verify_v2/pyproject.toml index 87906f01..1b4894dc 100644 --- a/verify_v2/pyproject.toml +++ b/verify_v2/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ - "vonage-http-client>=1.2.0", + "vonage-http-client>=1.2.1", "vonage-utils>=1.0.1", "pydantic>=2.6.1", ] diff --git a/verify_v2/src/vonage_verify_v2/__init__.py b/verify_v2/src/vonage_verify_v2/__init__.py index 2bacce82..f0b3b432 100644 --- a/verify_v2/src/vonage_verify_v2/__init__.py +++ b/verify_v2/src/vonage_verify_v2/__init__.py @@ -8,13 +8,14 @@ VoiceChannel, WhatsappChannel, ) -from .responses import StartVerificationResponse +from .responses import CheckCodeResponse, StartVerificationResponse from .verify_v2 import VerifyV2 __all__ = [ 'VerifyV2', 'VerifyError', 'ChannelType', + 'CheckCodeResponse', 'Locale', 'VerifyRequest', 'SilentAuthChannel', diff --git a/verify_v2/src/vonage_verify_v2/requests.py b/verify_v2/src/vonage_verify_v2/requests.py index 87f5003c..2aa7d124 100644 --- a/verify_v2/src/vonage_verify_v2/requests.py +++ b/verify_v2/src/vonage_verify_v2/requests.py @@ -31,11 +31,11 @@ def check_valid_from_field(cls, v): if ( v is not None and type(v) is not PhoneNumber - and not search(r'^[a-zA-Z0-9]{1,15}$', v) + and not search(r'^[a-zA-Z0-9]{3,11}$', v) ): raise VerifyError( 'You must specify a valid "from_" value if included. ' - 'It must be a valid phone number without the leading +, or a string of 1-15 alphanumeric characters. ' + 'It must be a valid phone number without the leading +, or a string of 3-11 alphanumeric characters. ' f'You set "from_": "{v}".' ) return v @@ -48,10 +48,10 @@ class WhatsappChannel(Channel): @field_validator('from_') @classmethod def check_valid_sender(cls, v): - if type(v) is not PhoneNumber and not search(r'^[a-zA-Z0-9]{1,15}$', v): + if type(v) is not PhoneNumber and not search(r'^[a-zA-Z0-9]{3,11}$', v): raise VerifyError( f'You must specify a valid "from_" value. ' - 'It must be a valid phone number without the leading +, or a string of 1-15 alphanumeric characters. ' + 'It must be a valid phone number without the leading +, or a string of 3-11 alphanumeric characters. ' f'You set "from_": "{v}".' ) return v @@ -84,14 +84,6 @@ class VerifyRequest(BaseModel): code_length: Optional[int] = Field(None, ge=4, le=10) code: Optional[str] = Field(None, pattern=r'^[a-zA-Z0-9]{4,10}$') - @model_validator(mode='after') - def remove_fields_if_only_silent_auth(self): - if len(self.workflow) == 1 and isinstance(self.workflow[0], SilentAuthChannel): - self.locale = None - self.code_length = None - self.code = None - return self - @model_validator(mode='after') def check_silent_auth_first_if_present(self): if len(self.workflow) > 1: diff --git a/verify_v2/src/vonage_verify_v2/verify_v2.py b/verify_v2/src/vonage_verify_v2/verify_v2.py index 2e7380d7..472033a4 100644 --- a/verify_v2/src/vonage_verify_v2/verify_v2.py +++ b/verify_v2/src/vonage_verify_v2/verify_v2.py @@ -59,11 +59,13 @@ def cancel_verification(self, request_id: str) -> None: @validate_call def trigger_next_workflow(self, request_id: str) -> None: - """Trigger the next workflow event in the verification process. + """Trigger the next workflow event in the list of workflows passed in when making the + request. Args: request_id (str): The request ID. """ - response = self._http_client.post( - self._http_client.api_host, f'/v2/verify/{request_id}' + self._http_client.post( + self._http_client.api_host, + f'/v2/verify/{request_id}/next_workflow', ) diff --git a/verify_v2/tests/data/trigger_next_workflow_error.json b/verify_v2/tests/data/trigger_next_workflow_error.json new file mode 100644 index 00000000..befd87a7 --- /dev/null +++ b/verify_v2/tests/data/trigger_next_workflow_error.json @@ -0,0 +1,6 @@ +{ + "title": "Conflict", + "detail": "There are no more events left to trigger.", + "instance": "4d731cb7-25d3-487a-9ea0-f6b5811b534f", + "type": "https://developer.nexmo.com/api-errors#conflict" +} \ No newline at end of file diff --git a/verify_v2/tests/test_models.py b/verify_v2/tests/test_models.py index f9877ef7..b1a3eb81 100644 --- a/verify_v2/tests/test_models.py +++ b/verify_v2/tests/test_models.py @@ -124,13 +124,6 @@ def test_create_verify_request(): assert verify_request.code_length == 6 assert verify_request.code == '123456' - # Silent auth only - params['workflow'] = [silent_auth_channel] - verify_request = VerifyRequest(**params) - assert verify_request.locale == None - assert verify_request.code_length == None - assert verify_request.code == None - def test_create_verify_request_error(): params = { diff --git a/verify_v2/tests/test_verify_v2.py b/verify_v2/tests/test_verify_v2.py index 3e1d57f8..57da7c89 100644 --- a/verify_v2/tests/test_verify_v2.py +++ b/verify_v2/tests/test_verify_v2.py @@ -67,7 +67,7 @@ def test_make_verify_request_full(): @responses.activate -def test_verify_request_error(): +def test_verify_request_concurrent_verifications_error(): build_response( path, 'POST', @@ -155,20 +155,30 @@ def test_cancel_verification(): @responses.activate def test_trigger_next_workflow(): - response = verify.trigger_next_event('c5037cb8b47449158ed6611afde58990') + responses.add( + responses.POST, + 'https://api.nexmo.com/v2/verify/36e7060d-2b23-4257-bad0-773ab47f85ef/next_workflow', + status=200, + ) + assert verify.trigger_next_workflow('36e7060d-2b23-4257-bad0-773ab47f85ef') is None + assert verify._http_client.last_response.status_code == 200 -# @responses.activate -# def test_trigger_next_event_error(): -# build_response( -# path, -# 'POST', -# 'https://api.nexmo.com/verify/control/json', -# 'trigger_next_event_error.json', -# ) +@responses.activate +def test_trigger_next_event_error(): + build_response( + path, + 'POST', + 'https://api.nexmo.com/v2/verify/36e7060d-2b23-4257-bad0-773ab47f85ef/next_workflow', + 'trigger_next_workflow_error.json', + status_code=409, + ) -# with raises(VerifyError) as e: -# verify.trigger_next_event('2c021d25cf2e47a9b277a996f4325b81') + with raises(HttpRequestError) as e: + verify.trigger_next_workflow('36e7060d-2b23-4257-bad0-773ab47f85ef') -# assert e.match("'status': '19") -# assert e.match('No more events are left to execute for the request') + assert e.value.response.status_code == 409 + assert e.value.response.json()['title'] == 'Conflict' + assert ( + e.value.response.json()['detail'] == 'There are no more events left to trigger.' + ) diff --git a/vonage/CHANGES.md b/vonage/CHANGES.md index ebeaef18..ddbf1d44 100644 --- a/vonage/CHANGES.md +++ b/vonage/CHANGES.md @@ -1,3 +1,7 @@ +# 3.99.0a5 +- Add support for the [Vonage Verify V2 API](https://developer.vonage.com/en/verify/overview). +- Expose error classes at the top level of the `vonage-http-client` package. + # 3.99.0a4 - Add support for the [Vonage Verify API](https://developer.vonage.com/en/api/verify). - Add `last_request` and `last_response` properties to the HTTP Client. diff --git a/vonage/pyproject.toml b/vonage/pyproject.toml index 053f8839..86026806 100644 --- a/vonage/pyproject.toml +++ b/vonage/pyproject.toml @@ -7,11 +7,12 @@ authors = [{ name = "Vonage", email = "devrel@vonage.com" }] requires-python = ">=3.8" dependencies = [ "vonage-utils>=1.0.1", - "vonage-http-client>=1.2.0", + "vonage-http-client>=1.2.1", "vonage-number-insight-v2>=0.1.0", "vonage-sms>=1.0.2", "vonage-users>=1.0.1", - "vonage-verify>=1.0.0", + "vonage-verify>=1.0.1", + "vonage-verify-v2>=1.0.0", ] classifiers = [ "Programming Language :: Python", diff --git a/vonage/src/vonage/_version.py b/vonage/src/vonage/_version.py index 9233543a..29defcc4 100644 --- a/vonage/src/vonage/_version.py +++ b/vonage/src/vonage/_version.py @@ -1 +1 @@ -__version__ = '3.99.0a4' +__version__ = '3.99.0a5' diff --git a/vonage/src/vonage/vonage.py b/vonage/src/vonage/vonage.py index 76eceeff..92b2d9a5 100644 --- a/vonage/src/vonage/vonage.py +++ b/vonage/src/vonage/vonage.py @@ -1,11 +1,10 @@ from typing import Optional -from vonage_http_client.auth import Auth -from vonage_http_client.http_client import HttpClient, HttpClientOptions -from vonage_number_insight_v2.number_insight_v2 import NumberInsightV2 -from vonage_sms.sms import Sms -from vonage_users.users import Users -from vonage_verify.verify import Verify +from vonage_http_client import Auth, HttpClient, HttpClientOptions +from vonage_number_insight_v2 import NumberInsightV2 +from vonage_sms import Sms +from vonage_users import Users +from vonage_verify import Verify from vonage_verify_v2 import VerifyV2 from ._version import __version__