Skip to content

Commit

Permalink
add conversion method, SMS tests, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkahan committed Mar 23, 2024
1 parent 01145a9 commit b547a95
Show file tree
Hide file tree
Showing 24 changed files with 251 additions and 66 deletions.
7 changes: 5 additions & 2 deletions http_client/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# 0.1.0
- Initial upload
# 1.0.0
- Initial upload

# 1.1.0
- Add support for signature authentication
2 changes: 1 addition & 1 deletion http_client/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "vonage-http-client"
version = "1.0.0"
version = "1.1.0"
description = "An HTTP client for making requests to Vonage APIs."
readme = "README.md"
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
Expand Down
4 changes: 1 addition & 3 deletions http_client/src/vonage_http_client/auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import hashlib
import hmac
from base64 import b64encode

from time import time
from typing import Literal, Optional

Expand Down Expand Up @@ -111,8 +110,7 @@ def sign_params(self, params: dict) -> str:

@validate_call
def check_signature(self, params: dict) -> bool:
"""
Checks the signature hash of the given parameters.
"""Checks the signature hash of the given parameters.
Args:
params (dict): The parameters to check the signature for.
Expand Down
6 changes: 5 additions & 1 deletion http_client/src/vonage_http_client/http_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from json import JSONDecodeError
from logging import getLogger
from platform import python_version
from typing import Literal, Optional, Union
Expand Down Expand Up @@ -167,7 +168,10 @@ def _parse_response(self, response: Response) -> Union[dict, None]:
if 200 <= response.status_code < 300:
if response.status_code == 204:
return None
return response.json()
try:
return response.json()
except JSONDecodeError:
return None
if response.status_code >= 400:
logger.warning(
f'Http Response Error! Status code: {response.status_code}; content: {repr(response.text)}; from url: {response.url}'
Expand Down
4 changes: 2 additions & 2 deletions number_insight_v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ It includes classes for making fraud check requests and handling the responses.
First, import the necessary classes and create an instance of the `NumberInsightV2` class:

```python
from vonage_http_client.http_client import HttpClient
from vonage_http_client.http_client import HttpClient, Auth
from number_insight_v2 import NumberInsightV2, FraudCheckRequest

http_client = HttpClient(api_host='your_api_host', api_key='your_api_key', api_secret='your_api_secret')
http_client = HttpClient(Auth(api_key='your_api_key', api_secret='your_api_secret'))
number_insight = NumberInsightV2(http_client)
```

Expand Down
2 changes: 2 additions & 0 deletions pants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ backend_packages = [
"pants.backend.experimental.python",
]

pants_ignore.add = ['!_test_scripts/']

[anonymous-telemetry]
enabled = false

Expand Down
2 changes: 1 addition & 1 deletion sms/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ python_distribution(
],
provides=python_artifact(),
generate_setup=False,
repositories=['@testpypi'],
repositories=['@pypi'],
)
2 changes: 2 additions & 0 deletions sms/CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 1.0.0
- Initial upload
21 changes: 20 additions & 1 deletion sms/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Vonage SMS Package

This package contains the code to use Vonage's SMS API with Python.
This package contains the code to use Vonage's SMS API in Python.

It includes a method for sending SMS messages and returns an `SmsResponse` class to handle the response.

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

### Send an SMS

Create an `SmsMessage` object, then pass into the `Sms.send` method.

```python
from vonage_sms import SmsMessage, SmsResponse

message = SmsMessage(to='1234567890', from_='Acme Inc.', text='Hello, World!')
response: SmsResponse = vonage_client.sms.send(message)

print(response.model_dump(exclude_unset=True))
```


2 changes: 1 addition & 1 deletion sms/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = 'vonage-sms'
version = '0.1.0'
version = '1.0.0'
description = 'Vonage SMS package'
readme = "README.md"
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
Expand Down
8 changes: 8 additions & 0 deletions sms/src/vonage_sms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
from .errors import PartialFailureError, SmsError
from .requests import SmsMessage
from .responses import MessageResponse, SmsResponse
from .sms import Sms

__all__ = [
'Sms',
'SmsMessage',
'SmsResponse',
'MessageResponse',
'SmsError',
'PartialFailureError',
]
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
from typing import Literal, Optional

from pydantic import (
BaseModel,
Field,
ValidationInfo,
field_validator,
model_validator,
)
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator


class SmsMessage(BaseModel):
Expand Down
31 changes: 19 additions & 12 deletions sms/src/vonage_sms/responses.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
from dataclasses import dataclass
from typing import List, Optional

from pydantic import BaseModel, Field, field_validator

@dataclass
class MessageResponse:

class MessageResponse(BaseModel):
to: str
message_id: str
message_id: str = Field(..., validation_alias='message-id')
status: str
remaining_balance: str
message_price: str
remaining_balance: str = Field(..., validation_alias='remaining-balance')
message_price: str = Field(..., validation_alias='message-price')
network: str
client_ref: Optional[str] = None
account_ref: Optional[str] = None
client_ref: Optional[str] = Field(None, validation_alias='client-ref')
account_ref: Optional[str] = Field(None, validation_alias='account-ref')


class SmsResponse(BaseModel):
message_count: str = Field(..., validation_alias='message-count')
messages: List[dict]

@dataclass
class SmsResponse:
message_count: str
messages: List[MessageResponse]
@field_validator('messages')
@classmethod
def create_message_response(cls, value):
messages = []
for message in value:
messages.append(MessageResponse(**message))
return messages
55 changes: 41 additions & 14 deletions sms/src/vonage_sms/sms.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from copy import deepcopy
from datetime import datetime, timezone

from pydantic import validate_call
from vonage_http_client.http_client import HttpClient

from .errors import PartialFailureError, SmsError
from .models import SmsMessage
from .responses import MessageResponse, SmsResponse
from .requests import SmsMessage
from .responses import SmsResponse


class Sms:
"""Calls Vonage's SMS API."""

def __init__(self, http_client: HttpClient) -> None:
self._http_client = deepcopy(http_client)
self._http_client = http_client
self._body_type = 'data'
if self._http_client._auth._signature_secret:
self._auth_type = 'signature'
Expand All @@ -31,17 +31,12 @@ def send(self, message: SmsMessage) -> SmsResponse:
)

if int(response['message-count']) > 1:
self.check_for_partial_failure(response)
self._check_for_partial_failure(response)
else:
self.check_for_error(response)
self._check_for_error(response)
return SmsResponse(**response)

messages = []
for message in response['messages']:
messages.append(MessageResponse(**message))

return SmsResponse(message_count=response['message-count'], messages=messages)

def check_for_partial_failure(self, response_data):
def _check_for_partial_failure(self, response_data):
successful_messages = 0
total_messages = int(response_data['message-count'])

Expand All @@ -51,9 +46,41 @@ def check_for_partial_failure(self, response_data):
if successful_messages < total_messages:
raise PartialFailureError(response_data)

def check_for_error(self, response_data):
def _check_for_error(self, response_data):
message = response_data['messages'][0]
if int(message['status']) != 0:
raise SmsError(
f'Sms.send_message method failed with error code {message["status"]}: {message["error-text"]}'
)

@validate_call
def submit_sms_conversion(
self, message_id: str, delivered: bool = True, timestamp: datetime = None
):
"""
Note: Not available without having this feature manually enabled on your account.
Notifies Vonage that an SMS was successfully received.
This method is used to submit conversion data about SMS messages that were successfully delivered.
If you are using the Verify API for two-factor authentication (2FA), this information is sent to Vonage automatically,
so you do not need to use this method for 2FA messages.
Args:
message_id (str): The `message-id` returned by the `Sms.send` call.
delivered (bool, optional): Set to `True` if the user replied to the message you sent. Otherwise, set to `False`.
timestamp (datetime, optional): A `datetime` object containing the time the SMS arrived.
"""
params = {
'message-id': message_id,
'delivered': delivered,
'timestamp': (timestamp or datetime.now(timezone.utc)).strftime(
'%Y-%m-%d %H:%M:%S'
),
}
self._http_client.get(
self._http_client.api_host,
'/conversions/sms',
params,
self._auth_type,
)
12 changes: 12 additions & 0 deletions sms/tests/data/conversion_not_enabled.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 402 </title>
</head>
<body>
<h2>HTTP ERROR: 402</h2>
<p>Problem accessing /conversions/sms. Reason:
<pre> Bad Account Credentials</pre></p>
<hr /><i><small>Powered by Jetty://</small></i>
</body>
</html>
Empty file added sms/tests/data/null
Empty file.
14 changes: 13 additions & 1 deletion sms/tests/data/send_sms.json
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
{}
{
"message-count": "1",
"messages": [
{
"to": "1234567890",
"message-id": "3295d748-4e14-4681-af78-166dca3c5aab",
"status": "0",
"remaining-balance": "38.07243628",
"message-price": "0.04120000",
"network": "23420"
}
]
}
9 changes: 9 additions & 0 deletions sms/tests/data/send_sms_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"message-count": "1",
"messages": [
{
"status": "7",
"error-text": "Number barred."
}
]
}
17 changes: 17 additions & 0 deletions sms/tests/data/send_sms_partial_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"message-count": "2",
"messages": [
{
"to": "1234567890",
"message-id": "3295d748-4e14-4681-af78-166dca3c5aab",
"status": "0",
"remaining-balance": "38.07243628",
"message-price": "0.04120000",
"network": "23420"
},
{
"status": "1",
"error-text": "Throttled"
}
]
}
Loading

0 comments on commit b547a95

Please sign in to comment.