diff --git a/.gitignore b/.gitignore index 1a30f4a..1b2be6e 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ target/ # PyCharm .idea/ + +# pyvenv +venv/ \ No newline at end of file diff --git a/examples/call_delete.py b/examples/call_delete.py new file mode 100644 index 0000000..1b2fe5e --- /dev/null +++ b/examples/call_delete.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +import os +import sys +import json +import argparse +import requests + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import messagebird + +parser = argparse.ArgumentParser(usage='call_create.py\ + --accessKey="*******" \ + --callId=dda20377-72da-4846-9b2c-0fea3ad4bcb6 \ +') +parser.add_argument('--accessKey', help='Access key for MessageBird API.', type=str, required=True) +parser.add_argument('--callId', help='The ID of the MessageBird call to delete.', type=str, required=True) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + + # Create a call for the specified callID. + call = client.call_delete(args['callId']) + + # If no error is thrown, means delete was successful. + print('\nDeleted call with id `%s` successfully!' % args['callId']) + +except messagebird.client.ErrorException as e: + print('\nAn error occurred while creating a call:\n') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s' % error.parameter) + print(' type : %s' % error.__class__) + +except requests.exceptions.HTTPError as e: + print('\nAn http exception occurred while deleting a call:') + print(' ', e) + print(' Http request body: ', e.request.body) + print(' Http response status: ', e.response.status_code) + print(' Http response body: ', e.response.content.decode()) + +except Exception as e: + print('\nAn ', e.__class__, ' exception occurred while deleting a call:') + print(e) + diff --git a/examples/call_list.py b/examples/call_list.py new file mode 100644 index 0000000..25e059a --- /dev/null +++ b/examples/call_list.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +import os +import sys +import argparse +import requests + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import messagebird + + +parser = argparse.ArgumentParser(usage='call_create.py\ + --accessKey="*******" \ + --page=1 \ +') +parser.add_argument('--accessKey', help='Access key for MessageBird API.', type=str, required=True) +parser.add_argument('--page', help='The page you wish to view.', type=str, required=False, default=1) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + del(args['accessKey']) + + # Create a call for the specified callID. + callList = client.call_list(**args) + + # Print the object information. + print('\nThe following information was returned as a %s object:\n' % callList.__class__) + if callList.items is not None: + print(' Containing the the following items:') + for item in callList.items: + print(' {') + print(' id : %s' % item.id) + print(' status : %s' % item.status) + print(' source : %s' % item.source) + print(' destination : %s' % item.destination) + print(' createdAt : %s' % item.createdAt) + print(' updatedAt : %s' % item.updatedAt) + print(' endedAt : %s' % item.endedAt) + print(' },') + else: + print(' With an empty response.') + +except messagebird.client.ErrorException as e: + print('\nAn error occurred while listing calls:\n') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s' % error.parameter) + print(' type : %s' % error.__class__) + +except requests.exceptions.HTTPError as e: + print('\nAn HTTP exception occurred while listing calls:') + print(' ', e) + print(' Http request body: ', e.request.body) + print(' Http response status: ', e.response.status_code) + print(' Http response body: ', e.response.content.decode()) + +except Exception as e: + print('\nAn ', e.__class__, ' exception occurred while creating a call:') + print(e) + + diff --git a/messagebird/base_list.py b/messagebird/base_list.py index f77a290..e5911cd 100644 --- a/messagebird/base_list.py +++ b/messagebird/base_list.py @@ -43,3 +43,7 @@ def items(self, value): items.append(self.itemType().load(item)) self._items = items + + def __str__(self): + items_count = 0 if self.items is None else len(self.items) + return str(self.__class__) + " with %d items.\n" % items_count diff --git a/messagebird/call_list.py b/messagebird/call_list.py new file mode 100644 index 0000000..0d2ec4f --- /dev/null +++ b/messagebird/call_list.py @@ -0,0 +1,42 @@ +from messagebird.base_list import BaseList +from messagebird.call_data import CallData + + +class CallList(BaseList): + def __init__(self): + # We're expecting items of type CallData + super(CallList, self).__init__(CallData) + self.perPage = None + self.currentPage = None + self.pageCount = None + self._pagination = None + + @property + def data(self): + return self.items + + @property + def pagination(self): + return { + "totalCount": self.totalCount, + "pageCount": self.pageCount, + "currentPage": self.currentPage, + "perPage": self.perPage + } + + @pagination.setter + def pagination(self, value): + if isinstance(value, dict): + self.totalCount = value['totalCount'] + self.pageCount = value['pageCount'] + self.currentPage = value['currentPage'] + self.perPage = value['perPage'] + self.limit = self.perPage * self.currentPage + self.offset = self.perPage * (self.currentPage - 1) + + @data.setter + def data(self, value): + if isinstance(value, list): + self.count = len(value) + self.items = value + diff --git a/messagebird/client.py b/messagebird/client.py index 7444f74..fe9d1ec 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -5,6 +5,7 @@ from messagebird.balance import Balance from messagebird.call import Call +from messagebird.call_list import CallList from messagebird.contact import Contact, ContactList from messagebird.error import Error from messagebird.group import Group, GroupList @@ -50,6 +51,10 @@ def __init__(self, errors): message = ' '.join([str(e) for e in self.errors]) super(ErrorException, self).__init__(message) +class SignleErrorException(Exception): + def __init__(self, errorMessage): + super(SignleErrorException, self).__init__(errorMessage) + class Feature(enum.Enum): ENABLE_CONVERSATIONS_API_WHATSAPP_SANDBOX = 1 @@ -124,6 +129,18 @@ def call(self,id): """Retrieve the information of a specific call""" return Call().load(self.request('calls/' + str(id), 'GET', None, VOICE_TYPE)) + def call_list(self, page=1): + """Listing calls + + Args: + page(int) : The page to list. + Raises: + ErrorException : On api returning errors + + Returns: + CallList(object) : The list of calls requested & their status.""" + return CallList().load(self.request('calls/?page=' + str(page), 'GET', None, VOICE_TYPE)) + def call_create(self, source, destination, callFlow, webhook): """Creating a call @@ -133,16 +150,23 @@ def call_create(self, source, destination, callFlow, webhook): callFlow(object) : The call flow object to be executed when the call is answered. webhook(object) : The webhook object containing the url & required token. Raises: - ErrorException: On api returning errors + ErrorException : On api returning errors Returns: - If successful, this request will return an object with a data property, which is an array that has a single - call object. If the request failed, an error object will be returned.""" + Call(object) : The Call object just created.""" params = locals() del(params['self']) return Call().load(self.request('calls', 'POST', params, VOICE_TYPE)) + def call_delete(self, id): + """Delete an existing call object.""" + response = self.request_plain_text('calls/' + str(id), 'DELETE', None, VOICE_TYPE) + + # successful delete should be empty + if len(response) > 0: + raise SignleErrorException(response) + def hlr(self, id): """Retrieve the information of a specific HLR lookup.""" return HLR().load(self.request('hlr/' + str(id))) diff --git a/tests/test_call.py b/tests/test_call.py index 8726ccf..22ee6f4 100644 --- a/tests/test_call.py +++ b/tests/test_call.py @@ -2,6 +2,7 @@ import unittest from messagebird import Client, ErrorException from messagebird.base import Base +from messagebird.client import VOICE_TYPE try: from unittest.mock import Mock @@ -47,6 +48,68 @@ def test_call(self): self.assertEqual('ended', call.data.status) + def test_call_list(self): + http_client = Mock() + http_client.request.return_value = """ + { + "data":[ + { + "id":"dda20377-72da-4846-9b2c-0fea3ad4bcb6", + "status":"no_answer", + "source":"16479311111", + "destination":"1416555555", + "createdAt":"2019-08-06T13:17:06Z", + "updatedAt":"2019-08-06T13:17:39Z", + "endedAt":"2019-08-06T13:17:39Z", + "_links":{ + "legs":"/calls/dda20377-72da-4846-9b2c-0fea3ad4bcb6/legs", + "self":"/calls/dda20377-72da-4846-9b2c-0fea3ad4bcb6" + } + }, + { + "id":"1541535b-9b80-4002-bde5-ed05b5ebed76", + "status":"ended", + "source":"16479311111", + "destination":"1416555556", + "createdAt":"2019-08-06T13:17:06Z", + "updatedAt":"2019-08-06T13:17:39Z", + "endedAt":"2019-08-06T13:17:39Z", + "_links":{ + "legs":"/calls/1541535b-9b80-4002-bde5-ed05b5ebed76/legs", + "self":"/calls/1541535b-9b80-4002-bde5-ed05b5ebed76" + } + } + ], + "_links": { + "self": "/calls?page=1" + }, + "pagination":{ + "totalCount":2, + "pageCount":1, + "currentPage":1, + "perPage":10 + } + } + """ + + callList = Client('', http_client).call_list(page=1) + + http_client.request.assert_called_once_with('calls/?page=1', 'GET', None) + + # check data is processed + self.assertEqual('no_answer', callList.data[0].status) + self.assertEqual('ended', callList.data[1].status) + + # check pagination is passed to object + self.assertEqual(2, callList.totalCount) + self.assertEqual(1, callList.pageCount) + self.assertEqual(1, callList.currentPage) + self.assertEqual(10, callList.perPage) + self.assertEqual(10, callList.pagination['perPage'], 'Check it also supports API pagination format.') + + self.assertEqual(0, callList.offset, 'Check it correctly calculates offset.') + self.assertEqual(10, callList.limit, 'Check it correctly calculates limit.') + def test_call_create(self): api_response = { "data": [ @@ -100,17 +163,15 @@ def test_call_create(self): self.assertEqual(expected_data, response_data, 'Check client response contains the API response data.') # check it can be formatted as string - expected_call_string = 'id : None\n' + \ - 'data.id : 21025ed1-cc1d-4554-ac05-043fa6c84e00\n' + \ - 'data.status : queued\n' + \ - 'data.source : 31644556677\n' + \ - 'data.destination : 31612345678\n' + \ - 'data.webhook : None\n' + \ - 'data.updatedAt : 2017-08-30 07:35:37+00:00\n' + \ - 'data.createdAt : 2017-08-30 07:35:37+00:00\n' + \ - 'data.endedAt : None' - self.assertEqual(expected_call_string, str(call_creation_response), 'Check returned call can be formatted as' + - ' string') + self.assertTrue(len(str(call_creation_response)) > 0, 'Check returned call can be formatted as string.') + + def test_call_delete(self): + http_client = Mock() + http_client.request.return_value = '' + call_id_to_delete = '21025ed1-cc1d-4554-ac05-043fa6c84e00' + Client('', http_client).call_delete(call_id_to_delete) + + http_client.request.assert_called_once_with('calls/%s' % call_id_to_delete, 'DELETE', None) @staticmethod def create_expected_call_data_based_on_api_response(api_response):