From 8c5b8069ac785fe0e14266453e18c5c7faa89e38 Mon Sep 17 00:00:00 2001 From: maxkahan Date: Wed, 24 Apr 2024 04:14:36 +0100 Subject: [PATCH] start adding number insight --- number_insight/BUILD | 16 ++ number_insight/CHANGES.md | 2 + number_insight/README.md | 9 ++ number_insight/pyproject.toml | 29 ++++ .../src/vonage_number_insight/BUILD | 1 + .../src/vonage_number_insight/__init__.py | 3 + .../src/vonage_number_insight/enums.py | 0 .../src/vonage_number_insight/errors.py | 5 + .../vonage_number_insight/number_insight.py | 139 ++++++++++++++++++ .../src/vonage_number_insight/requests.py | 21 +++ .../src/vonage_number_insight/responses.py | 27 ++++ number_insight/tests/BUILD | 1 + number_insight/tests/data/basic_insight.json | 11 ++ .../tests/data/basic_insight_error.json | 4 + number_insight/tests/test_number_insight.py | 50 +++++++ pants.toml | 1 + vonage/src/vonage/__init__.py | 2 + vonage/src/vonage/vonage.py | 2 + 18 files changed, 323 insertions(+) create mode 100644 number_insight/BUILD create mode 100644 number_insight/CHANGES.md create mode 100644 number_insight/README.md create mode 100644 number_insight/pyproject.toml create mode 100644 number_insight/src/vonage_number_insight/BUILD create mode 100644 number_insight/src/vonage_number_insight/__init__.py create mode 100644 number_insight/src/vonage_number_insight/enums.py create mode 100644 number_insight/src/vonage_number_insight/errors.py create mode 100644 number_insight/src/vonage_number_insight/number_insight.py create mode 100644 number_insight/src/vonage_number_insight/requests.py create mode 100644 number_insight/src/vonage_number_insight/responses.py create mode 100644 number_insight/tests/BUILD create mode 100644 number_insight/tests/data/basic_insight.json create mode 100644 number_insight/tests/data/basic_insight_error.json create mode 100644 number_insight/tests/test_number_insight.py diff --git a/number_insight/BUILD b/number_insight/BUILD new file mode 100644 index 00000000..b3212a5f --- /dev/null +++ b/number_insight/BUILD @@ -0,0 +1,16 @@ +resource(name='pyproject', source='pyproject.toml') +file(name='readme', source='README.md') + +files(sources=['tests/data/*']) + +python_distribution( + name='vonage-number-insight', + dependencies=[ + ':pyproject', + ':readme', + 'number_insight/src/vonage_number_insight', + ], + provides=python_artifact(), + generate_setup=False, + repositories=['@pypi'], +) diff --git a/number_insight/CHANGES.md b/number_insight/CHANGES.md new file mode 100644 index 00000000..be516a55 --- /dev/null +++ b/number_insight/CHANGES.md @@ -0,0 +1,2 @@ +# 1.0.0 +- Initial upload diff --git a/number_insight/README.md b/number_insight/README.md new file mode 100644 index 00000000..16d5325a --- /dev/null +++ b/number_insight/README.md @@ -0,0 +1,9 @@ +# 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. + +## 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`. + +### Make a Basic Number Insight Request diff --git a/number_insight/pyproject.toml b/number_insight/pyproject.toml new file mode 100644 index 00000000..4d0a7c83 --- /dev/null +++ b/number_insight/pyproject.toml @@ -0,0 +1,29 @@ +[project] +name = 'vonage-number-insight' +version = '1.0.0' +description = 'Vonage Number Insight package' +readme = "README.md" +authors = [{ name = "Vonage", email = "devrel@vonage.com" }] +requires-python = ">=3.8" +dependencies = [ + "vonage-http-client>=1.3.0", + "vonage-utils>=1.1.0", + "pydantic>=2.6.1", +] +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "License :: OSI Approved :: Apache Software License", +] + +[project.urls] +homepage = "https://github.com/Vonage/vonage-python-sdk" + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/number_insight/src/vonage_number_insight/BUILD b/number_insight/src/vonage_number_insight/BUILD new file mode 100644 index 00000000..db46e8d6 --- /dev/null +++ b/number_insight/src/vonage_number_insight/BUILD @@ -0,0 +1 @@ +python_sources() diff --git a/number_insight/src/vonage_number_insight/__init__.py b/number_insight/src/vonage_number_insight/__init__.py new file mode 100644 index 00000000..b791210a --- /dev/null +++ b/number_insight/src/vonage_number_insight/__init__.py @@ -0,0 +1,3 @@ +from .number_insight import NumberInsight + +__all__ = ['NumberInsight'] diff --git a/number_insight/src/vonage_number_insight/enums.py b/number_insight/src/vonage_number_insight/enums.py new file mode 100644 index 00000000..e69de29b diff --git a/number_insight/src/vonage_number_insight/errors.py b/number_insight/src/vonage_number_insight/errors.py new file mode 100644 index 00000000..be22357e --- /dev/null +++ b/number_insight/src/vonage_number_insight/errors.py @@ -0,0 +1,5 @@ +from vonage_utils.errors import VonageError + + +class NumberInsightError(VonageError): + """Indicates an error when using the Vonage Number Insight API.""" diff --git a/number_insight/src/vonage_number_insight/number_insight.py b/number_insight/src/vonage_number_insight/number_insight.py new file mode 100644 index 00000000..4176b147 --- /dev/null +++ b/number_insight/src/vonage_number_insight/number_insight.py @@ -0,0 +1,139 @@ +from pydantic import validate_call +from vonage_http_client.http_client import HttpClient + +from .errors import NumberInsightError +from .requests import ( + AdvancedAsyncInsightRequest, + AdvancedSyncInsightRequest, + BasicInsightRequest, + StandardInsightRequest, +) +from .responses import ( + AdvancedAsyncInsightResponse, + AdvancedSyncInsightResponse, + BasicInsightResponse, + StandardInsightResponse, +) + + +class NumberInsight: + """Calls Vonage's Number Insight API.""" + + def __init__(self, http_client: HttpClient) -> None: + self._http_client = http_client + self._auth_type = 'body' + + @property + def http_client(self) -> HttpClient: + """The HTTP client used to make requests to the Verify V2 API. + + Returns: + HttpClient: The HTTP client used to make requests to the Verify V2 API. + """ + return self._http_client + + @validate_call + def basic_number_insight(self, options: BasicInsightRequest) -> BasicInsightResponse: + """Get basic number insight information about a phone number. + + Args: + Options (BasicInsightRequest): The options for the request. The `number` paramerter + is required, and the `country_code` parameter is optional. + + Returns: + BasicInsightResponse: The response object containing the basic number insight + information about the phone number. + """ + response = self._http_client.get( + self._http_client.api_host, + '/ni/basic/json', + params=options.model_dump(exclude_none=True), + auth_type=self._auth_type, + ) + self._check_for_error(response) + + return BasicInsightResponse(**response) + + @validate_call + def standard_number_insight( + self, options: StandardInsightRequest + ) -> StandardInsightResponse: + """Get standard number insight information about a phone number. + + Args: + Options (StandardInsightRequest): The options for the request. The `number` paramerter + is required, and the `country_code` and `cnam` parameters are optional. + + Returns: + StandardInsightResponse: The response object containing the standard number insight + information about the phone number. + """ + response = self._http_client.get( + self._http_client.api_host, + '/ni/standard/json', + params=options.model_dump(exclude_none=True), + auth_type=self._auth_type, + ) + self._check_for_error(response) + + return StandardInsightResponse(**response) + + @validate_call + def advanced_async_number_insight( + self, options: AdvancedAsyncInsightRequest + ) -> AdvancedAsyncInsightResponse: + """Get advanced number insight information about a phone number asynchronously. + + Args: + Options (AdvancedAsyncInsightRequest): The options for the request. You must provide values + for the `callback` and `number` parameters. The `country_code` and `cnam` parameters + are optional. + + Returns: + AdvancedAsyncInsightResponse: The response object containing the advanced number insight + information about the phone number. + """ + response = self._http_client.get( + self._http_client.api_host, + '/ni/advanced/async/json', + params=options.model_dump(exclude_none=True), + auth_type=self._auth_type, + ) + + return AdvancedAsyncInsightResponse(**response) + + @validate_call + def advanced_sync_number_insight( + self, options: AdvancedSyncInsightRequest + ) -> AdvancedSyncInsightResponse: + """Get advanced number insight information about a phone number synchronously. + + Args: + Options (AdvancedSyncInsightRequest): The options for the request. The `number` parameter + is required, and the `country_code`, `cnam`, and `real_time_data` parameters are optional. + + Returns: + AdvancedSyncInsightResponse: The response object containing the advanced number insight + information about the phone number. + """ + response = self._http_client.get( + self._http_client.api_host, + '/ni/advanced/json', + params=options.model_dump(exclude_none=True), + auth_type=self._auth_type, + ) + + return AdvancedSyncInsightResponse(**response) + + def _check_for_error(self, response: dict) -> None: + """Check for an error in the response from the Number Insight API. + + Args: + response (dict): The response from the Number Insight API. + + Raises: + NumberInsightError: If the response contains an error. + """ + if response['status'] != 0: + error_message = f'Error with the following details: {response}' + raise NumberInsightError(error_message) diff --git a/number_insight/src/vonage_number_insight/requests.py b/number_insight/src/vonage_number_insight/requests.py new file mode 100644 index 00000000..0be558fa --- /dev/null +++ b/number_insight/src/vonage_number_insight/requests.py @@ -0,0 +1,21 @@ +from typing import Optional + +from pydantic import BaseModel +from vonage_utils.types import PhoneNumber + + +class BasicInsightRequest(BaseModel): + number: PhoneNumber + country: Optional[str] = None + + +class StandardInsightRequest(BasicInsightRequest): + cnam: Optional[bool] = None + + +class AdvancedAsyncInsightRequest(StandardInsightRequest): + callback: str + + +class AdvancedSyncInsightRequest(StandardInsightRequest): + real_time_data: Optional[bool] = None diff --git a/number_insight/src/vonage_number_insight/responses.py b/number_insight/src/vonage_number_insight/responses.py new file mode 100644 index 00000000..367ea097 --- /dev/null +++ b/number_insight/src/vonage_number_insight/responses.py @@ -0,0 +1,27 @@ +from typing import Optional + +from pydantic import BaseModel + + +class BasicInsightResponse(BaseModel): + status: int = None + status_message: str = None + request_id: Optional[str] = None + international_format_number: Optional[str] = None + national_format_number: Optional[str] = None + country_code: Optional[str] = None + country_code_iso3: Optional[str] = None + country_name: Optional[str] = None + country_prefix: Optional[str] = None + + +class StandardInsightResponse(BasicInsightResponse): + ... + + +class AdvancedAsyncInsightResponse(BaseModel): + ... + + +class AdvancedSyncInsightResponse(BaseModel): + ... diff --git a/number_insight/tests/BUILD b/number_insight/tests/BUILD new file mode 100644 index 00000000..63769138 --- /dev/null +++ b/number_insight/tests/BUILD @@ -0,0 +1 @@ +python_tests(dependencies=['number_insight', 'testutils']) diff --git a/number_insight/tests/data/basic_insight.json b/number_insight/tests/data/basic_insight.json new file mode 100644 index 00000000..b376f1d3 --- /dev/null +++ b/number_insight/tests/data/basic_insight.json @@ -0,0 +1,11 @@ +{ + "status": 0, + "status_message": "Success", + "request_id": "7f4a8a16-aa89-4078-b0ae-7743da34aca5", + "international_format_number": "12345678900", + "national_format_number": "(234) 567-8900", + "country_code": "US", + "country_code_iso3": "USA", + "country_name": "United States of America", + "country_prefix": "1" +} \ No newline at end of file diff --git a/number_insight/tests/data/basic_insight_error.json b/number_insight/tests/data/basic_insight_error.json new file mode 100644 index 00000000..142fe9b9 --- /dev/null +++ b/number_insight/tests/data/basic_insight_error.json @@ -0,0 +1,4 @@ +{ + "status": 3, + "status_message": "Invalid request :: Not valid number format detected [ 145645562 ]" +} \ No newline at end of file diff --git a/number_insight/tests/test_number_insight.py b/number_insight/tests/test_number_insight.py new file mode 100644 index 00000000..b7f7fc57 --- /dev/null +++ b/number_insight/tests/test_number_insight.py @@ -0,0 +1,50 @@ +from os.path import abspath + +import responses +from pytest import raises +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 testutils import build_response, get_mock_api_key_auth + +path = abspath(__file__) + + +number_insight = NumberInsight(HttpClient(get_mock_api_key_auth())) + + +def test_http_client_property(): + http_client = number_insight.http_client + assert isinstance(http_client, HttpClient) + + +@responses.activate +def test_basic_insight(): + build_response( + path, + 'GET', + 'https://api.nexmo.com/ni/basic/json', + 'basic_insight.json', + ) + options = BasicInsightRequest(number='12345678900', country_code='US') + response = number_insight.basic_number_insight(options) + assert response.status == 0 + assert response.status_message == 'Success' + + +@responses.activate +def test_basic_insight_error(): + build_response( + path, + 'GET', + 'https://api.nexmo.com/ni/basic/json', + 'basic_insight_error.json', + ) + + with raises(NumberInsightError) as e: + 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 diff --git a/pants.toml b/pants.toml index e6254cd8..5901ad43 100644 --- a/pants.toml +++ b/pants.toml @@ -33,6 +33,7 @@ filter = [ 'vonage/src', 'http_client/src', 'messages/src', + 'number_insight/src', 'number_insight_v2/src', 'sms/src', 'users/src', diff --git a/vonage/src/vonage/__init__.py b/vonage/src/vonage/__init__.py index fcd6bcaa..0d906ad8 100644 --- a/vonage/src/vonage/__init__.py +++ b/vonage/src/vonage/__init__.py @@ -4,6 +4,7 @@ Auth, HttpClientOptions, Messages, + NumberInsight, NumberInsightV2, Sms, Users, @@ -18,6 +19,7 @@ 'Auth', 'HttpClientOptions', 'Messages', + 'NumberInsight', 'NumberInsightV2', 'Sms', 'Users', diff --git a/vonage/src/vonage/vonage.py b/vonage/src/vonage/vonage.py index 9f599778..05e3ba3a 100644 --- a/vonage/src/vonage/vonage.py +++ b/vonage/src/vonage/vonage.py @@ -2,6 +2,7 @@ from vonage_http_client import Auth, HttpClient, HttpClientOptions from vonage_messages import Messages +from vonage_number_insight import NumberInsight from vonage_number_insight_v2 import NumberInsightV2 from vonage_sms import Sms from vonage_users import Users @@ -31,6 +32,7 @@ def __init__( self._http_client = HttpClient(auth, http_client_options, __version__) self.messages = Messages(self._http_client) + self.number_insight = NumberInsight(self._http_client) self.number_insight_v2 = NumberInsightV2(self._http_client) self.sms = Sms(self._http_client) self.users = Users(self._http_client)