Skip to content

Commit 6e0b6b7

Browse files
authored
Merge pull request #1 from messagebird/master
update from mb repo
2 parents fb6d652 + adb6f21 commit 6e0b6b7

8 files changed

+291
-21
lines changed

.travis.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ python:
99
install:
1010
- pip install mock==2.0
1111
- pip install requests
12+
- pip install codecov
13+
- pip install pytest pytest-cov
1214
- pip install .
1315
script:
14-
- python -m unittest discover -s tests/ -p test_*.py -v
16+
- coverage run --source=messagebird -m unittest discover -s tests/
17+
- coverage report --fail-under=80
1518
matrix:
1619
allow_failures:
1720
- python: 'nightly'
1821
- python: 'pypy2.7'
22+
- python: 'pypy3.5'

examples/voice_recording_download.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python
2+
import messagebird
3+
import argparse
4+
5+
parser = argparse.ArgumentParser()
6+
parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True)
7+
parser.add_argument('--callID', help='identifier for the call', type=str, required=True)
8+
parser.add_argument('--legID', help='identifier for the leg object you wish to list the recordings for', type=str, required=True)
9+
parser.add_argument('--recordingID', help='identifier for the recording', type=str, required=True)
10+
args = vars(parser.parse_args())
11+
12+
try:
13+
client = messagebird.Client(args['accessKey'])
14+
15+
voiceRecordingLocation = client.voice_recording_download(args['callID'], args['legID'], args['recordingID'])
16+
17+
# Print the object information.
18+
print('The following information was returned as a Voice Recording object:')
19+
print(voiceRecordingLocation)
20+
21+
except messagebird.client.ErrorException as e:
22+
print('An error occured while requesting a Message object:')
23+
24+
for error in e.errors:
25+
print(' code : %d' % error.code)
26+
print(' description : %s' % error.description)
27+
print(' parameter : %s\n' % error.parameter)

examples/voice_recording_view.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python
2+
import messagebird
3+
import argparse
4+
5+
parser = argparse.ArgumentParser()
6+
parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True)
7+
parser.add_argument('--callID', help='identifier for the call', type=str, required=True)
8+
parser.add_argument('--legID', help='identifier for the leg object you wish to list the recordings for', type=str, required=True)
9+
parser.add_argument('--recordingID', help='identifier for the recording', type=str, required=True)
10+
args = vars(parser.parse_args())
11+
12+
try:
13+
client = messagebird.Client(args['accessKey'])
14+
15+
voiceRecording = client.voice_recording_view(args['callID'], args['legID'], args['recordingID'])
16+
17+
# Print the object information.
18+
print('The following information was returned as a Voice Recording object:')
19+
print(voiceRecording)
20+
21+
except messagebird.client.ErrorException as e:
22+
print('An error occured while requesting a Message object:')
23+
24+
for error in e.errors:
25+
print(' code : %d' % error.code)
26+
print(' description : %s' % error.description)
27+
print(' parameter : %s\n' % error.parameter)

examples/voice_recordings_list.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python
2+
import messagebird
3+
import argparse
4+
5+
parser = argparse.ArgumentParser()
6+
parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True)
7+
parser.add_argument('--callID', help='identifier for the call', type=str, required=True)
8+
parser.add_argument('--legID', help='identifier for the leg object you wish to list the recordings for', type=str, required=True)
9+
args = vars(parser.parse_args())
10+
11+
try:
12+
client = messagebird.Client(args['accessKey'])
13+
14+
voiceRecordingList = client.voice_recording_list_recordings(args['callID'], args['legID'])
15+
16+
# Print the object information.
17+
print('The following information was returned as a Voice Recordings List object:')
18+
print(voiceRecordingList)
19+
20+
except messagebird.client.ErrorException as e:
21+
print('An error occured while requesting a Message object:')
22+
23+
for error in e.errors:
24+
print(' code : %d' % error.code)
25+
print(' description : %s' % error.description)
26+
print(' parameter : %s\n' % error.parameter)

messagebird/client.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22
import json
3+
import io
34

45
from messagebird.balance import Balance
56
from messagebird.contact import Contact, ContactList
@@ -11,10 +12,11 @@
1112
from messagebird.voicemessage import VoiceMessage
1213
from messagebird.lookup import Lookup
1314
from messagebird.verify import Verify
14-
from messagebird.http_client import HttpClient
15+
from messagebird.http_client import HttpClient, ResponseFormat
1516
from messagebird.conversation_message import ConversationMessage, ConversationMessageList
1617
from messagebird.conversation import Conversation, ConversationList
1718
from messagebird.conversation_webhook import ConversationWebhook, ConversationWebhookList
19+
from messagebird.voice_recording import VoiceRecordingsList, VoiceRecording
1820

1921
ENDPOINT = 'https://rest.messagebird.com'
2022
CLIENT_VERSION = '1.4.1'
@@ -28,6 +30,11 @@
2830
CONVERSATION_WEB_HOOKS_PATH = 'webhooks'
2931
CONVERSATION_TYPE = 'conversation'
3032

33+
VOICE_API_ROOT = 'https://voice.messagebird.com'
34+
VOICE_PATH = 'calls'
35+
VOICE_LEGS_PATH = 'legs'
36+
VOICE_RECORDINGS_PATH = 'recordings'
37+
3138

3239
class ErrorException(Exception):
3340
def __init__(self, errors):
@@ -53,7 +60,6 @@ def _get_http_client(self, type=REST_TYPE):
5360
def request(self, path, method='GET', params=None, type=REST_TYPE):
5461
"""Builds a request, gets a response and decodes it."""
5562
response_text = self._get_http_client(type).request(path, method, params)
56-
5763
if not response_text:
5864
return response_text
5965

@@ -82,6 +88,18 @@ def request_plain_text(self, path, method='GET', params=None, type=REST_TYPE):
8288

8389
return response_text
8490

91+
def request_store_as_file(self, path, filepath, method='GET', params=None, type=REST_TYPE):
92+
"""Builds a request, gets a response and decodes it."""
93+
response_binary = self._get_http_client(type).request(path, method, params, ResponseFormat.binary)
94+
95+
if not response_binary:
96+
return response_binary
97+
98+
with io.open(filepath, 'wb') as f:
99+
f.write(response_binary)
100+
101+
return filepath
102+
85103
def balance(self):
86104
"""Retrieve your balance."""
87105
return Balance().load(self.request('balance'))
@@ -296,5 +314,27 @@ def conversation_read_webhook(self, id):
296314
uri = CONVERSATION_WEB_HOOKS_PATH + '/' + str(id)
297315
return ConversationWebhook().load(self.request(uri, 'GET', None, CONVERSATION_TYPE))
298316

317+
def voice_recording_list_recordings(self, call_id, leg_id):
318+
uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH
319+
return VoiceRecordingsList().load(self.request(uri, 'GET'))
320+
321+
def voice_recording_view(self, call_id, leg_id, recording_id):
322+
uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id)
323+
recording_response = self.request(uri, 'GET')
324+
recording_links = recording_response.get('_links')
325+
if recording_links is not None:
326+
recording_response['data'][0]['_links'] = recording_links
327+
return VoiceRecording().load(recording_response['data'][0])
328+
329+
def voice_recording_download(self, call_id, leg_id, recording_id):
330+
uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id)
331+
recording_response = self.request(uri, 'GET')
332+
recording_links = recording_response.get('_links')
333+
if recording_links is None or recording_links.get('file') is None:
334+
raise (ErrorException('There is no recording available'))
335+
recording_file = recording_links.get('file')
336+
recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_file, recording_id + '.wav')
337+
return VOICE_API_ROOT + recording_file
338+
299339
def _format_query(self, limit, offset):
300340
return 'limit=' + str(limit) + '&offset=' + str(offset)

messagebird/http_client.py

+23-18
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import json
22
import requests
3+
from enum import Enum
34

45
try:
56
from urllib.parse import urljoin
67
except ImportError:
78
from urlparse import urljoin
89

10+
class ResponseFormat(Enum):
11+
text = 1
12+
binary = 2
13+
914

1015
class HttpClient(object):
1116
"""Used for sending simple HTTP requests."""
@@ -17,7 +22,7 @@ def __init__(self, endpoint, access_key, user_agent):
1722
self.access_key = access_key
1823
self.user_agent = user_agent
1924

20-
def request(self, path, method='GET', params=None):
25+
def request(self, path, method='GET', params=None, format=ResponseFormat.text):
2126
"""Builds a request and gets a response."""
2227
if params is None: params = {}
2328
url = urljoin(self.endpoint, path)
@@ -29,22 +34,22 @@ def request(self, path, method='GET', params=None):
2934
'Content-Type': 'application/json'
3035
}
3136

32-
if method == 'DELETE':
33-
response = requests.delete(url, verify=True, headers=headers, data=json.dumps(params))
34-
elif method == 'GET':
35-
response = requests.get(url, verify=True, headers=headers, params=params)
36-
elif method == 'PATCH':
37-
response = requests.patch(url, verify=True, headers=headers, data=json.dumps(params))
38-
elif method == 'POST':
39-
response = requests.post(url, verify=True, headers=headers, data=json.dumps(params))
40-
elif method == 'PUT':
41-
response = requests.put(url, verify=True, headers=headers, data=json.dumps(params))
42-
else:
43-
raise ValueError(str(method) + ' is not a supported HTTP method')
44-
45-
if response.status_code in self.__supported_status_codes:
46-
response_text = response.text
47-
else:
37+
method_switcher = {
38+
'DELETE': requests.delete(url, verify=True, headers=headers, data=json.dumps(params)),
39+
'GET': requests.get(url, verify=True, headers=headers, params=params),
40+
'PATCH': requests.patch(url, verify=True, headers=headers, data=json.dumps(params)),
41+
'POST': requests.post(url, verify=True, headers=headers, data=json.dumps(params)),
42+
'PUT': requests.put(url, verify=True, headers=headers, data=json.dumps(params))
43+
}
44+
response = method_switcher.get(method, str(method) + ' is not a supported HTTP method')
45+
if isinstance(response, str):
46+
raise ValueError(response)
47+
48+
if response.status_code not in self.__supported_status_codes:
4849
response.raise_for_status()
4950

50-
return response_text
51+
response_switcher = {
52+
ResponseFormat.text: response.text,
53+
ResponseFormat.binary: response.content
54+
}
55+
return response_switcher.get(format)

messagebird/voice_recording.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from messagebird.base import Base
2+
3+
class VoiceRecording(Base):
4+
5+
def __init__(self):
6+
self.id = None
7+
self.format = None
8+
self.type = None
9+
self.legId = None
10+
self.status = None
11+
self.duration = None
12+
self._createdDatetime = None
13+
self._updatedDatetime = None
14+
self._links = None
15+
16+
@property
17+
def createdDatetime(self):
18+
return self._createdDatetime
19+
20+
@createdDatetime.setter
21+
def createdAt(self, value):
22+
if value is not None:
23+
self._createdDatetime = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ')
24+
25+
@property
26+
def updatedDatetime(self):
27+
return self._updatedDatetime
28+
29+
@updatedDatetime.setter
30+
def updatedAt(self, value):
31+
if value is not None:
32+
self._updatedDatetime = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ')
33+
34+
def __str__(self):
35+
return "\n".join([
36+
'recording id : %s' % self.id,
37+
'format : %s' % self.format,
38+
'type : %s' % self.type,
39+
'leg id : %s' % self.legId,
40+
'status : %s' % self.status,
41+
'duration : %s' % self.duration,
42+
'created date time : %s' % self._createdDatetime,
43+
'updated date time : %s' % self._updatedDatetime,
44+
'links : %s' % self._links
45+
])
46+
47+
class VoiceRecordingsList(Base):
48+
def __init__(self):
49+
self._items = None
50+
51+
@property
52+
def data(self):
53+
return self._items
54+
55+
@data.setter
56+
def data(self, value):
57+
if isinstance(value, list):
58+
self._items = []
59+
for item in value:
60+
self._items.append(VoiceRecording().load(item))
61+
62+
def __str__(self):
63+
item_ids = []
64+
if self._items is not None:
65+
for recording_item in self._items:
66+
item_ids.append(recording_item.id)
67+
68+
return "\n".join([
69+
'items IDs : %s' % item_ids,
70+
'count : %s' % len(item_ids)
71+
])

tests/test_voice_recording.py

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import unittest
2+
from messagebird import Client, ErrorException
3+
from datetime import datetime
4+
5+
try:
6+
from unittest.mock import Mock
7+
except ImportError:
8+
# mock was added to unittest in Python 3.3, but was an external library
9+
# before.
10+
from mock import Mock
11+
12+
13+
class TestVoiceRecording(unittest.TestCase):
14+
15+
def test_voice_recording_view(self):
16+
http_client = Mock()
17+
http_client.request.return_value = '{"data":[{"id":"12345678-9012-3456-7890-123456789012","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":32,"type":"transfer","createdAt":"2018-01-01T00:00:01Z","updatedAt":"2018-01-01T00:00:05Z","deletedAt":null}],"_links":{"file":"/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012.wav","self":"/calls/12345678-9012-3456-7890-123456789012/legs/12348765-4321-0987-6543-210987654321/recordings/12345678-9012-3456-7890-123456789012"},"pagination":{"totalCount":0,"pageCount":0,"currentPage":0,"perPage":0}}'
18+
19+
voice_recording = Client('', http_client).voice_recording_view('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012')
20+
21+
http_client.request.assert_called_once_with('https://voice.messagebird.com/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012', 'GET', None)
22+
23+
self.assertEqual('12345678-9012-3456-7890-123456789012', voice_recording.id)
24+
self.assertEqual('done', voice_recording.status)
25+
self.assertEqual('wav', voice_recording.format)
26+
self.assertEqual(datetime(2018, 1, 1, 0, 0, 1), voice_recording.createdAt)
27+
self.assertEqual(datetime(2018, 1, 1, 0, 0, 5), voice_recording.updatedAt)
28+
self.assertEqual(2, len(voice_recording._links))
29+
self.assertIsInstance(str(voice_recording), str)
30+
31+
def test_voice_recording_list(self):
32+
http_client = Mock()
33+
http_client.request.return_value = '{"data":[{"id":"12345678-9012-3456-7890-123456789012","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":32,"type":"transfer","createdAt":"2018-01-01T00:00:01Z","updatedAt":"2018-01-01T00:00:05Z","deletedAt":null,"_links":{"file":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012.wav","self":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012"}},{"id":"12345678-9012-3456-7890-123456789013","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":12,"type":"transfer","createdAt":"2019-01-01T00:00:01Z","updatedAt":"2019-01-01T00:00:05Z","deletedAt":null,"_links":{"file":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789013.wav","self":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789013"}}],"_links":{"self":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings?page=1"},"pagination":{"totalCount":2,"pageCount":1,"currentPage":1,"perPage":10}}'
34+
35+
voice_recordings = Client('', http_client).voice_recording_list_recordings('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987')
36+
37+
http_client.request.assert_called_once_with('https://voice.messagebird.com/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings', 'GET', None)
38+
39+
recordings_check = {
40+
'12345678-9012-3456-7890-123456789012': { "id": '12345678-9012-3456-7890-123456789012', "duration": 32, "year": 2018 },
41+
'12345678-9012-3456-7890-123456789013': { "id": '12345678-9012-3456-7890-123456789013', "duration": 12, "year": 2019 }
42+
}
43+
44+
for item in voice_recordings._items:
45+
recording_specific = recordings_check.get(item.id)
46+
self.assertEqual(recording_specific['id'], item.id)
47+
self.assertEqual(recording_specific['duration'], item.duration)
48+
self.assertEqual('done', item.status)
49+
self.assertEqual('wav', item.format)
50+
self.assertEqual(datetime(recording_specific['year'], 1, 1, 0, 0, 1), item.createdAt)
51+
self.assertEqual(datetime(recording_specific['year'], 1, 1, 0, 0, 5), item.updatedAt)
52+
self.assertEqual(2, len(item._links))
53+
self.assertIsInstance(str(voice_recordings), str)
54+
55+
def test_voice_recording_download(self):
56+
http_client = Mock()
57+
http_client.request.return_value = '{"data":null,"errors":[{"message":"No recording found for ID `00000000-0000-0000-0000-000000000000`.","code":13}],"pagination":{"totalCount":0,"pageCount":0,"currentPage":0,"perPage":0}}'
58+
59+
with self.assertRaises(ErrorException):
60+
voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012')
61+
62+
http_client.request.return_value = '{"data":[{"id":"12345678-9012-3456-7890-123456789012","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":32,"type":"transfer","createdAt":"2018-01-01T00:00:01Z","updatedAt":"2018-01-01T00:00:05Z","deletedAt":null}],"pagination":{"totalCount":0,"pageCount":0,"currentPage":0,"perPage":0}}'
63+
64+
with self.assertRaises(ErrorException):
65+
voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012')
66+
67+
68+
69+
if __name__ == '__main__':
70+
unittest.main()

0 commit comments

Comments
 (0)