Skip to content

Commit 15b4b1c

Browse files
authored
Add Proactive Connect (#258)
* add list_lists elements and tests * creating proactive connect class, adding list methods and tests * add items and events endpoints, mocks and tests * adding proactive connect info to README.md and renaming some methods * using python 3.7 syntax for type hint * sending "text/csv" as partial mime type in csv upload * using python 3.7 type hint * test why request is failing * using requests version that doesn't use breaking urllib3 version * returning empty response and changing version numbers for requests * updating changelog * Bump version: 3.6.0 → 3.7.0 * fix long string formatting * improving logging statements
1 parent 6ba7c1b commit 15b4b1c

31 files changed

+1433
-21
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.6.0
2+
current_version = 3.7.0
33
commit = True
44
tag = False
55

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 3.7.0
2+
- Adding support for the [Vonage Meetings API](https://developer.vonage.com/en/meetings/overview)
3+
- Adding partial support for the [Vonage Proactive Connect API](https://developer.vonage.com/en/proactive-connect/overview) - supporting API methods relating to `lists`, `items` and `events`
4+
- Returning a more descriptive (non-internal) error message if invalid values are provided for `application_id` and/or `private_key` when instantiating a Vonage client object
5+
16
# 3.6.0
27
- Adding support for the [Vonage Subaccounts API](https://developer.vonage.com/en/account/subaccounts/overview)
38

README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ need a Vonage account. Sign up [for free at vonage.com][signup].
2020
- [Verify V1 API](#verify-v1-api)
2121
- [Meetings API](#meetings-api)
2222
- [Number Insight API](#number-insight-api)
23+
- [Proactive Connect API](#proactive-connect-api)
2324
- [Account API](#account-api)
2425
- [Subaccounts API](#subaccounts-api)
2526
- [Number Management API](#number-management-api)
@@ -744,6 +745,94 @@ client.number_insight.get_advanced_number_insight(number='447700900000')
744745

745746
Docs: [https://developer.nexmo.com/api/number-insight#getNumberInsightAdvanced](https://developer.nexmo.com/api/number-insight?utm_source=DEV_REL&utm_medium=github&utm_campaign=python-client-library#getNumberInsightAdvanced)
746747

748+
## Proactive Connect API
749+
750+
Full documentation for the [Proactive Connect API](https://developer.vonage.com/en/proactive-connect/overview) is available here.
751+
752+
These methods help you manage lists of contacts when using the API:
753+
754+
### Find all lists
755+
```python
756+
client.proactive_connect.list_all_lists()
757+
```
758+
759+
### Create a list
760+
Lists can be created manually or imported from Salesforce.
761+
762+
```python
763+
params = {'name': 'my list', 'description': 'my description', 'tags': ['vip']}
764+
client.proactive_connect.create_list(params)
765+
```
766+
767+
### Get a list
768+
```python
769+
client.proactive_connect.get_list(LIST_ID)
770+
```
771+
772+
### Update a list
773+
```python
774+
params = {'name': 'my list', 'tags': ['sport', 'football']}
775+
client.proactive_connect.update_list(LIST_ID, params)
776+
```
777+
778+
### Delete a list
779+
```python
780+
client.proactive_connect.delete_list(LIST_ID)
781+
```
782+
783+
### Sync a list from an external datasource
784+
```python
785+
params = {'name': 'my list', 'tags': ['sport', 'football']}
786+
client.proactive_connect.sync_list_from_datasource(LIST_ID)
787+
```
788+
789+
These methods help you work with individual items in a list:
790+
### Find all items in a list
791+
```python
792+
client.proactive_connect.list_all_items(LIST_ID)
793+
```
794+
795+
### Create a new list item
796+
```python
797+
data = {'firstName': 'John', 'lastName': 'Doe', 'phone': '123456789101'}
798+
client.proactive_connect.create_item(LIST_ID, data)
799+
```
800+
801+
### Get a list item
802+
```python
803+
client.proactive_connect.get_item(LIST_ID, ITEM_ID)
804+
```
805+
806+
### Update a list item
807+
```python
808+
data = {'firstName': 'John', 'lastName': 'Doe', 'phone': '447007000000'}
809+
client.proactive_connect.update_item(LIST_ID, ITEM_ID, data)
810+
```
811+
812+
### Delete a list item
813+
```python
814+
client.proactive_connect.delete_item(LIST_ID, ITEM_ID)
815+
```
816+
817+
### Download all items in a list as a .csv file
818+
```python
819+
FILE_PATH = 'path/to/the/downloaded/file/location'
820+
client.proactive_connect.download_list_items(LIST_ID, FILE_PATH)
821+
```
822+
823+
### Upload items from a .csv file into a list
824+
```python
825+
FILE_PATH = 'path/to/the/file/to/upload/location'
826+
client.proactive_connect.upload_list_items(LIST_ID, FILE_PATH)
827+
```
828+
829+
This method helps you work with events emitted by the Proactive Connect API when in use:
830+
831+
### List all events
832+
```python
833+
client.proactive_connect.list_events()
834+
```
835+
747836
## Account API
748837

749838
### Get your account balance

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
setup(
1111
name="vonage",
12-
version="3.6.0",
12+
version="3.7.0",
1313
description="Vonage Server SDK for Python",
1414
long_description=long_description,
1515
long_description_content_type="text/markdown",

src/vonage/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .client import *
22
from .ncco_builder.ncco import *
33

4-
__version__ = "3.6.0"
4+
__version__ = "3.7.0"

src/vonage/client.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .messages import Messages
99
from .number_insight import NumberInsight
1010
from .number_management import Numbers
11+
from .proactive_connect import ProactiveConnect
1112
from .redact import Redact
1213
from .short_codes import ShortCodes
1314
from .sms import Sms
@@ -102,6 +103,7 @@ def __init__(
102103
self._host = "rest.nexmo.com"
103104
self._api_host = "api.nexmo.com"
104105
self._meetings_api_host = "api-eu.vonage.com/beta/meetings"
106+
self._proactive_connect_host = "api-eu.vonage.com"
105107

106108
user_agent = f"vonage-python/{vonage.__version__} python/{python_version()}"
107109

@@ -116,6 +118,7 @@ def __init__(
116118
self.messages = Messages(self)
117119
self.number_insight = NumberInsight(self)
118120
self.numbers = Numbers(self)
121+
self.proactive_connect = ProactiveConnect(self)
119122
self.short_codes = ShortCodes(self)
120123
self.sms = Sms(self)
121124
self.subaccounts = Subaccounts(self)
@@ -152,6 +155,12 @@ def meetings_api_host(self, value=None):
152155
else:
153156
self._meetings_api_host = value
154157

158+
def proactive_connect_host(self, value=None):
159+
if value is None:
160+
return self._proactive_connect_host
161+
else:
162+
self._proactive_connect_host = value
163+
155164
def auth(self, params=None, **kwargs):
156165
self._jwt_claims = params or kwargs
157166

@@ -198,12 +207,25 @@ def get(self, host, request_uri, params=None, auth_type=None):
198207
f'Invalid authentication type. Must be one of "jwt", "header" or "params".'
199208
)
200209

201-
logger.debug(f"GET to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
210+
logger.debug(
211+
f"GET to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
212+
)
202213
return self.parse(
203-
host, self.session.get(uri, params=params, headers=self._request_headers, timeout=self.timeout)
214+
host,
215+
self.session.get(
216+
uri, params=params, headers=self._request_headers, timeout=self.timeout
217+
),
204218
)
205219

206-
def post(self, host, request_uri, params, auth_type=None, body_is_json=True, supports_signature_auth=False):
220+
def post(
221+
self,
222+
host,
223+
request_uri,
224+
params,
225+
auth_type=None,
226+
body_is_json=True,
227+
supports_signature_auth=False,
228+
):
207229
"""
208230
Low-level method to make a post request to an API server.
209231
This method automatically adds authentication, picking the first applicable authentication method from the following:
@@ -229,14 +251,22 @@ def post(self, host, request_uri, params, auth_type=None, body_is_json=True, sup
229251
f'Invalid authentication type. Must be one of "jwt", "header" or "params".'
230252
)
231253

232-
logger.debug(f"POST to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
254+
logger.debug(
255+
f"POST to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
256+
)
233257
if body_is_json:
234258
return self.parse(
235-
host, self.session.post(uri, json=params, headers=self._request_headers, timeout=self.timeout)
259+
host,
260+
self.session.post(
261+
uri, json=params, headers=self._request_headers, timeout=self.timeout
262+
),
236263
)
237264
else:
238265
return self.parse(
239-
host, self.session.post(uri, data=params, headers=self._request_headers, timeout=self.timeout)
266+
host,
267+
self.session.post(
268+
uri, data=params, headers=self._request_headers, timeout=self.timeout
269+
),
240270
)
241271

242272
def put(self, host, request_uri, params, auth_type=None):
@@ -252,9 +282,14 @@ def put(self, host, request_uri, params, auth_type=None):
252282
f'Invalid authentication type. Must be one of "jwt", "header" or "params".'
253283
)
254284

255-
logger.debug(f"PUT to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
285+
logger.debug(
286+
f"PUT to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
287+
)
256288
# All APIs that currently use put methods require a json-formatted body so don't need to check this
257-
return self.parse(host, self.session.put(uri, json=params, headers=self._request_headers, timeout=self.timeout))
289+
return self.parse(
290+
host,
291+
self.session.put(uri, json=params, headers=self._request_headers, timeout=self.timeout),
292+
)
258293

259294
def patch(self, host, request_uri, params, auth_type=None):
260295
uri = f"https://{host}{request_uri}"
@@ -267,7 +302,9 @@ def patch(self, host, request_uri, params, auth_type=None):
267302
else:
268303
raise InvalidAuthenticationTypeError(f"""Invalid authentication type.""")
269304

270-
logger.debug(f"PATCH to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
305+
logger.debug(
306+
f"PATCH to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
307+
)
271308
# Only newer APIs (that expect json-bodies) currently use this method, so we will always send a json-formatted body
272309
return self.parse(host, self.session.patch(uri, json=params, headers=self._request_headers))
273310

@@ -288,7 +325,10 @@ def delete(self, host, request_uri, params=None, auth_type=None):
288325
if params is not None:
289326
logger.debug(f"DELETE call has params {repr(params)}")
290327
return self.parse(
291-
host, self.session.delete(uri, headers=self._request_headers, timeout=self.timeout, params=params)
328+
host,
329+
self.session.delete(
330+
uri, headers=self._request_headers, timeout=self.timeout, params=params
331+
),
292332
)
293333

294334
def parse(self, host, response: Response):
@@ -322,25 +362,33 @@ def parse(self, host, response: Response):
322362
title = error_data["title"]
323363
detail = error_data["detail"]
324364
type = error_data["type"]
325-
message = f"{title}: {detail} ({type})"
365+
message = f"{title}: {detail} ({type}){self._add_individual_errors(error_data)}"
326366
elif 'status' in error_data and 'message' in error_data and 'name' in error_data:
327-
message = f'Status Code {error_data["status"]}: {error_data["name"]}: {error_data["message"]}'
328-
if 'errors' in error_data:
329-
for error in error_data['errors']:
330-
message += f', error: {error}'
367+
message = (
368+
f'Status Code {error_data["status"]}: {error_data["name"]}: {error_data["message"]}'
369+
f'{self._add_individual_errors(error_data)}'
370+
)
331371
else:
332372
message = error_data
333373
except JSONDecodeError:
334374
pass
335375
raise ClientError(message)
376+
336377
elif 500 <= response.status_code < 600:
337378
logger.warning(f"Server error: {response.status_code} {repr(response.content)}")
338379
message = f"{response.status_code} response from {host}"
339380
raise ServerError(message)
340381

382+
def _add_individual_errors(self, error_data):
383+
message = ''
384+
if 'errors' in error_data:
385+
for error in error_data["errors"]:
386+
message += f"\nError: {error}"
387+
return message
388+
341389
def _create_jwt_auth_string(self):
342390
return b"Bearer " + self._generate_application_jwt()
343-
391+
344392
def _generate_application_jwt(self):
345393
try:
346394
return self._jwt_client.generate_application_jwt(self._jwt_claims)

src/vonage/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ class Verify2Error(ClientError):
4646

4747
class SubaccountsError(ClientError):
4848
"""An error relating to the Subaccounts API."""
49+
50+
51+
class ProactiveConnectError(ClientError):
52+
"""An error relating to the Proactive Connect API."""

0 commit comments

Comments
 (0)