Skip to content

Commit

Permalink
refactored Client.parse method, new custom error class, now returning…
Browse files Browse the repository at this point in the history
… full error details, fixing all tests...
  • Loading branch information
maxkahan committed Aug 26, 2023
1 parent 5692c65 commit f94080f
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 870 deletions.
35 changes: 14 additions & 21 deletions src/vonage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,44 +316,37 @@ def send_request(
),
)

def parse(self, host, response: Response):
def parse(self, host: str, response: Response):
logger.debug(f'Response headers {repr(response.headers)}')

if response.status_code == 401:
raise AuthenticationError('Authentication failed.')
if response.status_code == 204:
return None
if 200 <= response.status_code < 300:

try:
content_type = response.headers['Content-Type'].split(';', 1)[0]
except KeyError:
raise ClientError(response, None, host)

if 200 <= response.status_code < 300:
if content_type == "application/json":
try:
return response.json()
except JSONDecodeError as err: # Get this when we get a 202 with no content
except JSONDecodeError: # Get this when we get a 202 with no content
return None
else:
return response.content
if 400 <= response.status_code < 500:
logger.warning(f"Client error: {response.status_code} {repr(response.content)}")
message = f"{response.status_code} response from {host}"
return response.text

try:
error_data = response.json()
message += str(error_data)
except JSONDecodeError:
pass
raise ClientError(message)
if 400 <= response.status_code < 500:
logger.warning(f'Client error: {response.status_code} {repr(response.content)}')
raise ClientError(response, content_type, host)

if 500 <= response.status_code < 600:
logger.warning(f"Server error: {response.status_code} {repr(response.content)}")
message = f"{response.status_code} response from {host}"
raise ServerError(message)

def _add_individual_errors(self, error_data):
message = ''
if 'errors' in error_data:
for error in error_data["errors"]:
message += f"\nError: {error}"
return message

def _create_jwt_auth_string(self):
return b"Bearer " + self.generate_application_jwt()

Expand All @@ -362,7 +355,7 @@ def generate_application_jwt(self):
return self._jwt_client.generate_application_jwt(self._jwt_claims)
except AttributeError as err:
if '_jwt_client' in str(err):
raise ClientError(
raise VonageError(
'JWT generation failed. Check that you passed in valid values for "application_id" and "private_key".'
)
else:
Expand Down
69 changes: 44 additions & 25 deletions src/vonage/errors.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,117 @@
class ClientError(Exception):
pass
from requests import Response


class ServerError(Exception):
pass
class VonageError(Exception):
"""Base Error Class for all Vonage SDK errors."""


class ServerError(VonageError):
"""Indicates an error was reported from a Vonage server."""


class ClientError(VonageError):
"""Indicates an error was recieved as part of the response from a Vonage SDK request."""

def __init__(self, response: Response, content_type: str, host: str):
if content_type == 'application/json':
self.body = response.json()
else:
self.body = response.text
if self.body:
super().__init__(
f'{response.status_code} response from {host}. \nError Message:\n{self.body}'
)
else:
super().__init__(f'{response.status_code} response from {host}.')


class AuthenticationError(ClientError):
class AuthenticationError(VonageError):
pass


class CallbackRequiredError(ClientError):
class CallbackRequiredError(VonageError):
"""Indicates a callback is required but was not present."""


class MessagesError(ClientError):
class MessagesError(VonageError):
"""
Indicates an error related to the Messages class which calls the Vonage Messages API.
"""


class SmsError(ClientError):
class SmsError(VonageError):
"""
Indicates an error related to the Sms class which calls the Vonage SMS API.
"""


class PartialFailureError(ClientError):
class PartialFailureError(VonageError):
"""
Indicates that one or more parts of the message was not sent successfully.
"""


class PricingTypeError(ClientError):
class PricingTypeError(VonageError):
"""A pricing type was specified that is not allowed."""


class InvalidAuthenticationTypeError(ClientError):
class InvalidAuthenticationTypeError(VonageError):
"""An authentication method was specified that is not allowed."""


class InvalidRoleError(ClientError):
class InvalidRoleError(VonageError):
"""The specified role was invalid."""


class TokenExpiryError(ClientError):
class TokenExpiryError(VonageError):
"""The specified token expiry time was invalid."""


class InvalidOptionsError(ClientError):
class InvalidOptionsError(VonageError):
"""The option(s) that were specified are invalid."""

"""An authentication method was specified that is not allowed."""


class VerifyError(ClientError):
class VerifyError(VonageError):
"""Error related to the Verify API."""


class BlockedNumberError(ClientError):
class BlockedNumberError(VonageError):
"""The number you are trying to verify is blocked for verification."""


class NumberInsightError(ClientError):
class NumberInsightError(VonageError):
"""Error related to the Number Insight API."""


class SipError(ClientError):
class SipError(VonageError):
"""Error related to usage of SIP calls."""


class InvalidInputError(ClientError):
class InvalidInputError(VonageError):
"""The input that was provided was invalid."""


class InvalidAuthenticationTypeError(ClientError):
class InvalidAuthenticationTypeError(VonageError):
"""An authentication method was specified that is not allowed."""


class MeetingsError(ClientError):
class MeetingsError(VonageError):
"""An error related to the Meetings class which calls the Vonage Meetings API."""


class Verify2Error(ClientError):
class Verify2Error(VonageError):
"""An error relating to the Verify (V2) API."""


class SubaccountsError(ClientError):
class SubaccountsError(VonageError):
"""An error relating to the Subaccounts API."""


class ProactiveConnectError(ClientError):
class ProactiveConnectError(VonageError):
"""An error relating to the Proactive Connect API."""


class UsersError(ClientError):
class UsersError(VonageError):
"""An error relating to the Users API."""
74 changes: 0 additions & 74 deletions tests/data/meetings/multiple_rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,80 +75,6 @@
"is_locale_switcher_available": false
}
},
{
"id": "d44529db-d1fa-48d5-bba0-43034bf91ae4",
"display_name": "my_test_room",
"metadata": null,
"type": "instant",
"expires_at": "2023-01-24T03:28:32.740Z",
"recording_options": {
"auto_record": false,
"record_only_owner": false
},
"meeting_code": "659359326",
"_links": {
"host_url": {
"href": "https://meetings.vonage.com/?room_token=659359326&participant_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5N2NmYTAzLTY3NTQtNGE0ZC1hYjU1LWZiMTdkNzc4NzRjMSJ9.eyJwYXJ0aWNpcGFudElkIjoiNjU5MjZjMTUtMzcwYi00YjlmLWI2MDMtZjZkODFlNzIxNWFkIiwiaWF0IjoxNjc0NTcyODg1fQ.TYjAbWOYdlt7UsjyQh-Y7Qr0hfWElIDrJQTNrOQuLSg"
},
"guest_url": {
"href": "https://meetings.vonage.com/659359326"
}
},
"created_at": "2023-01-24T03:18:32.741Z",
"is_available": true,
"expire_after_use": false,
"theme_id": null,
"initial_join_options": {
"microphone_state": "default"
},
"join_approval_level": "none",
"ui_settings": {
"language": "default"
},
"available_features": {
"is_recording_available": true,
"is_chat_available": true,
"is_whiteboard_available": true,
"is_locale_switcher_available": false
}
},
{
"id": "4f7dc750-6049-42ef-a25f-e7afa4953e32",
"display_name": "my_test_room",
"metadata": null,
"type": "instant",
"expires_at": "2023-01-24T03:30:21.506Z",
"recording_options": {
"auto_record": false,
"record_only_owner": false
},
"meeting_code": "752928832",
"_links": {
"host_url": {
"href": "https://meetings.vonage.com/?room_token=752928832&participant_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5N2NmYTAzLTY3NTQtNGE0ZC1hYjU1LWZiMTdkNzc4NzRjMSJ9.eyJwYXJ0aWNpcGFudElkIjoiMTMzYTY1MTctMDdhYS00NWUxLTg0OGMtNzVhZDM0YzUwODVkIiwiaWF0IjoxNjc0NTcyODg1fQ.afMtFPyLAgZvGsR66pPj0op7sgnNjfj4BHxhU1OP8_w"
},
"guest_url": {
"href": "https://meetings.vonage.com/752928832"
}
},
"created_at": "2023-01-24T03:20:21.508Z",
"is_available": true,
"expire_after_use": false,
"theme_id": null,
"initial_join_options": {
"microphone_state": "default"
},
"join_approval_level": "none",
"ui_settings": {
"language": "default"
},
"available_features": {
"is_recording_available": true,
"is_chat_available": true,
"is_whiteboard_available": true,
"is_locale_switcher_available": false
}
},
{
"id": "b3142c46-d1c1-4405-baa6-85683827ed69",
"display_name": "my_test_room",
Expand Down
21 changes: 5 additions & 16 deletions tests/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,8 @@ def test_list_secrets_missing(account):
with pytest.raises(vonage.ClientError) as ce:
account.list_secrets("myaccountid")
assert_basic_auth()
assert (
str(ce.value)
== """Invalid API Key: API key 'ABC123' does not exist, or you do not have access (https://developer.nexmo.com/api-errors#invalid-api-key)"""
)
print(ce)
assert 'Invalid API Key' in str(ce.value)


@responses.activate
Expand Down Expand Up @@ -155,10 +153,7 @@ def test_create_secret_max_secrets(account):
with pytest.raises(vonage.ClientError) as ce:
account.create_secret("meaccountid", "mahsecret")
assert_basic_auth()
assert (
str(ce.value)
== """Maxmimum number of secrets already met: This account has reached maximum number of '2' allowed secrets (https://developer.nexmo.com/api-errors/account/secret-management#maximum-secrets-allowed)"""
)
assert 'Maxmimum number of secrets already met' in str(ce.value)


@responses.activate
Expand All @@ -173,10 +168,7 @@ def test_create_secret_validation(account):
with pytest.raises(vonage.ClientError) as ce:
account.create_secret("meaccountid", "mahsecret")
assert_basic_auth()
assert (
str(ce.value)
== """Bad Request: The request failed due to validation errors (https://developer.nexmo.com/api-errors/account/secret-management#validation)"""
)
assert 'The request failed due to validation errors' in str(ce.value)


@responses.activate
Expand All @@ -198,7 +190,4 @@ def test_delete_secret_last_secret(account):
with pytest.raises(vonage.ClientError) as ce:
account.revoke_secret("meaccountid", "mahsecret")
assert_basic_auth()
assert (
str(ce.value)
== """Secret Deletion Forbidden: Can not delete the last secret. The account must always have at least 1 secret active at any time (https://developer.nexmo.com/api-errors/account/secret-management#delete-last-secret)"""
)
assert 'Secret Deletion Forbidden' in str(ce.value)
Loading

0 comments on commit f94080f

Please sign in to comment.