Skip to content

Commit

Permalink
Add Proactive Connect (#258)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
maxkahan committed Jun 21, 2023
1 parent 6ba7c1b commit 15b4b1c
Show file tree
Hide file tree
Showing 31 changed files with 1,433 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.6.0
current_version = 3.7.0
commit = True
tag = False

Expand Down
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 3.7.0
- Adding support for the [Vonage Meetings API](https://developer.vonage.com/en/meetings/overview)
- 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`
- 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

# 3.6.0
- Adding support for the [Vonage Subaccounts API](https://developer.vonage.com/en/account/subaccounts/overview)

Expand Down
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ need a Vonage account. Sign up [for free at vonage.com][signup].
- [Verify V1 API](#verify-v1-api)
- [Meetings API](#meetings-api)
- [Number Insight API](#number-insight-api)
- [Proactive Connect API](#proactive-connect-api)
- [Account API](#account-api)
- [Subaccounts API](#subaccounts-api)
- [Number Management API](#number-management-api)
Expand Down Expand Up @@ -744,6 +745,94 @@ client.number_insight.get_advanced_number_insight(number='447700900000')

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)

## Proactive Connect API

Full documentation for the [Proactive Connect API](https://developer.vonage.com/en/proactive-connect/overview) is available here.

These methods help you manage lists of contacts when using the API:

### Find all lists
```python
client.proactive_connect.list_all_lists()
```

### Create a list
Lists can be created manually or imported from Salesforce.

```python
params = {'name': 'my list', 'description': 'my description', 'tags': ['vip']}
client.proactive_connect.create_list(params)
```

### Get a list
```python
client.proactive_connect.get_list(LIST_ID)
```

### Update a list
```python
params = {'name': 'my list', 'tags': ['sport', 'football']}
client.proactive_connect.update_list(LIST_ID, params)
```

### Delete a list
```python
client.proactive_connect.delete_list(LIST_ID)
```

### Sync a list from an external datasource
```python
params = {'name': 'my list', 'tags': ['sport', 'football']}
client.proactive_connect.sync_list_from_datasource(LIST_ID)
```

These methods help you work with individual items in a list:
### Find all items in a list
```python
client.proactive_connect.list_all_items(LIST_ID)
```

### Create a new list item
```python
data = {'firstName': 'John', 'lastName': 'Doe', 'phone': '123456789101'}
client.proactive_connect.create_item(LIST_ID, data)
```

### Get a list item
```python
client.proactive_connect.get_item(LIST_ID, ITEM_ID)
```

### Update a list item
```python
data = {'firstName': 'John', 'lastName': 'Doe', 'phone': '447007000000'}
client.proactive_connect.update_item(LIST_ID, ITEM_ID, data)
```

### Delete a list item
```python
client.proactive_connect.delete_item(LIST_ID, ITEM_ID)
```

### Download all items in a list as a .csv file
```python
FILE_PATH = 'path/to/the/downloaded/file/location'
client.proactive_connect.download_list_items(LIST_ID, FILE_PATH)
```

### Upload items from a .csv file into a list
```python
FILE_PATH = 'path/to/the/file/to/upload/location'
client.proactive_connect.upload_list_items(LIST_ID, FILE_PATH)
```

This method helps you work with events emitted by the Proactive Connect API when in use:

### List all events
```python
client.proactive_connect.list_events()
```

## Account API

### Get your account balance
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

setup(
name="vonage",
version="3.6.0",
version="3.7.0",
description="Vonage Server SDK for Python",
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
2 changes: 1 addition & 1 deletion src/vonage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .client import *
from .ncco_builder.ncco import *

__version__ = "3.6.0"
__version__ = "3.7.0"
80 changes: 64 additions & 16 deletions src/vonage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .messages import Messages
from .number_insight import NumberInsight
from .number_management import Numbers
from .proactive_connect import ProactiveConnect
from .redact import Redact
from .short_codes import ShortCodes
from .sms import Sms
Expand Down Expand Up @@ -102,6 +103,7 @@ def __init__(
self._host = "rest.nexmo.com"
self._api_host = "api.nexmo.com"
self._meetings_api_host = "api-eu.vonage.com/beta/meetings"
self._proactive_connect_host = "api-eu.vonage.com"

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

Expand All @@ -116,6 +118,7 @@ def __init__(
self.messages = Messages(self)
self.number_insight = NumberInsight(self)
self.numbers = Numbers(self)
self.proactive_connect = ProactiveConnect(self)
self.short_codes = ShortCodes(self)
self.sms = Sms(self)
self.subaccounts = Subaccounts(self)
Expand Down Expand Up @@ -152,6 +155,12 @@ def meetings_api_host(self, value=None):
else:
self._meetings_api_host = value

def proactive_connect_host(self, value=None):
if value is None:
return self._proactive_connect_host
else:
self._proactive_connect_host = value

def auth(self, params=None, **kwargs):
self._jwt_claims = params or kwargs

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

logger.debug(f"GET to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
logger.debug(
f"GET to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
)
return self.parse(
host, self.session.get(uri, params=params, headers=self._request_headers, timeout=self.timeout)
host,
self.session.get(
uri, params=params, headers=self._request_headers, timeout=self.timeout
),
)

def post(self, host, request_uri, params, auth_type=None, body_is_json=True, supports_signature_auth=False):
def post(
self,
host,
request_uri,
params,
auth_type=None,
body_is_json=True,
supports_signature_auth=False,
):
"""
Low-level method to make a post request to an API server.
This method automatically adds authentication, picking the first applicable authentication method from the following:
Expand All @@ -229,14 +251,22 @@ def post(self, host, request_uri, params, auth_type=None, body_is_json=True, sup
f'Invalid authentication type. Must be one of "jwt", "header" or "params".'
)

logger.debug(f"POST to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
logger.debug(
f"POST to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
)
if body_is_json:
return self.parse(
host, self.session.post(uri, json=params, headers=self._request_headers, timeout=self.timeout)
host,
self.session.post(
uri, json=params, headers=self._request_headers, timeout=self.timeout
),
)
else:
return self.parse(
host, self.session.post(uri, data=params, headers=self._request_headers, timeout=self.timeout)
host,
self.session.post(
uri, data=params, headers=self._request_headers, timeout=self.timeout
),
)

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

logger.debug(f"PUT to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}")
logger.debug(
f"PUT to {repr(uri)} with params {repr(params)}, headers {repr(self._request_headers)}"
)
# All APIs that currently use put methods require a json-formatted body so don't need to check this
return self.parse(host, self.session.put(uri, json=params, headers=self._request_headers, timeout=self.timeout))
return self.parse(
host,
self.session.put(uri, json=params, headers=self._request_headers, timeout=self.timeout),
)

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

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

Expand All @@ -288,7 +325,10 @@ def delete(self, host, request_uri, params=None, auth_type=None):
if params is not None:
logger.debug(f"DELETE call has params {repr(params)}")
return self.parse(
host, self.session.delete(uri, headers=self._request_headers, timeout=self.timeout, params=params)
host,
self.session.delete(
uri, headers=self._request_headers, timeout=self.timeout, params=params
),
)

def parse(self, host, response: Response):
Expand Down Expand Up @@ -322,25 +362,33 @@ def parse(self, host, response: Response):
title = error_data["title"]
detail = error_data["detail"]
type = error_data["type"]
message = f"{title}: {detail} ({type})"
message = f"{title}: {detail} ({type}){self._add_individual_errors(error_data)}"
elif 'status' in error_data and 'message' in error_data and 'name' in error_data:
message = f'Status Code {error_data["status"]}: {error_data["name"]}: {error_data["message"]}'
if 'errors' in error_data:
for error in error_data['errors']:
message += f', error: {error}'
message = (
f'Status Code {error_data["status"]}: {error_data["name"]}: {error_data["message"]}'
f'{self._add_individual_errors(error_data)}'
)
else:
message = error_data
except JSONDecodeError:
pass
raise ClientError(message)

elif 500 <= response.status_code < 600:
logger.warning(f"Server error: {response.status_code} {repr(response.content)}")
message = f"{response.status_code} response from {host}"
raise ServerError(message)

def _add_individual_errors(self, error_data):
message = ''
if 'errors' in error_data:
for error in error_data["errors"]:
message += f"\nError: {error}"
return message

def _create_jwt_auth_string(self):
return b"Bearer " + self._generate_application_jwt()

def _generate_application_jwt(self):
try:
return self._jwt_client.generate_application_jwt(self._jwt_claims)
Expand Down
4 changes: 4 additions & 0 deletions src/vonage/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@ class Verify2Error(ClientError):

class SubaccountsError(ClientError):
"""An error relating to the Subaccounts API."""


class ProactiveConnectError(ClientError):
"""An error relating to the Proactive Connect API."""
Loading

0 comments on commit 15b4b1c

Please sign in to comment.