Skip to content

Commit 6ba7c1b

Browse files
authored
Add meetings api (#240)
* adding patch method, creating Meetings class and initial methods * adding new Meetings class methods, optional params field to client.delete, new error type * basic implementation of Meetings API methods * Bump version: 3.2.0 → 3.2.1 * fixing import issue with uncommitted init file * adding check that subfolders contain an __init__.py file * refactoring, raising exception * adding get_meetings_api_host method and _meetings_api_host attribute to Client * adding new test module and adding meetings object instantiation to client * small tweaks to methods * implementing Meetings API endpoints, test room and recording endpoints, add mocks * adding tests for themes methods, adding new mocks, check in client.parse * commenting out unimplemented test * adding Meetings.upload_logo_to_theme method to wrap the logo upload process, added tests and mocks * using Literal type from typing-extensions module for python 3.7 compatibility * adding Meetings API info to README * Bump version: 3.2.2 → 3.3.0 * adding xml response mock from aws * setting "no content" responses * catching exception where no values for application_id and private_key but try to use jwt auth * adding page_size parameter and code formatting * testing for returned empty json body on list_themes with no themes, response formatting * adding tests for previously undocumented properties * catching expires_at errors for creating room, updating error responses
1 parent 0a8ebf6 commit 6ba7c1b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1950
-16
lines changed

README.md

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ need a Vonage account. Sign up [for free at vonage.com][signup].
1818
- [NCCO Builder](#ncco-builder)
1919
- [Verify V2 API](#verify-v2-api)
2020
- [Verify V1 API](#verify-v1-api)
21+
- [Meetings API](#meetings-api)
2122
- [Number Insight API](#number-insight-api)
2223
- [Account API](#account-api)
2324
- [Subaccounts API](#subaccounts-api)
@@ -579,6 +580,144 @@ else:
579580
print("Error: %s" % response["error_text"])
580581
```
581582

583+
## Meetings API
584+
585+
Full docs for the [Meetings API are available here](https://developer.vonage.com/en/meetings/overview).
586+
587+
### Create a meeting room
588+
589+
```python
590+
# Instant room
591+
params = {'display_name': 'my_test_room'}
592+
meeting = client.meetings.create_room(params)
593+
594+
# Long term room
595+
params = {'display_name': 'test_long_term_room', 'type': 'long_term', 'expires_at': '2023-01-30T00:47:04+0000'}
596+
meeting = client.meetings.create_room(params)
597+
```
598+
599+
### Get all meeting rooms
600+
601+
```python
602+
client.meetings.list_rooms()
603+
```
604+
605+
### Get a room by id
606+
607+
```python
608+
client.meetings.get_room('MY_ROOM_ID')
609+
```
610+
611+
### Update a long term room
612+
613+
```python
614+
params = {
615+
'update_details': {
616+
"available_features": {
617+
"is_recording_available": False,
618+
"is_chat_available": False,
619+
}
620+
}
621+
}
622+
meeting = client.meetings.update_room('MY_ROOM_ID', params)
623+
```
624+
625+
### Get all recordings for a session
626+
627+
```python
628+
session = client.meetings.get_session_recordings('MY_SESSION_ID')
629+
```
630+
631+
### Get a recording by id
632+
633+
```python
634+
recording = client.meetings.get_recording('MY_RECORDING_ID')
635+
```
636+
637+
### Delete a recording
638+
639+
```python
640+
client.meetings.delete_recording('MY_RECORDING_ID')
641+
```
642+
643+
### List dial-in numbers
644+
645+
```python
646+
numbers = client.meetings.list_dial_in_numbers()
647+
```
648+
649+
### Create a theme
650+
651+
```python
652+
params = {
653+
'theme_name': 'my_theme',
654+
'main_color': '#12f64e',
655+
'brand_text': 'My Company',
656+
'short_company_url': 'my-company',
657+
}
658+
theme = client.meetings.create_theme(params)
659+
```
660+
661+
### Add a theme to a room
662+
663+
```python
664+
meetings.add_theme_to_room('MY_ROOM_ID', 'MY_THEME_ID')
665+
```
666+
667+
### List themes
668+
669+
```python
670+
themes = client.meetings.list_themes()
671+
```
672+
673+
### Get theme information
674+
675+
```python
676+
theme = client.meetings.get_theme('MY_THEME_ID')
677+
```
678+
679+
### Delete a theme
680+
681+
```python
682+
client.meetings.delete_theme('MY_THEME_ID')
683+
```
684+
685+
### Update a theme
686+
687+
```python
688+
params = {
689+
'update_details': {
690+
'theme_name': 'updated_theme',
691+
'main_color': '#FF0000',
692+
'brand_text': 'My Updated Company Name',
693+
'short_company_url': 'updated_company_url',
694+
}
695+
}
696+
theme = client.meetings.update_theme('MY_THEME_ID', params)
697+
```
698+
699+
### List all rooms using a specified theme
700+
701+
```python
702+
rooms = client.meetings.list_rooms_with_theme_id('MY_THEME_ID')
703+
```
704+
705+
### Update the default theme for your application
706+
707+
```python
708+
response = client.meetings.update_application_theme('MY_THEME_ID')
709+
```
710+
711+
### Upload a logo to a theme
712+
713+
```python
714+
response = client.meetings.upload_logo_to_theme(
715+
theme_id='MY_THEME_ID',
716+
path_to_image='path/to/my/image.png',
717+
logo_type='white', # 'white', 'colored' or 'favicon'
718+
)
719+
```
720+
582721
## Number Insight API
583722

584723
### Basic Number Insight
@@ -909,9 +1048,9 @@ from vonage import Client
9091048

9101049
client = Client(key='YOUR_API_KEY', secret='YOUR_API_SECRET')
9111050
print(client.host()) # return rest.nexmo.com
912-
client.host('mio.nexmo.com') # rewrites the host value to mio.nexmo.com
1051+
client.host('newhost.vonage.com') # rewrites the host value to newhost.vonage.com
9131052
print(client.api_host()) # returns api.vonage.com
914-
client.api_host('myapi.vonage.com') # rewrite the value of api_host
1053+
client.api_host('myapi.vonage.com') # rewrite the value of api_host to myapi.vonage.com
9151054
```
9161055

9171056
## Frequently Asked Questions
@@ -930,6 +1069,7 @@ The following is a list of Vonage APIs and whether the Python SDK provides suppo
9301069
| Dispatch API | Beta ||
9311070
| External Accounts API | Beta ||
9321071
| Media API | Beta ||
1072+
| Meetings API | General Availability ||
9331073
| Messages API | General Availability ||
9341074
| Number Insight API | General Availability ||
9351075
| Number Management API | General Availability ||

setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
from setuptools import setup, find_packages
55

66

7-
with io.open(
8-
os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8"
9-
) as f:
7+
with io.open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8") as f:
108
long_description = f.read()
119

1210
setup(

src/vonage/client.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .account import Account
55
from .application import ApplicationV2, Application
66
from .errors import *
7+
from .meetings import Meetings
78
from .messages import Messages
89
from .number_insight import NumberInsight
910
from .number_management import Numbers
@@ -100,6 +101,7 @@ def __init__(
100101
self._jwt_claims = {}
101102
self._host = "rest.nexmo.com"
102103
self._api_host = "api.nexmo.com"
104+
self._meetings_api_host = "api-eu.vonage.com/beta/meetings"
103105

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

@@ -110,6 +112,7 @@ def __init__(
110112

111113
self.account = Account(self)
112114
self.application = Application(self)
115+
self.meetings = Meetings(self)
113116
self.messages = Messages(self)
114117
self.number_insight = NumberInsight(self)
115118
self.numbers = Numbers(self)
@@ -128,20 +131,27 @@ def __init__(
128131
)
129132
self.session.mount("https://", self.adapter)
130133

131-
# Get and Set _host attribute
134+
# Gets and sets _host attribute
132135
def host(self, value=None):
133136
if value is None:
134137
return self._host
135138
else:
136139
self._host = value
137140

138-
# Gets And Set _api_host attribute
141+
# Gets and sets _api_host attribute
139142
def api_host(self, value=None):
140143
if value is None:
141144
return self._api_host
142145
else:
143146
self._api_host = value
144147

148+
# Gets and sets _meetings_api_host attribute
149+
def meetings_api_host(self, value=None):
150+
if value is None:
151+
return self._meetings_api_host
152+
else:
153+
self._meetings_api_host = value
154+
145155
def auth(self, params=None, **kwargs):
146156
self._jwt_claims = params or kwargs
147157

@@ -261,7 +271,7 @@ def patch(self, host, request_uri, params, auth_type=None):
261271
# Only newer APIs (that expect json-bodies) currently use this method, so we will always send a json-formatted body
262272
return self.parse(host, self.session.patch(uri, json=params, headers=self._request_headers))
263273

264-
def delete(self, host, request_uri, auth_type=None):
274+
def delete(self, host, request_uri, params=None, auth_type=None):
265275
uri = f"https://{host}{request_uri}"
266276
self._request_headers = self.headers
267277

@@ -275,7 +285,11 @@ def delete(self, host, request_uri, auth_type=None):
275285
)
276286

277287
logger.debug(f"DELETE to {repr(uri)} with headers {repr(self._request_headers)}")
278-
return self.parse(host, self.session.delete(uri, headers=self._request_headers, timeout=self.timeout))
288+
if params is not None:
289+
logger.debug(f"DELETE call has params {repr(params)}")
290+
return self.parse(
291+
host, self.session.delete(uri, headers=self._request_headers, timeout=self.timeout, params=params)
292+
)
279293

280294
def parse(self, host, response: Response):
281295
logger.debug(f"Response headers {repr(response.headers)}")
@@ -291,7 +305,10 @@ def parse(self, host, response: Response):
291305
if response.json() is None:
292306
return None
293307
if content_mime == "application/json":
294-
return response.json()
308+
try:
309+
return response.json()
310+
except JSONDecodeError:
311+
pass
295312
else:
296313
return response.content
297314
elif 400 <= response.status_code < 500:
@@ -306,6 +323,11 @@ def parse(self, host, response: Response):
306323
detail = error_data["detail"]
307324
type = error_data["type"]
308325
message = f"{title}: {detail} ({type})"
326+
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}'
309331
else:
310332
message = error_data
311333
except JSONDecodeError:
@@ -320,7 +342,15 @@ def _create_jwt_auth_string(self):
320342
return b"Bearer " + self._generate_application_jwt()
321343

322344
def _generate_application_jwt(self):
323-
return self._jwt_client.generate_application_jwt(self._jwt_claims)
345+
try:
346+
return self._jwt_client.generate_application_jwt(self._jwt_claims)
347+
except AttributeError as err:
348+
if '_jwt_client' in str(err):
349+
raise ClientError(
350+
'JWT generation failed. Check that you passed in valid values for "application_id" and "private_key".'
351+
)
352+
else:
353+
raise err
324354

325355
def _create_header_auth_string(self):
326356
hash = base64.b64encode(f"{self.api_key}:{self.api_secret}".encode("utf-8")).decode("ascii")

src/vonage/errors.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ class RedactError(Error):
3333

3434

3535
class InvalidAuthenticationTypeError(Error):
36-
"""An authentication method was specified that is not allowed."""
36+
"""An authentication method was specified that is not allowed"""
37+
38+
39+
class MeetingsError(ClientError):
40+
"""An error related to the Meetings class which calls the Vonage Meetings API."""
3741

3842

3943
class Verify2Error(ClientError):

0 commit comments

Comments
 (0)