Skip to content

Commit 102015e

Browse files
committed
add numbers api
1 parent 8539ea8 commit 102015e

28 files changed

+886
-3
lines changed

number_management/BUILD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
resource(name='pyproject', source='pyproject.toml')
2+
file(name='readme', source='README.md')
3+
4+
files(sources=['tests/data/*'])
5+
6+
python_distribution(
7+
name='vonage-numbers',
8+
dependencies=[
9+
':pyproject',
10+
':readme',
11+
'number_management/src/vonage_numbers',
12+
],
13+
provides=python_artifact(),
14+
generate_setup=False,
15+
repositories=['@pypi'],
16+
)

number_management/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# 1.0.0
2+
- Initial upload

number_management/README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Vonage Numbers Package
2+
3+
This package contains the code to use Vonage's Numbers API in Python.
4+
5+
It includes methods for managing and buying numbers.
6+
7+
## Usage
8+
9+
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`.
10+
11+
### List Numbers You Own
12+
13+
```python
14+
numbers, count, next_page = vonage_client.numbers.list_owned_numbers()
15+
print(numbers)
16+
print(count)
17+
print(next_page)
18+
19+
# With filtering
20+
from vonage_numbers import ListOwnedNumbersFilter
21+
numbers, count, next_page = vonage_client.numbers.list_owned_numbers(
22+
ListOwnedNumbersFilter(country='GB', size=3, index=2)
23+
)
24+
25+
numbers, count, next_page_index = vonage_client.numbers.list_owned_numbers()
26+
print(numbers)
27+
print(count)
28+
print(next_page_index)
29+
```
30+
31+
### Search for Available Numbers
32+
33+
```python
34+
from vonage_numbers import SearchAvailableNumbersFilter
35+
36+
numbers, count, next_page_index = vonage_client.numbers.search_available_numbers(
37+
SearchAvailableNumbersFilter(
38+
country='GB', size=10, pattern='44701', search_pattern=1
39+
)
40+
)
41+
print(numbers)
42+
print(count)
43+
print(next_page_index)
44+
```
45+
46+
### Buy a Number
47+
48+
```python
49+
from vonage_numbers import NumberParams
50+
51+
status = vonage_client.numbers.buy_number(NumberParams(country='GB', msisdn='447007000000'))
52+
print(status)
53+
```
54+
55+
### Cancel a number
56+
57+
```python
58+
from vonage_numbers import NumberParams
59+
60+
status = vonage_client.numbers.cancel_number(NumberParams(country='GB', msisdn='447007000000'))
61+
print(status)
62+
```
63+
64+
### Update a Number
65+
66+
```python
67+
from vonage_numbers import UpdateNumberParams
68+
69+
status = vonage_client.numbers.update_number(
70+
UpdateNumberParams(
71+
country='GB',
72+
msisdn='447007000000',
73+
mo_http_url='https://example.com',
74+
mo_smpp_sytem_type='inbound',
75+
voice_callback_type='tel',
76+
voice_callback_value='447008000000',
77+
voice_status_callback='https://example.com',
78+
)
79+
)
80+
81+
print(status)
82+
```

number_management/pyproject.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[project]
2+
name = 'vonage-numbers'
3+
version = '1.0.0'
4+
description = 'Vonage Numbers package'
5+
readme = "README.md"
6+
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
7+
requires-python = ">=3.8"
8+
dependencies = [
9+
"vonage-http-client>=1.4.0",
10+
"vonage-utils>=1.1.2",
11+
"pydantic>=2.7.1",
12+
]
13+
classifiers = [
14+
"Programming Language :: Python",
15+
"Programming Language :: Python :: 3",
16+
"Programming Language :: Python :: 3.8",
17+
"Programming Language :: Python :: 3.9",
18+
"Programming Language :: Python :: 3.10",
19+
"Programming Language :: Python :: 3.11",
20+
"Programming Language :: Python :: 3.12",
21+
"License :: OSI Approved :: Apache Software License",
22+
]
23+
24+
[project.urls]
25+
homepage = "https://github.com/Vonage/vonage-python-sdk"
26+
27+
[build-system]
28+
requires = ["setuptools>=61.0", "wheel"]
29+
build-backend = "setuptools.build_meta"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python_sources()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from .enums import NumberFeatures, NumberType, VoiceCallbackType
2+
from .errors import NumbersError
3+
from .number_management import Numbers
4+
from .requests import (
5+
ListOwnedNumbersFilter,
6+
NumberParams,
7+
SearchAvailableNumbersFilter,
8+
UpdateNumberParams,
9+
)
10+
from .responses import AvailableNumber, NumbersStatus, OwnedNumber
11+
12+
__all__ = [
13+
'NumberFeatures',
14+
'NumberType',
15+
'VoiceCallbackType',
16+
'NumbersError',
17+
'Numbers',
18+
'ListOwnedNumbersFilter',
19+
'NumberParams',
20+
'SearchAvailableNumbersFilter',
21+
'UpdateNumberParams',
22+
'AvailableNumber',
23+
'NumbersStatus',
24+
'OwnedNumber',
25+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from enum import Enum
2+
3+
4+
class NumberType(str, Enum):
5+
LANDLINE = 'landline'
6+
MOBILE_LVN = 'mobile-lvn'
7+
LANDLINE_TOLL_FREE = 'landline-toll-free'
8+
9+
10+
class NumberFeatures(str, Enum):
11+
SMS = 'SMS'
12+
VOICE = 'VOICE'
13+
MMS = 'MMS'
14+
SMS_VOICE = 'SMS,VOICE'
15+
SMS_MMS = 'SMS,MMS'
16+
VOICE_MMS = 'VOICE,MMS'
17+
SMS_VOICE_MMS = 'SMS,VOICE,MMS'
18+
19+
20+
class VoiceCallbackType(str, Enum):
21+
SIP = 'sip'
22+
TEL = 'tel'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from vonage_utils.errors import VonageError
2+
3+
4+
class NumbersError(VonageError):
5+
"""Indicates an error with the Numbers API package."""
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
from typing import List, Optional, Tuple
2+
3+
from pydantic import validate_call
4+
from vonage_http_client.http_client import HttpClient
5+
from vonage_numbers.errors import NumbersError
6+
from .requests import (
7+
SearchAvailableNumbersFilter,
8+
ListOwnedNumbersFilter,
9+
NumberParams,
10+
UpdateNumberParams,
11+
)
12+
from .responses import AvailableNumber, NumbersStatus, OwnedNumber
13+
14+
15+
class Numbers:
16+
"""Class containing methods for Vonage Application management."""
17+
18+
def __init__(self, http_client: HttpClient) -> None:
19+
self._http_client = http_client
20+
self._auth_type = 'basic'
21+
self._sent_data_type = 'form'
22+
23+
@property
24+
def http_client(self) -> HttpClient:
25+
"""The HTTP client used to make requests to the Numbers API.
26+
27+
Returns:
28+
HttpClient: The HTTP client used to make requests to the Numbers API.
29+
"""
30+
return self._http_client
31+
32+
@validate_call
33+
def list_owned_numbers(
34+
self, filter: ListOwnedNumbersFilter = ListOwnedNumbersFilter()
35+
) -> Tuple[List[OwnedNumber], int, Optional[int]]:
36+
"""List numbers you own.
37+
38+
By default, returns the first 100 numbers and the page index of
39+
the next page of results, if there are more than 100 numbers.
40+
41+
Args:
42+
filter (ListOwnedNumbersFilter): The filter object.
43+
44+
Returns:
45+
Tuple[List[OwnedNumber], int, Optional[int]]: A tuple containing a
46+
list of owned numbers, the total count of owned phone numbers
47+
and the next page index, if applicable.
48+
i.e.
49+
number_list: List[OwnedNumber], count: int, next_page_index: Optional[int])
50+
"""
51+
response = self._http_client.get(
52+
self._http_client.rest_host,
53+
'/account/numbers',
54+
filter.model_dump(exclude_none=True),
55+
self._auth_type,
56+
)
57+
58+
index = filter.index or 1
59+
page_size = filter.size
60+
61+
numbers = []
62+
try:
63+
for number in response['numbers']:
64+
numbers.append(OwnedNumber(**number))
65+
except KeyError:
66+
return [], 0, None
67+
68+
count = response['count']
69+
if count > page_size * index:
70+
return numbers, count, index + 1
71+
return numbers, count, None
72+
73+
@validate_call
74+
def search_available_numbers(
75+
self, filter: SearchAvailableNumbersFilter
76+
) -> Tuple[List[AvailableNumber], int, Optional[int]]:
77+
"""Search for available numbers to buy.
78+
79+
By default, returns the first 100 numbers and the page index of
80+
the next page of results, if there are more than 100 numbers.
81+
82+
Args:
83+
filter (SearchAvailableNumbersFilter): The filter object.
84+
85+
Returns:
86+
Tuple[List[AvailableNumber], int, Optional[int]]: A tuple containing a
87+
list of available numbers, the total count of available phone numbers
88+
and the next page index, if applicable.
89+
i.e.
90+
number_list: List[AvailableNumber], count: int, next_page_index: Optional[int])
91+
"""
92+
response = self._http_client.get(
93+
self._http_client.rest_host,
94+
'/number/search',
95+
filter.model_dump(exclude_none=True),
96+
self._auth_type,
97+
)
98+
99+
index = filter.index or 1
100+
page_size = filter.size
101+
102+
numbers = []
103+
try:
104+
for number in response['numbers']:
105+
numbers.append(AvailableNumber(**number))
106+
except KeyError:
107+
return [], 0, None
108+
109+
count = response['count']
110+
if count > page_size * index:
111+
return numbers, count, index + 1
112+
return numbers, count, None
113+
114+
@validate_call
115+
def buy_number(self, params: NumberParams) -> NumbersStatus:
116+
"""Buy a number.
117+
118+
Args:
119+
params (NumberParams): The number parameters.
120+
121+
Returns:
122+
NumbersStatus: The status of the number purchase.
123+
"""
124+
response = self._http_client.post(
125+
self._http_client.rest_host,
126+
'/number/buy',
127+
params.model_dump(exclude_none=True),
128+
self._auth_type,
129+
self._sent_data_type,
130+
)
131+
132+
self._check_for_error(response)
133+
return NumbersStatus(**response)
134+
135+
@validate_call
136+
def cancel_number(self, params: NumberParams) -> NumbersStatus:
137+
"""Cancel a number.
138+
139+
Args:
140+
params (NumberParams): The number parameters.
141+
142+
Returns:
143+
NumbersStatus: The status of the number cancellation.
144+
"""
145+
response = self._http_client.post(
146+
self._http_client.rest_host,
147+
'/number/cancel',
148+
params.model_dump(exclude_none=True),
149+
self._auth_type,
150+
self._sent_data_type,
151+
)
152+
153+
self._check_for_error(response)
154+
return NumbersStatus(**response)
155+
156+
@validate_call
157+
def update_number(self, params: UpdateNumberParams) -> NumbersStatus:
158+
"""Update a number.
159+
160+
Args:
161+
params (UpdateNumberParams): The number parameters.
162+
163+
Returns:
164+
NumbersStatus: The status of the number update.
165+
"""
166+
response = self._http_client.post(
167+
self._http_client.rest_host,
168+
'/number/update',
169+
params.model_dump(exclude_none=True),
170+
self._auth_type,
171+
self._sent_data_type,
172+
)
173+
174+
self._check_for_error(response)
175+
return NumbersStatus(**response)
176+
177+
def _check_for_error(self, response_data):
178+
if response_data['error-code'] != '200':
179+
raise NumbersError(
180+
f'Numbers API operation failed: {response_data["error-code"]} {response_data["error-code-label"]}'
181+
)

0 commit comments

Comments
 (0)