diff --git a/examples/number_available_list.py b/examples/number_available_list.py new file mode 100644 index 0000000..c5dd3a0 --- /dev/null +++ b/examples/number_available_list.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +import sys +import os +import argparse +import requests + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +import messagebird + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + + # Fetch the NumberList object with specified params, limit, offset. + params = {'features': ['sms', 'voice'], 'number': 319} + numbers = client.available_numbers_list('NL', params, 2, 0) + + # Print the object information. + print('\nThe following information was returned as a %s object:\n' % numbers.__class__) + if numbers.items is not None: + print(' Containing the the following items:') + for item in numbers.items: + print(' {') + print(' number : %s' % item.number) + print(' country : %s' % item.country) + print(' region : %s' % item.region) + print(' locality : %s' % item.locality) + print(' features : %s' % item.features) + print(' tags : %s' % item.tags) + print(' type : %s' % item.type) + print(' status : %s' % item.status) + print(' },') + else: + print(' With an empty response.') + +except messagebird.client.ErrorException as e: + print('\nAn error occured while requesting a NumberList object:\n') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s\n' % error.parameter) + +except requests.exceptions.HTTPError as e: + print('\nAn HTTP exception occurred while fetching all purchased phone numbers:') + 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 :') + print(e) diff --git a/examples/number_purchase.py b/examples/number_purchase.py new file mode 100644 index 0000000..63f72d9 --- /dev/null +++ b/examples/number_purchase.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import sys +import os +import argparse +import requests + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +import messagebird + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + + # Purchase number + number = client.purchase_number('3197010240126', 'NL', 3) + + # Print the object information. + print('\nThe following information was returned as a Number object:\n') + print(' number : %s' % number.number) + print(' country : %s' % number.country) + print(' region : %s' % number.region) + print(' locality : %s' % number.locality) + print(' features : %s' % number.features) + print(' tags : %s' % number.tags) + print(' type : %s' % number.type) + print(' status : %s' % number.status) + +except messagebird.client.ErrorException as e: + print('\nAn error occured while requesting a NumberList object:\n') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s\n' % error.parameter) + +except requests.exceptions.HTTPError as e: + print('\nAn HTTP exception occurred while fetching all purchased phone numbers:') + 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 :') + print(e) diff --git a/examples/number_purchased_list.py b/examples/number_purchased_list.py new file mode 100644 index 0000000..0d86f71 --- /dev/null +++ b/examples/number_purchased_list.py @@ -0,0 +1,78 @@ +#!/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() +parser.add_argument('--accessKey', help='Access key for MessageBird API.', type=str, required=True) +parser.add_argument('--limit', help='Limit the amount of results per page.', type=int, required=False, default=20) +parser.add_argument('--offset', help='Skip first n results.', type=int, required=False, default=0) +parser.add_argument('--features', help='Features for which search is done.', type=str, required=False, default=None) +parser.add_argument('--tags', help='Tags for which search is done.', type=str, required=False, default=None) +parser.add_argument('--number', help='Fragment of number.', type=str, required=False, default=None) +parser.add_argument('--region', help='Fragment of region data.', type=str, required=False, default=None) +parser.add_argument('--locality', help='Fragment of locality data.', type=str, required=False, default=None) +parser.add_argument('--type', help='Number type.', type=str, required=False, default=None) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + del(args['accessKey']) + + limit = 20 + offset = 0 + if args['limit'] is not None: + limit = args['limit'] + del(args['limit']) + if args['offset'] is not None: + offset = args['offset'] + del(args['offset']) + + # Fetching all purchased phone numbers. + numbers = client.purchased_numbers_list(args, limit, offset) + + # Print the object information. + print('\nThe following information was returned as a %s object:\n' % numbers.__class__) + if numbers.items is not None: + print(' Containing the the following items:') + for item in numbers.items: + print(' {') + print(' number : %s' % item.number) + print(' country : %s' % item.country) + print(' region : %s' % item.region) + print(' locality : %s' % item.locality) + print(' features : %s' % item.features) + print(' tags : %s' % item.tags) + print(' type : %s' % item.type) + print(' status : %s' % item.status) + print(' },') + else: + print(' With an empty response.') + +except messagebird.client.ErrorException as e: + print('\nAn error occurred while fetching all purchased phone numbers:\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 fetching all purchased phone numbers:') + 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 :') + print(e) + + + diff --git a/examples/number_purchased_view.py b/examples/number_purchased_view.py new file mode 100644 index 0000000..8bed53a --- /dev/null +++ b/examples/number_purchased_view.py @@ -0,0 +1,59 @@ +#!/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() +parser.add_argument('--accessKey', help='Access key for MessageBird API.', type=str, required=True) +parser.add_argument('--phoneNumber', help='Phone number.', type=str, required=True) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + + # Fetching all purchased phone numbers. + item = client.purchased_number(args['phoneNumber']) + + # Print the object information. + print('\nThe following information was returned as a %s object:\n' % item.__class__) + if item is not None: + print(' {') + print(' number : %s' % item.number) + print(' country : %s' % item.country) + print(' region : %s' % item.region) + print(' locality : %s' % item.locality) + print(' features : %s' % item.features) + print(' tags : %s' % item.tags) + print(' type : %s' % item.type) + print(' status : %s' % item.status) + print(' },') + else: + print(' With an empty response.') + +except messagebird.client.ErrorException as e: + print('\nAn error occurred while fetching all purchased phone numbers:\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 fetching all purchased phone numbers:') + 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 :') + print(e) + + + diff --git a/examples/number_update.py b/examples/number_update.py new file mode 100644 index 0000000..df653dd --- /dev/null +++ b/examples/number_update.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +import sys +import os +import argparse +import requests + +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +import messagebird + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +args = vars(parser.parse_args()) + +try: + # Create a MessageBird client with the specified accessKey. + client = messagebird.Client(args['accessKey']) + + # Update number + # Note: at the moment, we only support updating tags that can be used to group or label numbers + tags = ['tag1'] + number = client.update_number('3197010240126', tags) + + # Print the object information. + print('\nThe following information was returned as a Number object:\n') + print(' number : %s' % number.number) + print(' country : %s' % number.country) + print(' region : %s' % number.region) + print(' locality : %s' % number.locality) + print(' features : %s' % number.features) + print(' tags : %s' % number.tags) + print(' type : %s' % number.type) + print(' status : %s' % number.status) + +except messagebird.client.ErrorException as e: + print('\nAn error occured while requesting a NumberList object:\n') + + for error in e.errors: + print(' code : %d' % error.code) + print(' description : %s' % error.description) + print(' parameter : %s\n' % error.parameter) + +except requests.exceptions.HTTPError as e: + print('\nAn HTTP exception occurred while fetching all purchased phone numbers:') + 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 :') + print(e) diff --git a/messagebird/base_list.py b/messagebird/base_list.py index 0618170..cca865b 100644 --- a/messagebird/base_list.py +++ b/messagebird/base_list.py @@ -39,8 +39,9 @@ def items(self): def items(self, value): """Create typed objects from the dicts.""" items = [] - for item in value: - items.append(self.itemType().load(item)) + if value is not None: + for item in value: + items.append(self.itemType().load(item)) self._items = items diff --git a/messagebird/client.py b/messagebird/client.py index dbcf778..f855971 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -23,6 +23,7 @@ from messagebird.voice_recording import VoiceRecordingsList, VoiceRecording from messagebird.voice_transcription import VoiceTranscriptionsList, VoiceTranscriptionsView from messagebird.call_flow import CallFlow, CallFlowList, CallFlowNumberList +from messagebird.number import Number, NumberList ENDPOINT = 'https://rest.messagebird.com' CLIENT_VERSION = '1.4.1' @@ -46,6 +47,11 @@ VOICE_TRANSCRIPTIONS_PATH = 'transcriptions' VOICE_WEB_HOOKS_PATH = 'webhooks' +NUMBER_TYPE = 'number' +NUMBER_API_ROOT = 'https://numbers.messagebird.com/v1/' +NUMBER_PATH = 'phone-numbers' +NUMBER_AVAILABLE_PATH = 'available-phone-numbers' + class ErrorException(Exception): def __init__(self, errors): @@ -81,6 +87,9 @@ def _get_http_client(self, type=REST_TYPE): if type == VOICE_TYPE: return HttpClient(VOICE_API_ROOT, self.access_key, USER_AGENT) + if type == NUMBER_TYPE: + return HttpClient(NUMBER_API_ROOT, self.access_key, USER_AGENT) + return HttpClient(ENDPOINT, self.access_key, USER_AGENT) def request(self, path, method='GET', params=None, type=REST_TYPE): @@ -498,6 +507,31 @@ def call_flow_numbers_add(self, call_flow_id, numbers=()): def _format_query(self, limit, offset): return 'limit=' + str(limit) + '&offset=' + str(offset) + def available_numbers_list(self, country, params={}, limit=20, offset=0): + """Retrieve a list of phone numbers available for purchase.""" + params['limit'] = limit + params['offset'] = offset + return NumberList().load(self.request(NUMBER_AVAILABLE_PATH + '/' + str(country), 'GET', params, NUMBER_TYPE)) + + def purchase_number(self, number, country, billingIntervalMonths=1): + params = {'number': str(number), 'countryCode': str(country), 'billingIntervalMonths': int(billingIntervalMonths)} + return Number().load(self.request(NUMBER_PATH, 'POST', params, NUMBER_TYPE)) + + def update_number(self, number, tags): + params = {'tags': tags} + return Number().load(self.request(NUMBER_PATH + '/' + str(number), 'PATCH', params, NUMBER_TYPE)) + + def delete_number(self, number): + self.request(NUMBER_PATH + '/' + str(number), 'DELETE', None, NUMBER_TYPE) + + def purchased_numbers_list(self, params={}, limit=20, offset=0): + params['limit'] = limit + params['offset'] = offset + return NumberList().load(self.request(NUMBER_PATH, 'GET', params, NUMBER_TYPE)) + + def purchased_number(self, number): + return Number().load(self.request(NUMBER_PATH + '/' + number, 'GET', None, NUMBER_TYPE)) + @staticmethod def generate_voice_calls_url(call_id=None, leg_id=None, recording_id=None): uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' diff --git a/messagebird/number.py b/messagebird/number.py new file mode 100644 index 0000000..2362f47 --- /dev/null +++ b/messagebird/number.py @@ -0,0 +1,20 @@ +from messagebird.base import Base +from messagebird.base_list import BaseList + + +class NumberList(BaseList): + def __init__(self): + # We're expecting items of type Number + super(NumberList, self).__init__(Number) + + +class Number(Base): + def __init__(self): + self.number = None + self.country = None + self.region = None + self.locality = None + self.features = None + self.tags = None + self.type = None + self.status = None diff --git a/tests/test_number.py b/tests/test_number.py new file mode 100644 index 0000000..dc231f6 --- /dev/null +++ b/tests/test_number.py @@ -0,0 +1,80 @@ +import unittest +from messagebird import Client, ErrorException + +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 TestNumber(unittest.TestCase): + + def test_available_numbers_list(self): + http_client = Mock() + http_client.request.return_value = '{"items":[{"number":"3197010260188","country":"NL","region":"","locality":"","features":["sms","voice"],"type":"mobile"}],"limit":20,"count":1}' + + numbers = Client('', http_client).available_numbers_list('NL', {'number': 319}) + + http_client.request.assert_called_once_with('available-phone-numbers/NL', 'GET', {'number': 319, 'limit': 20, 'offset': 0}) + + self.assertEqual(1, numbers.count) + self.assertEqual(1, len(numbers.items)) + self.assertEqual('3197010260188', numbers.items[0].number) + + def test_purchase_number(self): + http_client = Mock() + http_client.request.return_value = '{"number":"31971234567","country":"NL","region":"Haarlem","locality":"Haarlem","features":["sms","voice"],"tags":[],"type":"landline_or_mobile","status":"active","createdAt":"2019-04-25T14:04:04Z","renewalAt":"2019-05-25T00:00:00Z"}' + + number = Client('', http_client).purchase_number('31971234567', 'NL', 1) + + http_client.request.assert_called_once_with( + 'phone-numbers', 'POST', + { + "number": "31971234567", + "countryCode": "NL", + "billingIntervalMonths": 1 + } + ) + + self.assertEqual('Haarlem', number.region) + self.assertEqual(["sms", "voice"], number.features) + + def test_delete_number(self): + http_client = Mock() + http_client.request.return_value = '{}' + + Client('', http_client).delete_number('31971234567') + + http_client.request.assert_called_once_with('phone-numbers/31971234567', 'DELETE', None) + + def test_delete_number_invalid(self): + http_client = Mock() + http_client.request.return_value = '{"errors": [{"code": 20, "description": "number not found", "parameter": null}]}' + + with self.assertRaises(ErrorException): + Client('', http_client).delete_number('non-existent-number') + + http_client.request.assert_called_once_with('phone-numbers/non-existent-number', 'DELETE', None) + + def test_purchased_number(self): + http_client = Mock() + http_client.request.return_value = '{"number":"31612345670","country":"NL","region":"Texel","locality":"Texel","features":["sms","voice"],"tags":[],"type":"mobile","status":"active"}' + number = Client('', http_client).purchased_number('31612345670') + + http_client.request.assert_called_once_with('phone-numbers/31612345670', 'GET', None) + + self.assertEqual('Texel', number.locality) + + def test_purchased_numbers_list(self): + http_client = Mock() + http_client.request.return_value = '{"items":[{"number":"3197010260188","country":"NL","region":"","locality":"","features":["sms","voice"],"type":"mobile"}],"limit":20,"count":1}' + + numbers = Client('', http_client).purchased_numbers_list({'number': 319}, 40, 2) + + http_client.request.assert_called_once_with('phone-numbers', 'GET', {'number': 319, 'limit': 40, 'offset': 2}) + + self.assertEqual(1, numbers.count) + self.assertEqual(1, len(numbers.items)) + self.assertEqual('3197010260188', numbers.items[0].number)