Skip to content

Commit

Permalink
adding start verification methods and starting check_code
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkahan committed Apr 3, 2024
1 parent b176787 commit dae7232
Show file tree
Hide file tree
Showing 15 changed files with 227 additions and 114 deletions.
3 changes: 3 additions & 0 deletions http_client/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 1.2.0
- Add `last_request` and `last_response` properties

# 1.1.1
- Add new Patch method
- New input fields for different ways to pass data in a request
Expand Down
27 changes: 25 additions & 2 deletions http_client/src/vonage_http_client/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Literal, Optional, Union

from pydantic import BaseModel, Field, ValidationError, validate_call
from requests import Response
from requests import PreparedRequest, Response
from requests.adapters import HTTPAdapter
from requests.sessions import Session
from typing_extensions import Annotated
Expand Down Expand Up @@ -81,6 +81,9 @@ def __init__(
self._user_agent = f'vonage-python-sdk/{sdk_version} python/{python_version()}'
self._headers = {'User-Agent': self._user_agent, 'Accept': 'application/json'}

self._last_request = None
self._last_response = None

@property
def auth(self):
return self._auth
Expand All @@ -101,6 +104,24 @@ def rest_host(self):
def user_agent(self):
return self._user_agent

@property
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.
"""
return self._last_response.request

@property
def last_response(self) -> Optional[Response]:
"""The last response received from the server.
Returns:
Optional[Response]: The response object received from the server.
"""
return self._last_response

def post(
self,
host: str,
Expand All @@ -119,7 +140,7 @@ def get(
request_path: str = '',
params: dict = None,
auth_type: Literal['jwt', 'basic', 'body', 'signature'] = 'jwt',
sent_data_type: Literal['json', 'form', 'query_params'] = 'json',
sent_data_type: Literal['json', 'form', 'query_params'] = 'query_params',
) -> Union[dict, None]:
return self.make_request(
'GET', host, request_path, params, auth_type, sent_data_type
Expand Down Expand Up @@ -199,6 +220,8 @@ def _parse_response(self, response: Response) -> Union[dict, None]:
logger.debug(
f'Response received from {response.url} with status code: {response.status_code}; headers: {response.headers}'
)
self._last_response = response

content_type = response.headers['Content-Type'].split(';', 1)[0]
if 200 <= response.status_code < 300:
if response.status_code == 204:
Expand Down
19 changes: 13 additions & 6 deletions http_client/tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import responses
from pytest import raises
from requests import Response
from requests import PreparedRequest, Response
from responses import matchers
from vonage_http_client.auth import Auth
from vonage_http_client.errors import (
Expand Down Expand Up @@ -55,7 +55,7 @@ def test_create_http_client_invalid_options_error():


@responses.activate
def test_make_get_request():
def test_make_get_request_and_last_request_and_response():
build_response(
path, 'GET', 'https://example.com/get_json?key=value', 'example_get.json'
)
Expand All @@ -64,15 +64,22 @@ def test_make_get_request():
http_client_options={'api_host': 'example.com'},
)
res = client.get(
host='example.com',
request_path='/get_json',
params={'key': 'value'},
sent_data_type='query_params',
host='example.com', request_path='/get_json', params={'key': 'value'}
)

assert res['hello'] == 'world'
assert responses.calls[0].request.headers['User-Agent'] == client._user_agent

assert type(client.last_request) == PreparedRequest
assert client.last_request.method == 'GET'
assert client.last_request.url == 'https://example.com/get_json?key=value'
assert client.last_request.body == None

assert type(client.last_response) == Response
assert client.last_response.status_code == 200
assert client.last_response.json() == res
assert client.last_response.headers == {'Content-Type': 'application/json'}


@responses.activate
def test_make_get_request_no_content():
Expand Down
3 changes: 3 additions & 0 deletions users/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# 1.0.1
- Internal refactoring

# 1.0.0
- Initial upload
1 change: 0 additions & 1 deletion users/src/vonage_users/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def list_users(
'/v1/users',
params.model_dump(exclude_none=True),
self._auth_type,
'query_params',
)

users_response = ListUsersResponse(**response)
Expand Down
13 changes: 1 addition & 12 deletions verify/src/vonage_verify/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,4 @@


class VerifyError(VonageError):
"""Indicates an error with the Vonage Verify Package."""


# class PartialFailureError(SmsError):
# """Indicates that a request was partially successful."""

# def __init__(self, response: Response):
# self.message = (
# 'Sms.send_message method partially failed. Not all of the message(s) sent successfully.',
# )
# super().__init__(self.message)
# self.response = response
"""Indicates an error when using the Vonage Verify API."""
12 changes: 6 additions & 6 deletions verify/src/vonage_verify/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ class BaseVerifyRequest(BaseModel):

number: PhoneNumber
country: Optional[str] = Field(None, max_length=2)
code_length: Optional[Literal[4, 6]] = 4
code_length: Optional[Literal[4, 6]] = None
pin_expiry: Optional[int] = Field(None, ge=60, le=3600)
next_event_wait: Optional[int] = Field(None, ge=60, le=900)
workflow_id: Optional[Literal[1, 2, 3, 4, 5, 6, 7]] = None
workflow_id: Optional[int] = Field(None, ge=1, le=7)

@model_validator(mode='after')
def check_expiry_and_next_event_timing(self):
if self.pin_expiry is None or self.next_event_wait is None:
return self
if self.pin_expiry % self.next_event_wait != 0:
logger.debug(
logger.warning(
f'The pin_expiry should be a multiple of next_event_wait.'
f'The current values are: pin_expiry={self.pin_expiry}, next_event_wait={self.next_event_wait}.'
f'The value of pin_expiry will be set to next_event_wait.'
f'\nThe current values are: pin_expiry={self.pin_expiry}, next_event_wait={self.next_event_wait}.'
f'\nThe value of pin_expiry will be set to next_event_wait.'
)
self.pin_expiry = self.next_event_wait
return self
Expand All @@ -40,7 +40,7 @@ class VerifyRequest(BaseVerifyRequest):
"""

brand: str = Field(..., max_length=18)
sender_id: Optional[str] = Field('VERIFY', max_length=11)
sender_id: Optional[str] = Field(None, max_length=11)
lg: Optional[LanguageCode] = None
pin_code: Optional[str] = Field(None, min_length=4, max_length=10)

Expand Down
13 changes: 12 additions & 1 deletion verify/src/vonage_verify/responses.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
from typing import Optional

from pydantic import BaseModel


class VerifyResponse(BaseModel):
class StartVerificationResponse(BaseModel):
request_id: str
status: str


class CheckCodeResponse(BaseModel):
request_id: str
status: str
event_id: str
price: str
currency: str
estimated_price_messages_sent: Optional[str] = None


# class MessageResponse(BaseModel):
Expand Down
168 changes: 88 additions & 80 deletions verify/src/vonage_verify/verify.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,115 @@
from pydantic import validate_call
from vonage_http_client.http_client import HttpClient

from .errors import VerifyError
from .requests import BaseVerifyRequest, Psd2Request, VerifyRequest
from .responses import VerifyResponse
from .responses import CheckCodeResponse, StartVerificationResponse


class Verify:
"""Calls Vonage's Verify API."""
"""Calls Vonage's Verify API.
This class provides methods to interact with Vonage's Verify API for starting verification
processes.
"""

def __init__(self, http_client: HttpClient) -> None:
self._http_client = http_client
self._sent_post_data_type = 'form'
self._sent_get_data_type = 'query_params'
self._sent_data_type = 'form'
self._auth_type = 'body'

@validate_call
def start_verification(self, verify_request: VerifyRequest) -> VerifyResponse:
"""Start a verification process."""
def start_verification(
self, verify_request: VerifyRequest
) -> StartVerificationResponse:
"""Start a verification process.
Args:
verify_request (VerifyRequest): The verification request object.
Returns:
StartVerificationResponse: The response object containing the verification result.
"""
return self._make_verify_request(verify_request)

@validate_call
def start_psd2_verification(self, verify_request: Psd2Request) -> VerifyResponse:
"""Start a PSD2 verification process."""
def start_psd2_verification(
self, verify_request: Psd2Request
) -> StartVerificationResponse:
"""Start a PSD2 verification process.
Args:
verify_request (Psd2Request): The PSD2 verification request object.
Returns:
StartVerificationResponse: The response object containing the verification result.
"""
return self._make_verify_request(verify_request)

def _make_verify_request(self, verify_request: BaseVerifyRequest) -> VerifyResponse:
@validate_call
def check_code(self, request_id: str, code: str) -> CheckCodeResponse:
"""Check a verification code.
Args:
request_id (str): The request ID.
code (str): The verification code.
Returns:
CheckCodeResponse: The response object containing the verification result.
"""
response = self._http_client.post(
self._http_client.api_host,
'/verify/check/json',
{'request_id': request_id, 'code': code},
self._auth_type,
self._sent_data_type,
)
self._check_for_error(response)
return CheckCodeResponse(**response)

def _make_verify_request(
self, verify_request: BaseVerifyRequest
) -> StartVerificationResponse:
"""Make a verify request.
This method makes a verify request to the Vonage Verify API.
Args:
verify_request (BaseVerifyRequest): The verify request object.
Returns:
VerifyResponse: The response object containing the verification result.
"""
if type(verify_request) == VerifyRequest:
request_path = '/verify/json'
elif type(verify_request) == Psd2Request:
request_path = '/verify/psd2/json'

response = self._http_client.post(
self._http_client.api_host,
request_path,
verify_request.model_dump(by_alias=True),
verify_request.model_dump(by_alias=True, exclude_none=True),
self._auth_type,
self._sent_post_data_type,
self._sent_data_type,
)
return VerifyResponse(**response)

# @validate_call
# def send(self, message: SmsMessage) -> SmsResponse:
# """Send an SMS message."""
# response = self._http_client.post(
# self._http_client.rest_host,
# '/sms/json',
# message.model_dump(by_alias=True),
# self._auth_type,
# self._sent_data_type,
# )

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

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

# for message in response_data['messages']:
# if message['status'] == '0':
# successful_messages += 1
# if successful_messages < total_messages:
# raise PartialFailureError(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.post(
# self._http_client.api_host,
# '/conversions/sms',
# params,
# self._auth_type,
# self._sent_data_type,
# )
self._check_for_error(response)
parsed_response = StartVerificationResponse(**response)

return parsed_response

def _check_for_error(self, response: dict) -> None:
"""Check for error in the response.
This method checks if the response contains an error and raises a VerifyError if an error is found.
Args:
response (dict): The response object.
Raises:
VerifyError: If an error is found in the response.
"""
print(self._http_client.last_request.body)
if int(response['status']) != 0:
error_message = f'Error with Vonage status code {response["status"]}: {response["error_text"]}.'
if 'network' in response:
error_message += f' Network ID: {response["network"]}'
raise VerifyError(error_message)
Empty file.
Loading

0 comments on commit dae7232

Please sign in to comment.