Skip to content

Commit 61718d6

Browse files
committed
finish NI module
1 parent 8c5b806 commit 61718d6

File tree

13 files changed

+318
-11
lines changed

13 files changed

+318
-11
lines changed

number_insight/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Vonage Number Insight Package
22

3-
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.
3+
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.
4+
5+
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.
46

57
## Usage
68

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
from . import errors
12
from .number_insight import NumberInsight
3+
from .requests import (
4+
AdvancedAsyncInsightRequest,
5+
AdvancedSyncInsightRequest,
6+
BasicInsightRequest,
7+
StandardInsightRequest,
8+
)
9+
from .responses import (
10+
AdvancedAsyncInsightResponse,
11+
AdvancedSyncInsightResponse,
12+
BasicInsightResponse,
13+
CallerIdentity,
14+
Carrier,
15+
RealTimeData,
16+
RoamingStatus,
17+
StandardInsightResponse,
18+
)
219

3-
__all__ = ['NumberInsight']
20+
__all__ = [
21+
'NumberInsight',
22+
'BasicInsightRequest',
23+
'StandardInsightRequest',
24+
'AdvancedAsyncInsightRequest',
25+
'AdvancedSyncInsightRequest',
26+
'BasicInsightResponse',
27+
'CallerIdentity',
28+
'Carrier',
29+
'RealTimeData',
30+
'RoamingStatus',
31+
'StandardInsightResponse',
32+
'AdvancedSyncInsightResponse',
33+
'AdvancedAsyncInsightResponse',
34+
'errors',
35+
]

number_insight/src/vonage_number_insight/number_insight.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from logging import getLogger
2+
13
from pydantic import validate_call
24
from vonage_http_client.http_client import HttpClient
35

@@ -15,6 +17,8 @@
1517
StandardInsightResponse,
1618
)
1719

20+
logger = getLogger('vonage_number_insight')
21+
1822

1923
class NumberInsight:
2024
"""Calls Vonage's Number Insight API."""
@@ -99,6 +103,7 @@ def advanced_async_number_insight(
99103
params=options.model_dump(exclude_none=True),
100104
auth_type=self._auth_type,
101105
)
106+
self._check_for_error(response)
102107

103108
return AdvancedAsyncInsightResponse(**response)
104109

@@ -122,6 +127,7 @@ def advanced_sync_number_insight(
122127
params=options.model_dump(exclude_none=True),
123128
auth_type=self._auth_type,
124129
)
130+
self._check_for_error(response)
125131

126132
return AdvancedSyncInsightResponse(**response)
127133

@@ -135,5 +141,13 @@ def _check_for_error(self, response: dict) -> None:
135141
NumberInsightError: If the response contains an error.
136142
"""
137143
if response['status'] != 0:
144+
if response['status'] in {43, 44, 45}:
145+
logger.warning(
146+
'Live mobile lookup not returned. Not all parameters are available.'
147+
)
148+
return
149+
logger.warning(
150+
f'Error using the Number Insight API. Response received: {response}'
151+
)
138152
error_message = f'Error with the following details: {response}'
139153
raise NumberInsightError(error_message)

number_insight/src/vonage_number_insight/responses.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional
1+
from typing import Literal, Optional, Union
22

33
from pydantic import BaseModel
44

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

1717

18+
class Carrier(BaseModel):
19+
network_code: Optional[str] = None
20+
name: Optional[str] = None
21+
country: Optional[str] = None
22+
network_type: Optional[str] = None
23+
24+
25+
class CallerIdentity(BaseModel):
26+
caller_type: Optional[str] = None
27+
caller_name: Optional[str] = None
28+
first_name: Optional[str] = None
29+
last_name: Optional[str] = None
30+
subscription_type: Optional[str] = None
31+
32+
1833
class StandardInsightResponse(BasicInsightResponse):
19-
...
34+
request_price: Optional[str] = None
35+
refund_price: Optional[str] = None
36+
remaining_balance: Optional[str] = None
37+
current_carrier: Optional[Carrier] = None
38+
original_carrier: Optional[Carrier] = None
39+
ported: Optional[str] = None
40+
caller_identity: Optional[CallerIdentity] = None
41+
caller_type: Optional[str] = None
42+
caller_name: Optional[str] = None
43+
first_name: Optional[str] = None
44+
last_name: Optional[str] = None
2045

2146

22-
class AdvancedAsyncInsightResponse(BaseModel):
23-
...
47+
class RoamingStatus(BaseModel):
48+
status: Optional[str] = None
49+
roaming_country_code: Optional[str] = None
50+
roaming_network_code: Optional[str] = None
51+
roaming_network_name: Optional[str] = None
52+
2453

54+
class RealTimeData(BaseModel):
55+
active_status: Optional[str] = None
56+
handset_status: Optional[str] = None
2557

26-
class AdvancedSyncInsightResponse(BaseModel):
27-
...
58+
59+
class AdvancedSyncInsightResponse(StandardInsightResponse):
60+
roaming: Optional[Union[RoamingStatus, Literal['unknown']]] = None
61+
lookup_outcome: Optional[int] = None
62+
lookup_outcome_message: Optional[str] = None
63+
valid_number: Optional[str] = None
64+
reachable: Optional[str] = None
65+
real_time_data: Optional[RealTimeData] = None
66+
ip_warnings: Optional[str] = None
67+
68+
69+
class AdvancedAsyncInsightResponse(BaseModel):
70+
request_id: Optional[str] = None
71+
number: Optional[str] = None
72+
remaining_balance: Optional[str] = None
73+
request_price: Optional[str] = None
74+
status: Optional[int] = None
75+
error_text: Optional[str] = None
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"number": "447700900000",
3+
"remaining_balance": "32.92665294",
4+
"request_id": "434205b5-90ec-4ee2-a337-7b40d9683420",
5+
"request_price": "0.04000000",
6+
"status": 0
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"error_text": "Invalid credentials",
3+
"status": 4
4+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"error_text": "Live mobile lookup not returned",
3+
"status": 43,
4+
"number": "447700900000",
5+
"remaining_balance": "32.92665294",
6+
"request_id": "434205b5-90ec-4ee2-a337-7b40d9683420",
7+
"request_price": "0.04000000"
8+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"caller_identity": {
3+
"caller_name": "John Smith",
4+
"caller_type": "consumer",
5+
"first_name": "John",
6+
"last_name": "Smith",
7+
"subscription_type": "postpaid"
8+
},
9+
"caller_name": "John Smith",
10+
"caller_type": "consumer",
11+
"country_code": "US",
12+
"country_code_iso3": "USA",
13+
"country_name": "United States of America",
14+
"country_prefix": "1",
15+
"current_carrier": {
16+
"country": "US",
17+
"name": "AT&T Mobility",
18+
"network_code": "310090",
19+
"network_type": "mobile"
20+
},
21+
"first_name": "John",
22+
"international_format_number": "12345678900",
23+
"ip_warnings": "unknown",
24+
"last_name": "Smith",
25+
"lookup_outcome": 1,
26+
"lookup_outcome_message": "Partial success - some fields populated",
27+
"national_format_number": "(234) 567-8900",
28+
"original_carrier": {
29+
"country": "US",
30+
"name": "AT&T Mobility",
31+
"network_code": "310090",
32+
"network_type": "mobile"
33+
},
34+
"ported": "not_ported",
35+
"reachable": "unknown",
36+
"refund_price": "0.01025000",
37+
"remaining_balance": "32.68590294",
38+
"request_id": "97e973e7-2e27-4fd3-9e1a-972ea14dd992",
39+
"request_price": "0.05025000",
40+
"roaming": "unknown",
41+
"status": 44,
42+
"status_message": "Lookup Handler unable to handle request",
43+
"valid_number": "valid"
44+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"status": 0,
3+
"status_message": "Success",
4+
"request_id": "1d56406b-9d52-497a-a023-b3f40b62f9b3",
5+
"international_format_number": "447700900000",
6+
"national_format_number": "07700 900000",
7+
"country_code": "GB",
8+
"country_code_iso3": "GBR",
9+
"country_name": "United Kingdom",
10+
"country_prefix": "44",
11+
"request_price": "0.00500000",
12+
"remaining_balance": "32.98665294",
13+
"current_carrier": {
14+
"network_code": "23415",
15+
"name": "Vodafone Limited",
16+
"country": "GB",
17+
"network_type": "mobile"
18+
},
19+
"original_carrier": {
20+
"network_code": "23420",
21+
"name": "Hutchison 3G Ltd",
22+
"country": "GB",
23+
"network_type": "mobile"
24+
},
25+
"ported": "ported",
26+
"caller_identity": {
27+
"caller_type": "consumer",
28+
"caller_name": "John Smith",
29+
"first_name": "John",
30+
"last_name": "Smith"
31+
},
32+
"caller_name": "John Smith",
33+
"last_name": "Smith",
34+
"first_name": "John",
35+
"caller_type": "consumer"
36+
}

number_insight/tests/test_number_insight.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from vonage_http_client.http_client import HttpClient
66
from vonage_number_insight.errors import NumberInsightError
77
from vonage_number_insight.number_insight import NumberInsight
8-
from vonage_number_insight.requests import BasicInsightRequest
8+
from vonage_number_insight.requests import (
9+
AdvancedAsyncInsightRequest,
10+
AdvancedSyncInsightRequest,
11+
BasicInsightRequest,
12+
StandardInsightRequest,
13+
)
914

1015
from testutils import build_response, get_mock_api_key_auth
1116

@@ -47,4 +52,107 @@ def test_basic_insight_error():
4752
options = BasicInsightRequest(number='1234567890', country_code='US')
4853
number_insight.basic_number_insight(options)
4954
assert e.match('Invalid request :: Not valid number format detected')
50-
assert 0
55+
56+
57+
@responses.activate
58+
def test_standard_insight():
59+
build_response(
60+
path,
61+
'GET',
62+
'https://api.nexmo.com/ni/standard/json',
63+
'standard_insight.json',
64+
)
65+
options = StandardInsightRequest(number='12345678900', country_code='US', cnam=True)
66+
response = number_insight.standard_number_insight(options)
67+
assert response.status == 0
68+
assert response.status_message == 'Success'
69+
assert response.current_carrier.network_code == '23415'
70+
assert response.original_carrier.network_type == 'mobile'
71+
assert response.caller_identity.caller_name == 'John Smith'
72+
73+
74+
@responses.activate
75+
def test_advanced_async_insight():
76+
build_response(
77+
path,
78+
'GET',
79+
'https://api.nexmo.com/ni/advanced/async/json',
80+
'advanced_async_insight.json',
81+
)
82+
options = AdvancedAsyncInsightRequest(
83+
callback='https://example.com/callback',
84+
number='447700900000',
85+
country_code='GB',
86+
cnam=True,
87+
)
88+
response = number_insight.advanced_async_number_insight(options)
89+
assert response.status == 0
90+
assert response.request_id == '434205b5-90ec-4ee2-a337-7b40d9683420'
91+
assert response.number == '447700900000'
92+
assert response.remaining_balance == '32.92665294'
93+
94+
95+
@responses.activate
96+
def test_advanced_async_insight_error():
97+
build_response(
98+
path,
99+
'GET',
100+
'https://api.nexmo.com/ni/advanced/async/json',
101+
'advanced_async_insight_error.json',
102+
)
103+
104+
options = AdvancedAsyncInsightRequest(
105+
callback='https://example.com/callback',
106+
number='447700900000',
107+
country_code='GB',
108+
cnam=True,
109+
)
110+
with raises(NumberInsightError) as e:
111+
number_insight.advanced_async_number_insight(options)
112+
assert e.match('Invalid credentials')
113+
114+
115+
@responses.activate
116+
def test_advanced_async_insight_partial_error(caplog):
117+
build_response(
118+
path,
119+
'GET',
120+
'https://api.nexmo.com/ni/advanced/async/json',
121+
'advanced_async_insight_partial_error.json',
122+
)
123+
124+
options = AdvancedAsyncInsightRequest(
125+
callback='https://example.com/callback',
126+
number='447700900000',
127+
country_code='GB',
128+
cnam=True,
129+
)
130+
response = number_insight.advanced_async_number_insight(options)
131+
assert 'Not all parameters are available' in caplog.text
132+
assert response.status == 43
133+
134+
135+
@responses.activate
136+
def test_advanced_sync_insight(caplog):
137+
build_response(
138+
path,
139+
'GET',
140+
'https://api.nexmo.com/ni/advanced/json',
141+
'advanced_sync_insight.json',
142+
)
143+
options = AdvancedSyncInsightRequest(
144+
number='12345678900', country_code='US', cnam=True, real_time_data=True
145+
)
146+
response = number_insight.advanced_sync_number_insight(options)
147+
148+
assert 'Not all parameters are available' in caplog.text
149+
assert response.status == 44
150+
assert response.request_id == '97e973e7-2e27-4fd3-9e1a-972ea14dd992'
151+
assert response.current_carrier.network_code == '310090'
152+
assert response.first_name == 'John'
153+
assert response.last_name == 'Smith'
154+
assert response.lookup_outcome == 1
155+
assert response.lookup_outcome_message == 'Partial success - some fields populated'
156+
assert response.roaming == 'unknown'
157+
assert response.status_message == 'Lookup Handler unable to handle request'
158+
assert response.valid_number == 'valid'

0 commit comments

Comments
 (0)