Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This is the Python server SDK to help you use Vonage APIs in your Python applica
- [Application API](#application-api)
- [HTTP Client](#http-client)
- [JWT Client](#jwt-client)
- [Identity Insights](#identity-insights)
- [Messages API](#messages-api)
- [Network Number Verification API](#network-number-verification-api)
- [Network Sim Swap API](#network-sim-swap-api)
Expand Down Expand Up @@ -403,6 +404,34 @@ from vonage_jwt import verify_signature
verify_signature(TOKEN, SIGNATURE_SECRET) # Returns a boolean
```

## Identity Insights

### Get Insights

```python
from vonage_identity_insights import (
IdentityInsightsRequest,
InsightsRequest,
EmptyInsight,
SimSwapInsight,
)

options = HttpClientOptions(api_host='api-eu.vonage.com', timeout=30)

client = Vonage(auth=auth, http_client_options=options)

request = IdentityInsightsRequest(
phone_number='1234567890',
purpose='FraudPreventionAndDetection',
insights=InsightsRequest(
format=EmptyInsight(),
sim_swap=SimSwapInsight(period=240)
)
)

response = client.identity_insights.get_insights(request)
```

## Messages API

### How to Construct a Message
Expand Down Expand Up @@ -1436,6 +1465,7 @@ The following is a list of Vonage APIs and whether the Python SDK provides suppo
| External Accounts API | Beta | ❌ |
| Media API | Beta | ❌ |
| Messages API | General Availability | ✅ |
| Identity Insights API | General Availability | ✅ |
| Number Insight API | General Availability | ✅ |
| Number Management API | General Availability | ✅ |
| Pricing API | General Availability | ✅ |
Expand Down
3 changes: 1 addition & 2 deletions account/tests/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import responses
from pytest import raises
from testutils import build_response, get_mock_api_key_auth
from vonage_account.account import Account
from vonage_account.errors import InvalidSecretError
from vonage_account.requests import (
Expand All @@ -12,8 +13,6 @@
from vonage_http_client.errors import ForbiddenError
from vonage_http_client.http_client import HttpClient

from testutils import build_response, get_mock_api_key_auth

path = abspath(__file__)

account = Account(HttpClient(get_mock_api_key_auth()))
Expand Down
3 changes: 1 addition & 2 deletions application/tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import responses
from pytest import raises
from testutils import build_response, get_mock_api_key_auth
from vonage_application.application import Application
from vonage_application.common import (
ApplicationUrl,
Expand All @@ -24,8 +25,6 @@
from vonage_application.requests import ApplicationConfig, ListApplicationsFilter
from vonage_http_client.http_client import HttpClient

from testutils import build_response, get_mock_api_key_auth

path = abspath(__file__)

application = Application(HttpClient(get_mock_api_key_auth()))
Expand Down
3 changes: 1 addition & 2 deletions http_client/tests/test_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from requests import PreparedRequest, Response, Session
from requests.exceptions import ConnectionError
from responses import matchers
from testutils import build_response, get_mock_jwt_auth
from vonage_http_client.auth import Auth
from vonage_http_client.errors import (
AuthenticationError,
Expand All @@ -20,8 +21,6 @@
)
from vonage_http_client.http_client import HttpClient, HttpClientOptions

from testutils import build_response, get_mock_jwt_auth

path = abspath(__file__)


Expand Down
16 changes: 16 additions & 0 deletions identity_insights/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
resource(name='pyproject', source='pyproject.toml')
file(name='readme', source='README.md')

files(sources=['tests/data/*'])

python_distribution(
name='vonage-identity-insights',
dependencies=[
':pyproject',
':readme',
'identity_insights/src/vonage_identity_insights',
],
provides=python_artifact(),
generate_setup=False,
repositories=['@pypi'],
)
2 changes: 2 additions & 0 deletions identity_insights/CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 1.0.0
- Initial upload
35 changes: 35 additions & 0 deletions identity_insights/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Vonage Identity Insights Package

This package contains the code to use the [Vonage Identity Insights API](https://developer.vonage.com/en/identity-insights/overview) in Python. The API provides real-time access to a broad range of attributes related to the carrier, subscriber, or device associated with a phone number. To use it you will need a Vonage account. Sign up [for free at vonage.com][signup].

## 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 Standard Identity Insights Request

```python
from vonage import Vonage, Auth, HttpClientOptions
from vonage_identity_insights import (
IdentityInsightsRequest,
InsightsRequest,
EmptyInsight,
SimSwapInsight,
)

options = HttpClientOptions(api_host="api-eu.vonage.com", timeout=30)

client = Vonage(auth=auth, http_client_options=options)

request = IdentityInsightsRequest(
phone_number="1234567890",
purpose="FraudPreventionAndDetection",
insights=InsightsRequest(
format=EmptyInsight(), sim_swap=SimSwapInsight(period=240)
),
)

response = client.identity_insights.requests(request)

```

32 changes: 32 additions & 0 deletions identity_insights/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[project]
name = 'vonage-identity-insights'
dynamic = ["version"]
description = 'Vonage Identity Insights package'
readme = "README.md"
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
requires-python = ">=3.9"
dependencies = [
"vonage-http-client>=1.5.0",
"vonage-utils>=1.1.4",
"pydantic>=2.9.2",
]
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"License :: OSI Approved :: Apache Software License",
]

[project.urls]
homepage = "https://github.com/Vonage/vonage-python-sdk"

[tool.setuptools.dynamic]
version = { attr = "vonage_identity_insights._version.__version__" }

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
1 change: 1 addition & 0 deletions identity_insights/src/vonage_identity_insights/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python_sources()
20 changes: 20 additions & 0 deletions identity_insights/src/vonage_identity_insights/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from . import errors
from .identity_insights import IdentityInsights
from .requests import (
EmptyInsight,
IdentityInsightsRequest,
InsightsRequest,
SimSwapInsight,
)
from .responses import IdentityInsightsResponse, InsightStatus

__all__ = [
"IdentityInsights",
"IdentityInsightsRequest",
"InsightsRequest",
"EmptyInsight",
"SimSwapInsight",
"IdentityInsightsResponse",
"InsightStatus",
"errors",
]
1 change: 1 addition & 0 deletions identity_insights/src/vonage_identity_insights/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.0.0"
9 changes: 9 additions & 0 deletions identity_insights/src/vonage_identity_insights/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from vonage_utils.errors import VonageError


class IdentityInsightsError(VonageError):
"""Indicates an error when using the Vonage Identity Insights API."""


class EmptyInsightsRequestException(VonageError):
"""At least one insight must be provided."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from logging import getLogger

from pydantic import validate_call
from vonage_http_client.http_client import HttpClient

from .errors import EmptyInsightsRequestException, IdentityInsightsError
from .requests import IdentityInsightsRequest
from .responses import IdentityInsightsResponse

logger = getLogger("vonage_identity_insights")


class IdentityInsights:
"""Calls Vonage's Identity Insights API."""

def __init__(self, http_client: HttpClient) -> None:
"""Initialize the IdentityInsights client.

Args:
http_client (HttpClient): Configured HTTP client used to make
authenticated requests to the Vonage API.
"""
self._http_client = http_client
self._auth_type = "jwt"

@property
def http_client(self) -> HttpClient:
"""The HTTP client used to make requests to the Vonage Indentity Insights API.

Returns:
HttpClient: The HTTP client used to make requests to the Identity Insights API.
"""
return self._http_client

@validate_call
def requests(
self, insights_request: IdentityInsightsRequest
) -> IdentityInsightsResponse:
"""Retrieve identity insights for a phone number.

Sends an aggregated request to the Identity Insights API and returns
the results for each requested insight.

Args:
insights_request (IdentityInsightsRequest): The request object
containing the phone number and the set of identity insights
to retrieve.

Returns:
IdentityInsightsResponse: The response object containing the results
and status of each requested insight.

Raises:
IdentityInsightsError: If the API returns an error response in
`application/problem+json` format.
"""
payload = insights_request.model_dump(exclude_none=True)
Comment thread
alnacle marked this conversation as resolved.

insights = payload.get("insights")
if not insights or not isinstance(insights, dict):
raise EmptyInsightsRequestException()

response = self._http_client.post(
self._http_client.api_host,
"/identity-insights/v1/requests",
payload,
auth_type=self._auth_type,
)
self._check_for_error(response)

return IdentityInsightsResponse(**response)

def _check_for_error(self, response: dict) -> None:
"""Check whether the API response represents an error.

The Identity Insights API returns errors using the
`application/problem+json` format. If such an error is detected, this
method raises an IdentityInsightsError.

Args:
response (dict): Raw response returned by the HTTP client.

Raises:
IdentityInsightsError: If the response contains an error payload.
"""
if "title" in response and "detail" in response:
error_message = f"Error with the following details: {response}"
raise IdentityInsightsError(error_message)
79 changes: 79 additions & 0 deletions identity_insights/src/vonage_identity_insights/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from typing import Optional

from pydantic import BaseModel, Field
from vonage_utils.types import PhoneNumber


class EmptyInsight(BaseModel):
"""Model for an insight request without parameters.

This model represents insights that must be included as an empty JSON object (`{}`) to
indicate that the insight is requested.
"""


class SimSwapInsight(BaseModel):
"""Model for a SIM swap insight request.

This insight checks whether a SIM swap has occurred within a specified
period of time.

Args:
period (int, Optional): Period in hours to be checked for SIM swap.
Must be between 1 and 2400. Defaults to 240.
"""

period: Optional[int] = Field(
default=240,
ge=1,
le=2400,
description="Period in hours to be checked for SIM swap",
)


class InsightsRequest(BaseModel):
"""Model for a collection of identity insight requests.

Each field represents an individual insight. Only the insights included
in this object will be processed and returned in the response.

Args:
format (EmptyInsight, Optional): Request phone number format validation.
sim_swap (SimSwapInsight, Optional): Request SIM swap information.
original_carrier (EmptyInsight, Optional): Request original carrier
information.
current_carrier (EmptyInsight, Optional): Request current carrier
information.
"""

format: Optional[EmptyInsight] = None
sim_swap: Optional[SimSwapInsight] = None
original_carrier: Optional[EmptyInsight] = None
current_carrier: Optional[EmptyInsight] = None

def at_least_one_insight(cls, values):
"""Validate that at least one insight is provided."""
if not any(v is not None for v in values.values()):
raise ValueError("At least one insight must be provided")
return values


class IdentityInsightsRequest(BaseModel):
"""Model for an Identity Insights API request.

This model represents a single aggregated request for one or more identity
insights related to a phone number.

Args:
phone_number (PhoneNumber): The phone number to retrieve identity
insights for.
purpose (str, Optional): Purpose of the request. Required for insights
that rely on the Network Registry.
insights (InsightsRequest): Collection of requested insights.
"""

phone_number: PhoneNumber
purpose: Optional[str] = Field(
None, description="Purpose of the request (required for some insights)"
)
insights: InsightsRequest
Loading