diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2aebb87 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: python +python: + - 'pypy2.7' + - 'pypy3.5' + - '2.7' + - '3.3' + - '3.6' + - 'nightly' +install: + - pip install mock==2.0 + - pip install requests +script: + - python -m unittest discover -s tests/ -p test_*.py -v +matrix: + allow_failures: + - python: 'nightly' + - python: 'pypy2.7' diff --git a/README.md b/README.md index 4a51510..5adac28 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ MessageBird's REST API for Python ================================= This repository contains the open source Python client for MessageBird's REST API. Documentation can be found at: https://developers.messagebird.com/. +[![Build Status](https://travis-ci.org/messagebird/python-rest-api.svg?branch=master)](https://travis-ci.org/messagebird/python-rest-api) + Requirements ------------ - [Sign up](https://www.messagebird.com/en/signup) for a free MessageBird account diff --git a/messagebird/client.py b/messagebird/client.py index 6ffceeb..64c25bf 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -1,16 +1,11 @@ import sys import json -import requests - -try: - from urllib.parse import urljoin -except ImportError: - from urlparse import urljoin from messagebird.base import Base from messagebird.balance import Balance from messagebird.error import Error from messagebird.hlr import HLR +from messagebird.http_client import HttpClient from messagebird.message import Message from messagebird.voicemessage import VoiceMessage from messagebird.lookup import Lookup @@ -19,6 +14,7 @@ ENDPOINT = 'https://rest.messagebird.com' CLIENT_VERSION = '1.2.1' PYTHON_VERSION = '%d.%d.%d' % (sys.version_info[0], sys.version_info[1], sys.version_info[2]) +USER_AGENT = 'MessageBird/ApiClient/%s Python/%s' % (CLIENT_VERSION, PYTHON_VERSION) class ErrorException(Exception): @@ -29,35 +25,23 @@ def __init__(self, errors): class Client(object): - def __init__(self, access_key): + def __init__(self, access_key, http_client=None): self.access_key = access_key - self._supported_status_codes = [200, 201, 204, 401, 404, 405, 422] - - def request(self, path, method='GET', params=None): - if params is None: params = {} - url = urljoin(ENDPOINT, path) - headers = { - 'Accept' : 'application/json', - 'Authorization' : 'AccessKey ' + self.access_key, - 'User-Agent' : 'MessageBird/ApiClient/%s Python/%s' % (CLIENT_VERSION, PYTHON_VERSION), - 'Content-Type' : 'application/json' - } - - if method == 'GET': - response = requests.get(url, verify=True, headers=headers, params=params) + if http_client is None: + self.http_client = HttpClient(ENDPOINT, access_key, USER_AGENT) else: - response = requests.post(url, verify=True, headers=headers, data=json.dumps(params)) + self.http_client = http_client - if response.status_code in self._supported_status_codes: - json_response = response.json() - else: - response.raise_for_status() + def request(self, path, method='GET', params=None): + """Builds a request, gets a response and decodes it.""" + response_text = self.http_client.request(path, method, params) + response_json = json.loads(response_text) - if 'errors' in json_response: - raise(ErrorException([Error().load(e) for e in json_response['errors']])) + if 'errors' in response_json: + raise(ErrorException([Error().load(e) for e in response_json['errors']])) - return json_response + return response_json def balance(self): """Retrieve your balance.""" diff --git a/messagebird/http_client.py b/messagebird/http_client.py new file mode 100644 index 0000000..e07a10c --- /dev/null +++ b/messagebird/http_client.py @@ -0,0 +1,41 @@ +import requests + +try: + from urllib.parse import urljoin +except ImportError: + from urlparse import urljoin + + +class HttpClient(object): + """Used for sending simple HTTP requests.""" + + def __init__(self, endpoint, access_key, user_agent): + self.__supported_status_codes = [200, 201, 204, 401, 404, 405, 422] + + self.endpoint = endpoint + self.access_key = access_key + self.user_agent = user_agent + + def request(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 == 'GET': + response = requests.get(url, verify=True, headers=headers, params=params) + else: + response = requests.post(url, verify=True, headers=headers, data=json.dumps(params)) + + if response.status_code in self.__supported_status_codes: + response_text = response.text + else: + response.raise_for_status() + + return response_text \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_balance.py b/tests/test_balance.py new file mode 100644 index 0000000..ca025ee --- /dev/null +++ b/tests/test_balance.py @@ -0,0 +1,23 @@ +import unittest +from messagebird import Client + +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 TestBalance(unittest.TestCase): + + def test_balance(self): + http_client = Mock() + http_client.request.return_value = '{"payment": "prepaid","type": "credits","amount": 9.2}' + + balance = Client('', http_client).balance() + + http_client.request.assert_called_once_with('balance', 'GET', None) + + self.assertEqual('prepaid', balance.payment) + self.assertEqual('credits', balance.type) diff --git a/tests/test_hlr.py b/tests/test_hlr.py new file mode 100644 index 0000000..5f2fa2b --- /dev/null +++ b/tests/test_hlr.py @@ -0,0 +1,30 @@ +import unittest +from messagebird import Client + +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 TestHLR(unittest.TestCase): + + def test_hlr(self): + http_client = Mock() + http_client.request.return_value = '{"id":"hlr-id","href":"https://rest.messagebird.com/hlr/hlr-id","msisdn":31612345678,"network":20406,"reference":"MyReference","status": "sent","createdDatetime": "2015-01-04T13:14:08+00:00","statusDatetime": "2015-01-04T13:14:09+00:00"}' + + hlr = Client('', http_client).hlr('hlr-id') + + http_client.request.assert_called_once_with('hlr/hlr-id', 'GET', None) + + self.assertEqual('hlr-id', hlr.id) + + def test_hlr_create(self): + http_client = Mock() + http_client.request.return_value = '{}' + + Client('', http_client).hlr_create(31612345678, 'MyReference') + + http_client.request.assert_called_once_with('hlr', 'POST', {'msisdn': 31612345678, 'reference': 'MyReference'}) diff --git a/tests/test_lookup.py b/tests/test_lookup.py new file mode 100644 index 0000000..2d2d534 --- /dev/null +++ b/tests/test_lookup.py @@ -0,0 +1,40 @@ +import unittest +from messagebird import Client + +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 TestLookup(unittest.TestCase): + + def test_lookup(self): + http_client = Mock() + http_client.request.return_value = '{"href": "https://rest.messagebird.com/lookup/31612345678","countryCode": "NL","countryPrefix": 31,"phoneNumber": 31612345678,"type": "mobile","formats": {"e164": "+31612345678","international": "+31 6 12345678","national": "06 12345678","rfc3966": "tel:+31-6-12345678"},"hlr": {"id": "hlr-id","network": 20416,"reference": "reference2000","status": "active","createdDatetime": "2015-12-15T08:19:24+00:00","statusDatetime": "2015-12-15T08:19:25+00:00"}}' + + lookup = Client('', http_client).lookup('0612345678', {'countryCode': 'NL'}) + + http_client.request.assert_called_once_with('lookup/0612345678', 'GET', {'countryCode': 'NL'}) + + self.assertEqual('mobile', lookup.type) + + def test_lookup_hlr(self): + http_client = Mock() + http_client.request.return_value = '{"id": "hlr-id","network": 20416,"reference": "reference2000","status": "active","createdDatetime": "2015-12-15T08:19:24+00:00","statusDatetime": "2015-12-15T08:19:25+00:00"}' + + lookup_hlr = Client('', http_client).lookup_hlr(31612345678, {'reference': 'reference2000'}) + + http_client.request.assert_called_once_with('lookup/31612345678/hlr', 'GET', {'reference': 'reference2000'}) + + self.assertEqual(lookup_hlr.status, 'active') + + def test_lookup_hlr_create(self): + http_client = Mock() + http_client.request.return_value = '{}' + + Client('', http_client).lookup_hlr_create(31612345678, {'reference': 'MyReference'}) + + http_client.request.assert_called_once_with('lookup/31612345678/hlr', 'POST', {'reference': 'MyReference'}) diff --git a/tests/test_message.py b/tests/test_message.py new file mode 100644 index 0000000..a81003e --- /dev/null +++ b/tests/test_message.py @@ -0,0 +1,30 @@ +import unittest +from messagebird import Client + +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 TestMessage(unittest.TestCase): + + def test_message(self): + http_client = Mock() + http_client.request.return_value = '{"body": "Hello World","createdDatetime": "2015-01-05T10:02:59+00:00","datacoding": "plain","direction": "mt","gateway": 239,"href": "https://rest.messagebird.com/messages/message-id","id": "message-id","mclass": 1,"originator": "TestName","recipients": {"items": [{"recipient": 31612345678,"status": "sent","statusDatetime": "2015-01-05T10:02:59+00:00"}],"totalCount": 1,"totalDeliveredCount": 0,"totalDeliveryFailedCount": 0,"totalSentCount": 1},"reference": null,"scheduledDatetime": null,"type": "sms","typeDetails": {},"validity": null}' + + message = Client('', http_client).message('message-id') + + http_client.request.assert_called_once_with('messages/message-id', 'GET', None) + + self.assertEqual('mt', message.direction) + + def test_message_create(self): + http_client = Mock() + http_client.request.return_value = '{}' + + Client('', http_client).message_create('MessageBird', ['31612345678', '31687654321'], 'Hello World', {'datacoding': 'unicode'}) + + http_client.request.assert_called_once_with('messages', 'POST', {'datacoding': 'unicode', 'originator': 'MessageBird', 'body': 'Hello World', 'recipients': '31612345678,31687654321' }) diff --git a/tests/test_verify.py b/tests/test_verify.py new file mode 100644 index 0000000..eee9760 --- /dev/null +++ b/tests/test_verify.py @@ -0,0 +1,40 @@ +import unittest +from messagebird import Client + +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 TestVerify(unittest.TestCase): + + def test_verify(self): + http_client = Mock() + http_client.request.return_value = '{"id": "verify-id","href": "https://rest.messagebird.com/verify/verify-id","recipient": 31612345678,"reference": "MyReference","messages": {"href": "https://rest.messagebird.com/messages/message-id"},"status": "verified","createdDatetime": "2017-05-30T12:39:50+00:00","validUntilDatetime": "2017-05-30T12:40:20+00:00"}' + + verify = Client('', http_client).verify('verify-id') + + http_client.request.assert_called_once_with('verify/verify-id', 'GET', None) + + self.assertEqual('verify-id', verify.id) + + def test_verify_create(self): + http_client = Mock() + http_client.request.return_value = '{}' + + Client('', http_client).verify_create('31612345678', {}) + + http_client.request.assert_called_once_with('verify', 'POST', {'recipient': '31612345678'}) + + def test_verify_verify(self): + http_client = Mock() + http_client.request.return_value = '{"id": "verify-id","href": "https://rest.messagebird.com/verify/verify-id","recipient": 31612345678,"reference": "MyReference","messages": {"href": "https://rest.messagebird.com/messages/63b168423592d681641eb07b76226648"},"status": "verified","createdDatetime": "2017-05-30T12:39:50+00:00","validUntilDatetime": "2017-05-30T12:40:20+00:00"}' + + verify = Client('', http_client).verify_verify('verify-id', 'secret') + + http_client.request.assert_called_once_with('verify/verify-id', 'GET', {'token': 'secret'}) + + self.assertEqual('verified', verify.status) diff --git a/tests/test_voicemessage.py b/tests/test_voicemessage.py new file mode 100644 index 0000000..c7f2a14 --- /dev/null +++ b/tests/test_voicemessage.py @@ -0,0 +1,30 @@ +import unittest +from messagebird import Client + +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 TestVoicemessage(unittest.TestCase): + + def test_voicemessage(self): + http_client = Mock() + http_client.request.return_value = '{"body": "Hello World","createdDatetime": "2015-01-05T16:11:24+00:00","href": "https://rest.messagebird.com/voicemessages/voicemessage-id","id": "voicemessage-id","ifMachine": "continue","language": "en-gb","originator": "MessageBird","recipients": {"items": [{"recipient": 31612345678,"status": "calling","statusDatetime": "2015-01-05T16:11:24+00:00"}],"totalCount": 1,"totalDeliveredCount": 0,"totalDeliveryFailedCount": 0,"totalSentCount": 1},"reference": null,"repeat": 1,"scheduledDatetime": null,"voice": "female"}' + + voice_message = Client('', http_client).voice_message('voicemessage-id') + + http_client.request.assert_called_once_with('voicemessages/voicemessage-id', 'GET', None) + + self.assertEqual('voicemessage-id', voice_message.id) + + def test_voicemessage_create(self): + http_client = Mock() + http_client.request.return_value = '{}' + + Client('', http_client).voice_message_create(['31612345678', '31687654321'], 'Hello World', { 'reference': 'MyReference' }) + + http_client.request.assert_called_once_with('voicemessages', 'POST', {'body': 'Hello World', 'recipients': '31612345678,31687654321', 'reference': 'MyReference'})