Skip to content

Commit

Permalink
finish NI module
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkahan committed Apr 25, 2024
1 parent 8c5b806 commit 61718d6
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 11 deletions.
4 changes: 3 additions & 1 deletion number_insight/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Vonage Number Insight Package

This package contains the code to use [Vonage's Number Insight API](https://developer.vonage.com/en/number-insight/overview) in Python. This package includes methods to get information about phone numbers. It has 3 levels of insight, basic, standard, and advanced.
This package contains the code to use [Vonage's Number Insight API](https://developer.vonage.com/en/number-insight/overview) in Python. This package includes methods to get information about phone numbers. It has 3 levels of insight: basic, standard, and advanced.

The advanced insight can be obtained synchronously or asynchronously. An async approach is recommended to avoid timeouts. Optionally, you can get caller name information (additional charge) by passing the `cnam` parameter to a standard or advanced insight request.

## Usage

Expand Down
34 changes: 33 additions & 1 deletion number_insight/src/vonage_number_insight/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
from . import errors
from .number_insight import NumberInsight
from .requests import (
AdvancedAsyncInsightRequest,
AdvancedSyncInsightRequest,
BasicInsightRequest,
StandardInsightRequest,
)
from .responses import (
AdvancedAsyncInsightResponse,
AdvancedSyncInsightResponse,
BasicInsightResponse,
CallerIdentity,
Carrier,
RealTimeData,
RoamingStatus,
StandardInsightResponse,
)

__all__ = ['NumberInsight']
__all__ = [
'NumberInsight',
'BasicInsightRequest',
'StandardInsightRequest',
'AdvancedAsyncInsightRequest',
'AdvancedSyncInsightRequest',
'BasicInsightResponse',
'CallerIdentity',
'Carrier',
'RealTimeData',
'RoamingStatus',
'StandardInsightResponse',
'AdvancedSyncInsightResponse',
'AdvancedAsyncInsightResponse',
'errors',
]
14 changes: 14 additions & 0 deletions number_insight/src/vonage_number_insight/number_insight.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from logging import getLogger

from pydantic import validate_call
from vonage_http_client.http_client import HttpClient

Expand All @@ -15,6 +17,8 @@
StandardInsightResponse,
)

logger = getLogger('vonage_number_insight')


class NumberInsight:
"""Calls Vonage's Number Insight API."""
Expand Down Expand Up @@ -99,6 +103,7 @@ def advanced_async_number_insight(
params=options.model_dump(exclude_none=True),
auth_type=self._auth_type,
)
self._check_for_error(response)

return AdvancedAsyncInsightResponse(**response)

Expand All @@ -122,6 +127,7 @@ def advanced_sync_number_insight(
params=options.model_dump(exclude_none=True),
auth_type=self._auth_type,
)
self._check_for_error(response)

return AdvancedSyncInsightResponse(**response)

Expand All @@ -135,5 +141,13 @@ def _check_for_error(self, response: dict) -> None:
NumberInsightError: If the response contains an error.
"""
if response['status'] != 0:
if response['status'] in {43, 44, 45}:
logger.warning(
'Live mobile lookup not returned. Not all parameters are available.'
)
return
logger.warning(
f'Error using the Number Insight API. Response received: {response}'
)
error_message = f'Error with the following details: {response}'
raise NumberInsightError(error_message)
60 changes: 54 additions & 6 deletions number_insight/src/vonage_number_insight/responses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Literal, Optional, Union

from pydantic import BaseModel

Expand All @@ -15,13 +15,61 @@ class BasicInsightResponse(BaseModel):
country_prefix: Optional[str] = None


class Carrier(BaseModel):
network_code: Optional[str] = None
name: Optional[str] = None
country: Optional[str] = None
network_type: Optional[str] = None


class CallerIdentity(BaseModel):
caller_type: Optional[str] = None
caller_name: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
subscription_type: Optional[str] = None


class StandardInsightResponse(BasicInsightResponse):
...
request_price: Optional[str] = None
refund_price: Optional[str] = None
remaining_balance: Optional[str] = None
current_carrier: Optional[Carrier] = None
original_carrier: Optional[Carrier] = None
ported: Optional[str] = None
caller_identity: Optional[CallerIdentity] = None
caller_type: Optional[str] = None
caller_name: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None


class AdvancedAsyncInsightResponse(BaseModel):
...
class RoamingStatus(BaseModel):
status: Optional[str] = None
roaming_country_code: Optional[str] = None
roaming_network_code: Optional[str] = None
roaming_network_name: Optional[str] = None


class RealTimeData(BaseModel):
active_status: Optional[str] = None
handset_status: Optional[str] = None

class AdvancedSyncInsightResponse(BaseModel):
...

class AdvancedSyncInsightResponse(StandardInsightResponse):
roaming: Optional[Union[RoamingStatus, Literal['unknown']]] = None
lookup_outcome: Optional[int] = None
lookup_outcome_message: Optional[str] = None
valid_number: Optional[str] = None
reachable: Optional[str] = None
real_time_data: Optional[RealTimeData] = None
ip_warnings: Optional[str] = None


class AdvancedAsyncInsightResponse(BaseModel):
request_id: Optional[str] = None
number: Optional[str] = None
remaining_balance: Optional[str] = None
request_price: Optional[str] = None
status: Optional[int] = None
error_text: Optional[str] = None
7 changes: 7 additions & 0 deletions number_insight/tests/data/advanced_async_insight.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"number": "447700900000",
"remaining_balance": "32.92665294",
"request_id": "434205b5-90ec-4ee2-a337-7b40d9683420",
"request_price": "0.04000000",
"status": 0
}
4 changes: 4 additions & 0 deletions number_insight/tests/data/advanced_async_insight_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"error_text": "Invalid credentials",
"status": 4
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"error_text": "Live mobile lookup not returned",
"status": 43,
"number": "447700900000",
"remaining_balance": "32.92665294",
"request_id": "434205b5-90ec-4ee2-a337-7b40d9683420",
"request_price": "0.04000000"
}
44 changes: 44 additions & 0 deletions number_insight/tests/data/advanced_sync_insight.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"caller_identity": {
"caller_name": "John Smith",
"caller_type": "consumer",
"first_name": "John",
"last_name": "Smith",
"subscription_type": "postpaid"
},
"caller_name": "John Smith",
"caller_type": "consumer",
"country_code": "US",
"country_code_iso3": "USA",
"country_name": "United States of America",
"country_prefix": "1",
"current_carrier": {
"country": "US",
"name": "AT&T Mobility",
"network_code": "310090",
"network_type": "mobile"
},
"first_name": "John",
"international_format_number": "12345678900",
"ip_warnings": "unknown",
"last_name": "Smith",
"lookup_outcome": 1,
"lookup_outcome_message": "Partial success - some fields populated",
"national_format_number": "(234) 567-8900",
"original_carrier": {
"country": "US",
"name": "AT&T Mobility",
"network_code": "310090",
"network_type": "mobile"
},
"ported": "not_ported",
"reachable": "unknown",
"refund_price": "0.01025000",
"remaining_balance": "32.68590294",
"request_id": "97e973e7-2e27-4fd3-9e1a-972ea14dd992",
"request_price": "0.05025000",
"roaming": "unknown",
"status": 44,
"status_message": "Lookup Handler unable to handle request",
"valid_number": "valid"
}
36 changes: 36 additions & 0 deletions number_insight/tests/data/standard_insight.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"status": 0,
"status_message": "Success",
"request_id": "1d56406b-9d52-497a-a023-b3f40b62f9b3",
"international_format_number": "447700900000",
"national_format_number": "07700 900000",
"country_code": "GB",
"country_code_iso3": "GBR",
"country_name": "United Kingdom",
"country_prefix": "44",
"request_price": "0.00500000",
"remaining_balance": "32.98665294",
"current_carrier": {
"network_code": "23415",
"name": "Vodafone Limited",
"country": "GB",
"network_type": "mobile"
},
"original_carrier": {
"network_code": "23420",
"name": "Hutchison 3G Ltd",
"country": "GB",
"network_type": "mobile"
},
"ported": "ported",
"caller_identity": {
"caller_type": "consumer",
"caller_name": "John Smith",
"first_name": "John",
"last_name": "Smith"
},
"caller_name": "John Smith",
"last_name": "Smith",
"first_name": "John",
"caller_type": "consumer"
}
112 changes: 110 additions & 2 deletions number_insight/tests/test_number_insight.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from vonage_http_client.http_client import HttpClient
from vonage_number_insight.errors import NumberInsightError
from vonage_number_insight.number_insight import NumberInsight
from vonage_number_insight.requests import BasicInsightRequest
from vonage_number_insight.requests import (
AdvancedAsyncInsightRequest,
AdvancedSyncInsightRequest,
BasicInsightRequest,
StandardInsightRequest,
)

from testutils import build_response, get_mock_api_key_auth

Expand Down Expand Up @@ -47,4 +52,107 @@ def test_basic_insight_error():
options = BasicInsightRequest(number='1234567890', country_code='US')
number_insight.basic_number_insight(options)
assert e.match('Invalid request :: Not valid number format detected')
assert 0


@responses.activate
def test_standard_insight():
build_response(
path,
'GET',
'https://api.nexmo.com/ni/standard/json',
'standard_insight.json',
)
options = StandardInsightRequest(number='12345678900', country_code='US', cnam=True)
response = number_insight.standard_number_insight(options)
assert response.status == 0
assert response.status_message == 'Success'
assert response.current_carrier.network_code == '23415'
assert response.original_carrier.network_type == 'mobile'
assert response.caller_identity.caller_name == 'John Smith'


@responses.activate
def test_advanced_async_insight():
build_response(
path,
'GET',
'https://api.nexmo.com/ni/advanced/async/json',
'advanced_async_insight.json',
)
options = AdvancedAsyncInsightRequest(
callback='https://example.com/callback',
number='447700900000',
country_code='GB',
cnam=True,
)
response = number_insight.advanced_async_number_insight(options)
assert response.status == 0
assert response.request_id == '434205b5-90ec-4ee2-a337-7b40d9683420'
assert response.number == '447700900000'
assert response.remaining_balance == '32.92665294'


@responses.activate
def test_advanced_async_insight_error():
build_response(
path,
'GET',
'https://api.nexmo.com/ni/advanced/async/json',
'advanced_async_insight_error.json',
)

options = AdvancedAsyncInsightRequest(
callback='https://example.com/callback',
number='447700900000',
country_code='GB',
cnam=True,
)
with raises(NumberInsightError) as e:
number_insight.advanced_async_number_insight(options)
assert e.match('Invalid credentials')


@responses.activate
def test_advanced_async_insight_partial_error(caplog):
build_response(
path,
'GET',
'https://api.nexmo.com/ni/advanced/async/json',
'advanced_async_insight_partial_error.json',
)

options = AdvancedAsyncInsightRequest(
callback='https://example.com/callback',
number='447700900000',
country_code='GB',
cnam=True,
)
response = number_insight.advanced_async_number_insight(options)
assert 'Not all parameters are available' in caplog.text
assert response.status == 43


@responses.activate
def test_advanced_sync_insight(caplog):
build_response(
path,
'GET',
'https://api.nexmo.com/ni/advanced/json',
'advanced_sync_insight.json',
)
options = AdvancedSyncInsightRequest(
number='12345678900', country_code='US', cnam=True, real_time_data=True
)
response = number_insight.advanced_sync_number_insight(options)

assert 'Not all parameters are available' in caplog.text
assert response.status == 44
assert response.request_id == '97e973e7-2e27-4fd3-9e1a-972ea14dd992'
assert response.current_carrier.network_code == '310090'
assert response.first_name == 'John'
assert response.last_name == 'Smith'
assert response.lookup_outcome == 1
assert response.lookup_outcome_message == 'Partial success - some fields populated'
assert response.roaming == 'unknown'
assert response.status_message == 'Lookup Handler unable to handle request'
assert response.valid_number == 'valid'
Loading

0 comments on commit 61718d6

Please sign in to comment.