Skip to content

Commit

Permalink
finish verify implementation and prepare for release
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkahan committed Apr 4, 2024
1 parent 0843a15 commit 2b68284
Show file tree
Hide file tree
Showing 22 changed files with 255 additions and 39 deletions.
1 change: 1 addition & 0 deletions http_client/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 1.2.0
- Add `last_request` and `last_response` properties
- Add new `Forbidden` error

# 1.1.1
- Add new Patch method
Expand Down
14 changes: 13 additions & 1 deletion http_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,21 @@ response = client.get(host='api.nexmo.com', request_path='/v1/messages')
response = client.post(host='api.nexmo.com', request_path='/v1/messages', params={'key': 'value'})
```

## Get the Last Request and Last Response from the HTTP Client

The `HttpClient` class exposes two properties, `last_request` and `last_response` that cache the last sent request and response.

```python
# Get last request, has type requests.PreparedRequest
request = client.last_request

# Get last response, has type requests.Response
response = client.last_response
```

### Appending to the User-Agent Header

The HttpClient class also supports appending additional information to the User-Agent header via the append_to_user_agent method:
The `HttpClient` class also supports appending additional information to the User-Agent header via the append_to_user_agent method:

```python
client.append_to_user_agent('additional_info')
Expand Down
4 changes: 2 additions & 2 deletions http_client/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "vonage-http-client"
version = "1.1.1"
version = "1.2.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.0",
"vonage-utils>=1.0.1",
"vonage-jwt>=1.1.0",
"requests>=2.27.0",
"pydantic>=2.6.1",
Expand Down
18 changes: 18 additions & 0 deletions http_client/src/vonage_http_client/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ def __init__(self, response: Response, content_type: str):
super().__init__(response, content_type)


class ForbiddenError(HttpRequestError):
"""Exception indicating a forbidden request in a Vonage SDK request.
This error is raised when the HTTP response status code is 403 (Forbidden).
Args:
response (requests.Response): The HTTP response object.
content_type (str): The response content type.
Attributes (inherited from HttpRequestError parent exception):
response (requests.Response): The HTTP response object.
message (str): The returned error message.
"""

def __init__(self, response: Response, content_type: str):
super().__init__(response, content_type)


class NotFoundError(HttpRequestError):
"""Exception indicating a resource was not found in a Vonage SDK request.
Expand Down
12 changes: 8 additions & 4 deletions http_client/src/vonage_http_client/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from vonage_http_client.auth import Auth
from vonage_http_client.errors import (
AuthenticationError,
ForbiddenError,
HttpRequestError,
InvalidHttpClientOptionsError,
NotFoundError,
Expand Down Expand Up @@ -109,7 +110,8 @@ def last_request(self) -> Optional[PreparedRequest]:
"""The last request sent to the server.
Returns:
Optional[PreparedRequest]: The exact bytes of the request sent to the server.
Optional[PreparedRequest]: The exact bytes of the request sent to the server,
or None if no request has been sent.
"""
return self._last_response.request

Expand All @@ -118,7 +120,8 @@ def last_response(self) -> Optional[Response]:
"""The last response received from the server.
Returns:
Optional[Response]: The response object received from the server.
Optional[Response]: The response object received from the server,
or None if no response has been received.
"""
return self._last_response

Expand Down Expand Up @@ -221,7 +224,6 @@ def _parse_response(self, response: Response) -> Union[dict, None]:
f'Response received from {response.url} with status code: {response.status_code}; headers: {response.headers}'
)
self._last_response = response
print(response.content)
content_type = response.headers['Content-Type'].split(';', 1)[0]
if 200 <= response.status_code < 300:
if response.status_code == 204:
Expand All @@ -234,8 +236,10 @@ def _parse_response(self, response: Response) -> Union[dict, None]:
logger.warning(
f'Http Response Error! Status code: {response.status_code}; content: {repr(response.text)}; from url: {response.url}'
)
if response.status_code == 401 or response.status_code == 403:
if response.status_code == 401:
raise AuthenticationError(response, content_type)
if response.status_code == 403:
raise ForbiddenError(response, content_type)
elif response.status_code == 404:
raise NotFoundError(response, content_type)
elif response.status_code == 429:
Expand Down
6 changes: 6 additions & 0 deletions http_client/tests/data/403.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "https://developer.vonage.com/api-errors#forbidden",
"title": "Forbidden",
"detail": "Your account does not have permission to perform this action.",
"instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf"
}
16 changes: 16 additions & 0 deletions http_client/tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from vonage_http_client.auth import Auth
from vonage_http_client.errors import (
AuthenticationError,
ForbiddenError,
HttpRequestError,
InvalidHttpClientOptionsError,
RateLimitedError,
Expand Down Expand Up @@ -196,6 +197,21 @@ def test_authentication_error_no_content():
assert type(err.response) == Response


@responses.activate
def test_forbidden_error():
build_response(path, 'GET', 'https://example.com/get_json', '403.json', 403)

client = HttpClient(Auth())
try:
client.get(host='example.com', request_path='/get_json', auth_type='basic')
except ForbiddenError as err:
assert err.response.json()['title'] == 'Forbidden'
assert (
err.response.json()['detail']
== 'Your account does not have permission to perform this action.'
)


@responses.activate
def test_not_found_error():
build_response(path, 'GET', 'https://example.com/get_json', '404.json', 404)
Expand Down
8 changes: 4 additions & 4 deletions users/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = 'vonage-users'
version = '1.0.0'
description = 'Vonage SMS package'
version = '1.0.1'
description = 'Vonage Users 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.2.0",
"vonage-utils>=1.0.1",
"pydantic>=2.6.1",
]
classifiers = [
Expand Down
62 changes: 49 additions & 13 deletions verify/README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,63 @@
# Vonage Verify Package

This package contains the code to use Vonage's Verify API in Python. There is a more current package to user Vonage's Verify v2 API which is recommended to use for most use cases. The v2 API lets you send messages via multiple channels, including Email, SMS, MMS, WhatsApp, Messenger and others. You can also make Silent Authentication requests with Verify v2 to give an end user a more seamless experience.

This package includes methods for sending 2-factor authentication (2FA) messages and returns...


asdf
asdf
This package contains the code to use Vonage's Verify API in Python. This package includes methods for working with 2-factor authentication (2FA) messages sent via SMS or TTS.

Note: There is a more current package available: [Vonage's Verify v2 API](https://developer.vonage.com/en/verify/overview) which is recommended for most use cases. The v2 API lets you send messages via multiple channels, including Email, SMS, MMS, WhatsApp, Messenger and others. You can also make Silent Authentication requests with Verify v2 to give an end user a more seamless experience.

## 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`.

### Make a Verify Request

<!-- Create an `SmsMessage` object, then pass into the `Sms.send` method.
```python
from vonage_verify import VerifyRequest
params = {'number': '1234567890', 'brand': 'Acme Inc.'}
request = VerifyRequest(**params)
response = vonage_client.verify.start_verification(request)
```

### Make a PSD2 (Payment Services Directive v2) Request

```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)
```

### 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'])
```

### Cancel a Verification

```python
from vonage_sms import SmsMessage, SmsResponse
response = vonage_client.verify.cancel_verification('my_request_id')
```

message = SmsMessage(to='1234567890', from_='Acme Inc.', text='Hello, World!')
response: SmsResponse = vonage_client.sms.send(message)
### Trigger the Next Workflow Event

print(response.model_dump(exclude_unset=True))
``` -->
```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')
```
4 changes: 2 additions & 2 deletions verify/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ 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.2.0",
"vonage-utils>=1.0.1",
"pydantic>=2.6.1",
]
classifiers = [
Expand Down
22 changes: 18 additions & 4 deletions verify/src/vonage_verify/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
# from .errors import PartialFailureError, SmsError
from .errors import VerifyError
from .language_codes import LanguageCode, Psd2LanguageCode
from .requests import Psd2Request, VerifyRequest

# from .responses import MessageResponse, SmsResponse
from .responses import (
CheckCodeResponse,
NetworkUnblockStatus,
StartVerificationResponse,
VerifyControlStatus,
VerifyStatus,
)
from .verify import Verify

__all__ = [
'Verify',
'VerifyRequest',
'VerifyError',
'LanguageCode',
'Psd2LanguageCode',
'Psd2Request',
'VerifyRequest',
'CheckCodeResponse',
'NetworkUnblockStatus',
'StartVerificationResponse',
'VerifyControlStatus',
'VerifyStatus',
]
27 changes: 25 additions & 2 deletions verify/src/vonage_verify/verify.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import List, Union
from typing import List, Optional, Union

from pydantic import validate_call
from pydantic import Field, validate_call
from vonage_http_client.http_client import HttpClient

from .errors import VerifyError
from .requests import BaseVerifyRequest, Psd2Request, VerifyRequest
from .responses import (
CheckCodeResponse,
NetworkUnblockStatus,
StartVerificationResponse,
VerifyControlStatus,
VerifyStatus,
Expand Down Expand Up @@ -153,6 +154,28 @@ def trigger_next_event(self, request_id: str) -> VerifyControlStatus:

return VerifyControlStatus(**response)

@validate_call
def request_network_unblock(
self, network: str, unblock_duration: Optional[int] = Field(None, ge=0, le=86400)
) -> NetworkUnblockStatus:
"""Request to unblock a network that has been blocked due to potential fraud detection.
Note: The network unblock feature is switched off by default.
Please contact Sales to enable the Network Unblock API for your account.
Args:
network (str): The network code of the network to unblock.
unblock_duration (int, optional): How long (in seconds) to unblock the network for.
"""
response = self._http_client.post(
self._http_client.api_host,
'/verify/network-unblock',
{'network': network, 'duration': unblock_duration},
self._auth_type,
)

return NetworkUnblockStatus(**response)

def _make_verify_request(
self, verify_request: BaseVerifyRequest
) -> StartVerificationResponse:
Expand Down
4 changes: 4 additions & 0 deletions verify/tests/data/network_unblock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"network": "23410",
"unblocked_until": "2024-04-22T08:34:58Z"
}
6 changes: 6 additions & 0 deletions verify/tests/data/network_unblock_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "https://developer.vonage.com/api-errors#bad-request",
"title": "Not Found",
"detail": "The network you provided does not have an active block.",
"instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf"
}
Loading

0 comments on commit 2b68284

Please sign in to comment.