From 01790716109578013911a47d5bd18123685a79f4 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Tue, 6 Aug 2019 16:58:07 +0200 Subject: [PATCH 01/14] GROW-1083 Python sdk: Voice API resource - Recordings --- examples/voice_recording_download.py | 27 +++++++++++ examples/voice_recording_view.py | 27 +++++++++++ examples/voice_recordings_list.py | 26 ++++++++++ messagebird/client.py | 34 +++++++++++++ messagebird/http_client.py | 32 +++++++++++++ messagebird/voice_recording.py | 71 ++++++++++++++++++++++++++++ 6 files changed, 217 insertions(+) create mode 100644 examples/voice_recording_download.py create mode 100644 examples/voice_recording_view.py create mode 100644 examples/voice_recordings_list.py create mode 100644 messagebird/voice_recording.py diff --git a/examples/voice_recording_download.py b/examples/voice_recording_download.py new file mode 100644 index 0000000..770cac4 --- /dev/null +++ b/examples/voice_recording_download.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +import messagebird +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--callID', help='identifier for the call', type=str, required=True) +parser.add_argument('--legID', help='identifier for the leg object you wish to list the recordings for', type=str, required=True) +parser.add_argument('--recordingID', help='identifier for the recording', type=str, required=True) +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + voiceRecordingLocation = client.voice_recording_download(args['callID'], args['legID'], args['recordingID']) + + # Print the object information. + print('The following information was returned as a Voice Recording object:') + print(voiceRecordingLocation) + +except messagebird.client.ErrorException as e: + print('An error occured while requesting a Message object:') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s\n' % error.parameter) diff --git a/examples/voice_recording_view.py b/examples/voice_recording_view.py new file mode 100644 index 0000000..9551f0b --- /dev/null +++ b/examples/voice_recording_view.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +import messagebird +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--callID', help='identifier for the call', type=str, required=True) +parser.add_argument('--legID', help='identifier for the leg object you wish to list the recordings for', type=str, required=True) +parser.add_argument('--recordingID', help='identifier for the recording', type=str, required=True) +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + voiceRecording = client.voice_recording_view(args['callID'], args['legID'], args['recordingID']) + + # Print the object information. + print('The following information was returned as a Voice Recording object:') + print(voiceRecording) + +except messagebird.client.ErrorException as e: + print('An error occured while requesting a Message object:') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s\n' % error.parameter) diff --git a/examples/voice_recordings_list.py b/examples/voice_recordings_list.py new file mode 100644 index 0000000..985dba0 --- /dev/null +++ b/examples/voice_recordings_list.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +import messagebird +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--callID', help='identifier for the call', type=str, required=True) +parser.add_argument('--legID', help='identifier for the leg object you wish to list the recordings for', type=str, required=True) +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + voiceRecordingList = client.voice_recording_list_recordings(args['callID'], args['legID']) + + # Print the object information. + print('The following information was returned as a Voice Recordings List object:') + print(voiceRecordingList) + +except messagebird.client.ErrorException as e: + print('An error occured while requesting a Message object:') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s\n' % error.parameter) diff --git a/messagebird/client.py b/messagebird/client.py index fdd51f6..f78825e 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -15,6 +15,7 @@ from messagebird.conversation_message import ConversationMessage, ConversationMessageList from messagebird.conversation import Conversation, ConversationList from messagebird.conversation_webhook import ConversationWebhook, ConversationWebhookList +from messagebird.voice_recording import VoiceRecordingsList, VoiceRecording ENDPOINT = 'https://rest.messagebird.com' CLIENT_VERSION = '1.4.1' @@ -28,6 +29,11 @@ CONVERSATION_WEB_HOOKS_PATH = 'webhooks' CONVERSATION_TYPE = 'conversation' +VOICE_API_ROOT = 'https://voice.messagebird.com' +VOICE_PATH = 'calls' +VOICE_LEGS_PATH = 'legs' +VOICE_RECORDINGS_PATH = 'recordings' + class ErrorException(Exception): def __init__(self, errors): @@ -82,6 +88,17 @@ def request_plain_text(self, path, method='GET', params=None, type=REST_TYPE): return response_text + def request_store_as_file(self, path, filepath, method='GET', params=None, type=REST_TYPE): + """Builds a request, gets a response and decodes it.""" + response_binary = self._get_http_client(type).request_binary(path, method, params) + + if not response_binary: + return response_binary + else: + open(filepath, 'wb').write(response_binary) + + return filepath + def balance(self): """Retrieve your balance.""" return Balance().load(self.request('balance')) @@ -292,5 +309,22 @@ def conversation_read_webhook(self, id): uri = CONVERSATION_WEB_HOOKS_PATH + '/' + str(id) return ConversationWebhook().load(self.request(uri, 'GET', None, CONVERSATION_TYPE)) + def voice_recording_list_recordings(self, call_id, leg_id): + uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + return VoiceRecordingsList().load(self.request(uri, 'GET')) + + def voice_recording_view(self, call_id, leg_id, recording_id): + uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + recording_response = self.request(uri, 'GET') + recording_response['data'][0]['_links'] = recording_response['_links'] + return VoiceRecording().load(recording_response['data'][0]) + + def voice_recording_download(self, call_id, leg_id, recording_id): + uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + recording_response = self.request(uri, 'GET') + if recording_response['_links'] is not None: + recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_response['_links']['file'], recording_response['data'][0]['id'] + '.wav') + return VOICE_API_ROOT + recording_response['_links']['file'] + def _format_query(self, limit, offset): return 'limit=' + str(limit) + '&offset=' + str(offset) diff --git a/messagebird/http_client.py b/messagebird/http_client.py index 72123c3..708966f 100644 --- a/messagebird/http_client.py +++ b/messagebird/http_client.py @@ -48,3 +48,35 @@ def request(self, path, method='GET', params=None): response.raise_for_status() return response_text + + def request_binary(self, path, method='GET', params=None): + """Builds a request and gets a response.""" + if params is None: params = {} + url = urljoin(self.endpoint, path) + + headers = { + 'Accept': 'application/json', + 'Authorization': 'AccessKey ' + self.access_key, + 'User-Agent': self.user_agent, + 'Content-Type': 'application/json' + } + + if method == 'DELETE': + response = requests.delete(url, verify=True, headers=headers, data=json.dumps(params)) + elif method == 'GET': + response = requests.get(url, verify=True, headers=headers, params=params) + elif method == 'PATCH': + response = requests.patch(url, verify=True, headers=headers, data=json.dumps(params)) + elif method == 'POST': + response = requests.post(url, verify=True, headers=headers, data=json.dumps(params)) + elif method == 'PUT': + response = requests.put(url, verify=True, headers=headers, data=json.dumps(params)) + else: + raise ValueError(str(method) + ' is not a supported HTTP method') + + if response.status_code in self.__supported_status_codes: + response_binary = response.content + else: + response.raise_for_status() + + return response_binary diff --git a/messagebird/voice_recording.py b/messagebird/voice_recording.py new file mode 100644 index 0000000..a1c228e --- /dev/null +++ b/messagebird/voice_recording.py @@ -0,0 +1,71 @@ +from messagebird.base import Base + +class VoiceRecording(Base): + + def __init__(self): + self.id = None + self.format = None + self.type = None + self.legId = None + self.status = None + self.duration = None + self._createdDatetime = None + self._updatedDatetime = None + self._links = None + + @property + def createdDatetime(self): + return self._createdDatetime + + @createdDatetime.setter + def createdAt(self, value): + if value is not None: + self._createdDatetime = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') + + @property + def updatedDatetime(self): + return self._updatedDatetime + + @updatedDatetime.setter + def updatedAt(self, value): + if value is not None: + self._updatedDatetime = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') + + def __str__(self): + return "\n".join([ + 'recording id : %s' % self.id, + 'format : %s' % self.format, + 'type : %s' % self.type, + 'leg id : %s' % self.legId, + 'status : %s' % self.status, + 'duration : %s' % self.duration, + 'created date time : %s' % self._createdDatetime, + 'updated date time : %s' % self._updatedDatetime, + 'links : %s' % self._links + ]) + +class VoiceRecordingsList(Base): + def __init__(self): + self._items = None + + @property + def data(self): + return self._items + + @data.setter + def data(self, value): + if isinstance(value, list): + self._items = [] + for item in value: + self._items.append(VoiceRecording().load(item)) + + def __str__(self): + item_ids = [] + if self._items is not None: + for recording_item in self._items: + item_ids.append(recording_item.id) + + return "\n".join([ + 'items IDs : %s' % item_ids, + 'count : %s' % len(item_ids) + ]) From af89a015d5879c955857ad94624cbc7eba0368b5 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Thu, 8 Aug 2019 14:52:04 +0200 Subject: [PATCH 02/14] Fixing comments and suggestions by Ali Ben Zarrouk --- messagebird/client.py | 18 ++++++----- messagebird/http_client.py | 66 ++++++++++++-------------------------- 2 files changed, 30 insertions(+), 54 deletions(-) diff --git a/messagebird/client.py b/messagebird/client.py index 731fc16..55530e5 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -11,7 +11,7 @@ from messagebird.voicemessage import VoiceMessage from messagebird.lookup import Lookup from messagebird.verify import Verify -from messagebird.http_client import HttpClient +from messagebird.http_client import HttpClient, ResponseFormat from messagebird.conversation_message import ConversationMessage, ConversationMessageList from messagebird.conversation import Conversation, ConversationList from messagebird.conversation_webhook import ConversationWebhook, ConversationWebhookList @@ -59,7 +59,6 @@ def _get_http_client(self, type=REST_TYPE): def request(self, path, method='GET', params=None, type=REST_TYPE): """Builds a request, gets a response and decodes it.""" response_text = self._get_http_client(type).request(path, method, params) - if not response_text: return response_text @@ -90,12 +89,13 @@ def request_plain_text(self, path, method='GET', params=None, type=REST_TYPE): def request_store_as_file(self, path, filepath, method='GET', params=None, type=REST_TYPE): """Builds a request, gets a response and decodes it.""" - response_binary = self._get_http_client(type).request_binary(path, method, params) + response_binary = self._get_http_client(type).request(path, method, params, ResponseFormat.binary) if not response_binary: return response_binary - else: - open(filepath, 'wb').write(response_binary) + + with open(filepath, 'wb') as f: + f.write(response_binary) return filepath @@ -320,14 +320,16 @@ def voice_recording_list_recordings(self, call_id, leg_id): def voice_recording_view(self, call_id, leg_id, recording_id): uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) recording_response = self.request(uri, 'GET') - recording_response['data'][0]['_links'] = recording_response['_links'] + if recording_response['_links'] is not None: + recording_response['data'][0]['_links'] = recording_response['_links'] return VoiceRecording().load(recording_response['data'][0]) def voice_recording_download(self, call_id, leg_id, recording_id): uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) recording_response = self.request(uri, 'GET') - if recording_response['_links'] is not None: - recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_response['_links']['file'], recording_response['data'][0]['id'] + '.wav') + if recording_response['_links'] is None or recording_response['_links']['file'] is None: + raise (ErrorException('There is no recording available')) + recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_response['_links']['file'], recording_response['data'][0]['id'] + '.wav') return VOICE_API_ROOT + recording_response['_links']['file'] def _format_query(self, limit, offset): diff --git a/messagebird/http_client.py b/messagebird/http_client.py index 708966f..68a2787 100644 --- a/messagebird/http_client.py +++ b/messagebird/http_client.py @@ -1,11 +1,16 @@ import json import requests +from enum import Enum try: from urllib.parse import urljoin except ImportError: from urlparse import urljoin +class ResponseFormat(Enum): + text = 1 + binary = 2 + class HttpClient(object): """Used for sending simple HTTP requests.""" @@ -17,7 +22,7 @@ def __init__(self, endpoint, access_key, user_agent): self.access_key = access_key self.user_agent = user_agent - def request(self, path, method='GET', params=None): + def request(self, path, method='GET', params=None, format=ResponseFormat.text): """Builds a request and gets a response.""" if params is None: params = {} url = urljoin(self.endpoint, path) @@ -29,54 +34,23 @@ def request(self, path, method='GET', params=None): 'Content-Type': 'application/json' } - if method == 'DELETE': - response = requests.delete(url, verify=True, headers=headers, data=json.dumps(params)) - elif method == 'GET': - response = requests.get(url, verify=True, headers=headers, params=params) - elif method == 'PATCH': - response = requests.patch(url, verify=True, headers=headers, data=json.dumps(params)) - elif method == 'POST': - response = requests.post(url, verify=True, headers=headers, data=json.dumps(params)) - elif method == 'PUT': - response = requests.put(url, verify=True, headers=headers, data=json.dumps(params)) - else: - raise ValueError(str(method) + ' is not a supported HTTP method') - - if response.status_code in self.__supported_status_codes: - response_text = response.text - else: - response.raise_for_status() - - return response_text - - def request_binary(self, path, method='GET', params=None): - """Builds a request and gets a response.""" - if params is None: params = {} - url = urljoin(self.endpoint, path) - - headers = { - 'Accept': 'application/json', - 'Authorization': 'AccessKey ' + self.access_key, - 'User-Agent': self.user_agent, - 'Content-Type': 'application/json' + method_switcher = { + 'DELETE': requests.delete(url, verify=True, headers=headers, data=json.dumps(params)), + 'GET': requests.get(url, verify=True, headers=headers, params=params), + 'PATCH': requests.patch(url, verify=True, headers=headers, data=json.dumps(params)), + 'POST': requests.post(url, verify=True, headers=headers, data=json.dumps(params)), + 'PUT': requests.put(url, verify=True, headers=headers, data=json.dumps(params)) } - - if method == 'DELETE': - response = requests.delete(url, verify=True, headers=headers, data=json.dumps(params)) - elif method == 'GET': - response = requests.get(url, verify=True, headers=headers, params=params) - elif method == 'PATCH': - response = requests.patch(url, verify=True, headers=headers, data=json.dumps(params)) - elif method == 'POST': - response = requests.post(url, verify=True, headers=headers, data=json.dumps(params)) - elif method == 'PUT': - response = requests.put(url, verify=True, headers=headers, data=json.dumps(params)) - else: - raise ValueError(str(method) + ' is not a supported HTTP method') + response = method_switcher.get(method, str(method) + ' is not a supported HTTP method') + if isinstance(response, str): + raise ValueError(response) if response.status_code in self.__supported_status_codes: - response_binary = response.content + if format == ResponseFormat.text: + response_text = response.text + else: + response_text = response.content else: response.raise_for_status() - return response_binary + return response_text From 0cdf60f175565f5d3ec93f091727190a75c95e9b Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 13:49:56 +0200 Subject: [PATCH 03/14] Adding unit tests for the voice recordings --- tests/test_voice_recording.py | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/test_voice_recording.py diff --git a/tests/test_voice_recording.py b/tests/test_voice_recording.py new file mode 100644 index 0000000..892008e --- /dev/null +++ b/tests/test_voice_recording.py @@ -0,0 +1,54 @@ +import unittest +from messagebird import Client +from datetime import datetime + +try: + from unittest.mock import Mock +except ImportError: + # mock was added to unittest in Python 3.3, but was an external library + # before. + from mock import Mock + + +class TestVoiceRecording(unittest.TestCase): + + def test_voice_recording_view(self): + http_client = Mock() + 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}}' + + voice_recording = Client('', http_client).voice_recording_view('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012') + + 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) + + self.assertEqual('12345678-9012-3456-7890-123456789012', voice_recording.id) + self.assertEqual('done', voice_recording.status) + self.assertEqual('wav', voice_recording.format) + self.assertEqual(datetime(2018, 1, 1, 0, 0, 1), voice_recording.createdAt) + self.assertEqual(datetime(2018, 1, 1, 0, 0, 5), voice_recording.updatedAt) + self.assertEqual(2, len(voice_recording._links)) + + def test_voice_recording_list(self): + http_client = Mock() + 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}}' + + voice_recordings = Client('', http_client).voice_recording_list_recordings('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987') + + 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) + + recordings_check = { + '12345678-9012-3456-7890-123456789012': { "id": '12345678-9012-3456-7890-123456789012', "duration": 32, "year": 2018 }, + '12345678-9012-3456-7890-123456789013': { "id": '12345678-9012-3456-7890-123456789013', "duration": 12, "year": 2019 } + } + + for item in voice_recordings._items: + recording_specific = recordings_check.get(item.id) + self.assertEqual(recording_specific['id'], item.id) + self.assertEqual(recording_specific['duration'], item.duration) + self.assertEqual('done', item.status) + self.assertEqual('wav', item.format) + self.assertEqual(datetime(recording_specific['year'], 1, 1, 0, 0, 1), item.createdAt) + self.assertEqual(datetime(recording_specific['year'], 1, 1, 0, 0, 5), item.updatedAt) + self.assertEqual(2, len(item._links)) + +if __name__ == '__main__': + unittest.main() From c97b8ab8ce74c50864dd63d85b59a237285ac0ab Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 13:50:33 +0200 Subject: [PATCH 04/14] Adding coverage on tests --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6869f26..9efcaf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,11 @@ python: install: - pip install mock==2.0 - pip install requests + - pip install codecov + - pip install pytest pytest-cov - pip install . script: - - python -m unittest discover -s tests/ -p test_*.py -v + - pytest --cov=messagebird tests/ matrix: allow_failures: - python: 'nightly' From 7bd98e63756535d28ee7442c822b019f9489236f Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 13:58:47 +0200 Subject: [PATCH 05/14] Fixing travis config --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9efcaf4..8aaf850 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,8 @@ install: - pip install mock==2.0 - pip install requests - pip install codecov - - pip install pytest pytest-cov + - pip install pytest==.3.6 + - pip install pytest-cov - pip install . script: - pytest --cov=messagebird tests/ From 182b989856ed8b6461dff385cdef4ed13f197d49 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 14:24:36 +0200 Subject: [PATCH 06/14] Fixing travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8aaf850..43e18cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ install: - pip install mock==2.0 - pip install requests - pip install codecov - - pip install pytest==.3.6 - - pip install pytest-cov + - pip install pytest + - pip install pytest-cov<2.6.0 - pip install . script: - pytest --cov=messagebird tests/ From 8d30d3b27a21baf908fe5d84ca62270598be5602 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 14:26:46 +0200 Subject: [PATCH 07/14] Fixing travis config --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 43e18cb..14bf4f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: - pip install requests - pip install codecov - pip install pytest - - pip install pytest-cov<2.6.0 + - pip install pytest-cov - pip install . script: - pytest --cov=messagebird tests/ @@ -19,3 +19,4 @@ matrix: allow_failures: - python: 'nightly' - python: 'pypy2.7' + - python: 'pypy3.5' From 45b26c6d18022235b1eb319cf5eb577beb5892c4 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 14:30:42 +0200 Subject: [PATCH 08/14] Adding coveralls --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 14bf4f7..a3b4042 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,5 @@ matrix: - python: 'nightly' - python: 'pypy2.7' - python: 'pypy3.5' +after_success: + — coveralls From 6f02d45e7de311428439c0415bc3ca0bd2ae9d49 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 14:32:38 +0200 Subject: [PATCH 09/14] Adding coveralls --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a3b4042..fbd3218 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,10 +15,10 @@ install: - pip install . script: - pytest --cov=messagebird tests/ +after_success: + — coveralls matrix: allow_failures: - python: 'nightly' - python: 'pypy2.7' - python: 'pypy3.5' -after_success: - — coveralls From 46b9315a382809686b7dc76f65cb583a5353790d Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Fri, 9 Aug 2019 14:35:25 +0200 Subject: [PATCH 10/14] Adding coveralls --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fbd3218..d159c53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,7 @@ install: - pip install mock==2.0 - pip install requests - pip install codecov - - pip install pytest - - pip install pytest-cov + - pip install pytest pytest-cov - pip install . script: - pytest --cov=messagebird tests/ From ea52226a3bb28fd98c6ab8edb7006365bacc351d Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Mon, 12 Aug 2019 10:07:29 +0200 Subject: [PATCH 11/14] Improved the fetching entries of dict objects by using get --- messagebird/client.py | 13 ++++++++----- tests/test_voice_recording.py | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/messagebird/client.py b/messagebird/client.py index 55530e5..e4cb3d7 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -320,17 +320,20 @@ def voice_recording_list_recordings(self, call_id, leg_id): def voice_recording_view(self, call_id, leg_id, recording_id): uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) recording_response = self.request(uri, 'GET') - if recording_response['_links'] is not None: - recording_response['data'][0]['_links'] = recording_response['_links'] + recording_links = recording_response.get('_links') + if recording_links is not None: + recording_response['data'][0]['_links'] = recording_links return VoiceRecording().load(recording_response['data'][0]) def voice_recording_download(self, call_id, leg_id, recording_id): uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) recording_response = self.request(uri, 'GET') - if recording_response['_links'] is None or recording_response['_links']['file'] is None: + recording_links = recording_response.get('_links') + if recording_links is None or recording_links.get('file') is None: raise (ErrorException('There is no recording available')) - recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_response['_links']['file'], recording_response['data'][0]['id'] + '.wav') - return VOICE_API_ROOT + recording_response['_links']['file'] + recording_file = recording_links.get('file') + recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_file, recording_id + '.wav') + return VOICE_API_ROOT + recording_file def _format_query(self, limit, offset): return 'limit=' + str(limit) + '&offset=' + str(offset) diff --git a/tests/test_voice_recording.py b/tests/test_voice_recording.py index 892008e..ae7f125 100644 --- a/tests/test_voice_recording.py +++ b/tests/test_voice_recording.py @@ -1,5 +1,5 @@ import unittest -from messagebird import Client +from messagebird import Client, ErrorException from datetime import datetime try: @@ -26,6 +26,7 @@ def test_voice_recording_view(self): self.assertEqual(datetime(2018, 1, 1, 0, 0, 1), voice_recording.createdAt) self.assertEqual(datetime(2018, 1, 1, 0, 0, 5), voice_recording.updatedAt) self.assertEqual(2, len(voice_recording._links)) + self.assertIsInstance(str(voice_recording), str) def test_voice_recording_list(self): http_client = Mock() @@ -49,6 +50,21 @@ def test_voice_recording_list(self): self.assertEqual(datetime(recording_specific['year'], 1, 1, 0, 0, 1), item.createdAt) self.assertEqual(datetime(recording_specific['year'], 1, 1, 0, 0, 5), item.updatedAt) self.assertEqual(2, len(item._links)) + self.assertIsInstance(str(voice_recordings), str) + + def test_voice_recording_download(self): + http_client = Mock() + 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}}' + + with self.assertRaises(ErrorException): + voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012') + + 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}}' + + with self.assertRaises(ErrorException): + voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012') + + if __name__ == '__main__': unittest.main() From c628ce244edcdf8ab1b68c400be67a244aa99749 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Mon, 12 Aug 2019 10:09:48 +0200 Subject: [PATCH 12/14] Swapped pytest+coveralls for coverage command --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d159c53..5c9e0e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,8 @@ install: - pip install pytest pytest-cov - pip install . script: - - pytest --cov=messagebird tests/ -after_success: - — coveralls + - coverage run --source=messagebird -m unittest discover -s tests/ + - coverage report --fail-under=80 matrix: allow_failures: - python: 'nightly' From 5b70161ce4489816dca4a6d2866df7bf263f89a4 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Mon, 12 Aug 2019 10:40:07 +0200 Subject: [PATCH 13/14] Returning the correct content via a switcher --- messagebird/http_client.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/messagebird/http_client.py b/messagebird/http_client.py index 68a2787..f18f1b0 100644 --- a/messagebird/http_client.py +++ b/messagebird/http_client.py @@ -45,12 +45,11 @@ def request(self, path, method='GET', params=None, format=ResponseFormat.text): if isinstance(response, str): raise ValueError(response) - if response.status_code in self.__supported_status_codes: - if format == ResponseFormat.text: - response_text = response.text - else: - response_text = response.content - else: + if response.status_code not in self.__supported_status_codes: response.raise_for_status() - return response_text + response_switcher = { + ResponseFormat.text: response.text, + ResponseFormat.binary: response.content + } + return response_switcher.get(format) From 7b27b95373294a0783c0995c9a24384bb20539c6 Mon Sep 17 00:00:00 2001 From: Art van Scheppingen Date: Thu, 15 Aug 2019 14:49:59 +0200 Subject: [PATCH 14/14] Using IO instead of built in for Py2.7 compatibility --- messagebird/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/messagebird/client.py b/messagebird/client.py index e4cb3d7..11b4d66 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -1,5 +1,6 @@ import sys import json +import io from messagebird.balance import Balance from messagebird.contact import Contact, ContactList @@ -94,7 +95,7 @@ def request_store_as_file(self, path, filepath, method='GET', params=None, type= if not response_binary: return response_binary - with open(filepath, 'wb') as f: + with io.open(filepath, 'wb') as f: f.write(response_binary) return filepath