Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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'
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion messagebird/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from messagebird.client import Client
from messagebird.client import Client, ErrorException
45 changes: 45 additions & 0 deletions messagebird/base_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from messagebird.base import Base


class Links(Base):

def __init__(self):
self.first = None
self.previous = None
self.next = None
self.last = None


class BaseList(Base):

def __init__(self, item_type):
"""When setting items, they are instantiated as objects of type item_type."""
self.limit = None
self.offset = None
self.count = None
self.totalCount = None
self._links = None
self._items = None

self.itemType = item_type

@property
def links(self):
return self._links

@links.setter
def links(self, value):
self._links = Links().load(value)

@property
def items(self):
return self._items

@items.setter
def items(self, value):
"""Create typed objects from the dicts."""
items = []
for item in value:
items.append(self.itemType().load(item))

self._items = items
110 changes: 83 additions & 27 deletions messagebird/client.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
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.contact import Contact, ContactList
from messagebird.error import Error
from messagebird.group import ContactReference, Group, GroupList
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
Expand All @@ -19,6 +16,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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍



class ErrorException(Exception):
Expand All @@ -29,35 +27,41 @@ 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]

if http_client is None:
self.http_client = HttpClient(ENDPOINT, access_key, USER_AGENT)
else:
self.http_client = http_client

def request(self, path, method='GET', params=None):
if params is None: params = {}
url = urljoin(ENDPOINT, path)
"""Builds a request, gets a response and decodes it."""
response_text = self.http_client.request(path, method, params)
response_json = json.loads(response_text)

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 'errors' in response_json:
raise(ErrorException([Error().load(e) for e in response_json['errors']]))

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))
return response_json

if response.status_code in self._supported_status_codes:
json_response = response.json()
else:
response.raise_for_status()
def request_plain_text(self, path, method='GET', params=None):
"""Builds a request, gets a response and returns the body."""
response_text = self.http_client.request(path, method, params)

if 'errors' in json_response:
raise(ErrorException([Error().load(e) for e in json_response['errors']]))
try:
# Try to decode the response to JSON to see if the API returned any
# errors.
response_json = json.loads(response_text)

return json_response
if 'errors' in response_json:
raise (ErrorException([Error().load(e) for e in response_json['errors']]))
except ValueError:
# Do nothing: json.loads throws if the input string is not valid JSON,
# which is expected. We'll just return the response body below.
pass

return response_text

def balance(self):
"""Retrieve your balance."""
Expand Down Expand Up @@ -125,3 +129,55 @@ def verify_create(self, recipient, params=None):
def verify_verify(self, id, token):
"""Verify the token of a specific verification."""
return Verify().load(self.request('verify/' + str(id), params={'token': token}))

def contact(self, id):
"""Retrieve the information of a specific contact."""
return Contact().load(self.request('contacts/' + str(id)))

def contact_create(self, phonenumber, params=None):
if params is None: params = {}
params.update({'msisdn': phonenumber})
return Contact().load(self.request('contacts', 'POST', params))

def contact_delete(self, id):
self.request_plain_text('contacts/' + str(id), 'DELETE')

def contact_update(self, id, params=None):
self.request_plain_text('contacts/' + str(id), 'PATCH', params)

def contact_list(self, limit=0, offset=0):
query = 'limit='+str(limit)+'&offset='+str(offset)
return ContactList().load(self.request('contacts?'+query, 'GET', None))

def group(self, id):
return Group().load(self.request('groups/' + str(id), 'GET', None))

def group_create(self, name, params=None):
if params is None: params = {}
params.update({'name': name})
return Group().load(self.request('groups', 'POST', params))

def group_delete(self, id):
self.request_plain_text('groups/' + str(id), 'DELETE', None)

def group_list(self, limit=0, offset=0):
query = 'limit=' + str(limit) + '&offset=' + str(offset)
return GroupList().load(self.request('groups?'+query, 'GET', None))

def group_update(self, id, name, params=None):
if params is None: params = {}
params.update({'name': name})
self.request_plain_text('groups/' + str(id), 'PATCH', params)

def group_add_contacts(self, groupId, contactIds):
query = self.__group_add_contacts_query(contactIds)
self.request_plain_text('groups/' + str(groupId) + '?' + query, 'PUT', None)

def __group_add_contacts_query(self, contactIds):
# __group_add_contacts_query gets a query string to add contacts to a
# group. The expected format is ids[]=first-contact&ids[]=second-contact.
# See: https://developers.messagebird.com/docs/groups#add-contact-to-group.
return '&'.join('ids[]=' + str(id) for id in contactIds)

def group_remove_contact(self, groupId, contactId):
self.request_plain_text('groups/' + str(groupId) + '/contacts/' + str(contactId), 'DELETE', None)
87 changes: 87 additions & 0 deletions messagebird/contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from messagebird.base import Base
from messagebird.base_list import BaseList


class CustomDetails(Base):

def __init__(self):
self.custom1 = None
self.custom2 = None
self.custom3 = None
self.custom4 = None


class GroupReference(Base):

def __init__(self):
self.href = None
self.totalCount = None


class MessageReference(Base):

def __init__(self):
self.href = None
self.totalCount = None


class ContactList(BaseList):

def __init__(self):
# Signal the BaseList that we're expecting items of type Contact...
super(ContactList, self).__init__(Contact)


class Contact(Base):

def __init__(self):
self.id = None
self.href = None
self.msisdn = None
self.firstName = None
self.lastName = None
self._customDetails = None
self._groups = None
self._messages = None
self._createdDatetime = None
self._updatedDatetime = None

@property
def customDetails(self):
return self._customDetails

@customDetails.setter
def customDetails(self, value):
self._customDetails = CustomDetails().load(value)

@property
def groups(self):
return self._groups

@groups.setter
def groups(self, value):
self._groups = GroupReference().load(value)

@property
def messages(self):
return self._messages

@messages.setter
def messages(self, value):
self._messages = MessageReference().load(value)

@property
def createdDatetime(self):
return self._createdDatetime

@createdDatetime.setter
def createdDatetime(self, value):
self._createdDatetime = self.value_to_time(value)

@property
def updatedDatetime(self):
return self._updatedDatetime

@updatedDatetime.setter
def updatedDatetime(self, value):
self._updatedDatetime = self.value_to_time(value)
51 changes: 51 additions & 0 deletions messagebird/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from messagebird.base import Base
from messagebird.base_list import BaseList


class ContactReference(Base):

def __init__(self):
self.href = None
self.totalCount = None


class GroupList(BaseList):

def __init__(self):
# Signal the BaseList that we're expecting items of type Group...
super(GroupList, self).__init__(Group)


class Group(Base):

def __init__(self):
self.id = None
self.href = None
self.name = None
self._contacts = None
self.createdDatetime = None
self.updatedDatetime = None

@property
def contacts(self):
return self._contacts

@contacts.setter
def contacts(self, value):
self._contacts = ContactReference().load(value)

@property
def createdDatetime(self):
return self._createdDatetime

@createdDatetime.setter
def createdDatetime(self, value):
self._createdDatetime = self.value_to_time(value)

@property
def updatedDatetime(self):
return self._updatedDatetime

@updatedDatetime.setter
def updatedDatetime(self, value):
self._updatedDatetime = self.value_to_time(value)
Loading