├── kickbox ├── __init__.py ├── error │ ├── __init__.py │ └── client_error.py ├── api │ ├── __init__.py │ └── kickbox.py ├── http_client │ ├── response.py │ ├── response_handler.py │ ├── auth_handler.py │ ├── error_handler.py │ ├── request_handler.py │ └── __init__.py └── client.py ├── .gitignore ├── setup.py └── README.md /kickbox/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | -------------------------------------------------------------------------------- /kickbox/error/__init__.py: -------------------------------------------------------------------------------- 1 | from .client_error import ClientError 2 | -------------------------------------------------------------------------------- /kickbox/api/__init__.py: -------------------------------------------------------------------------------- 1 | # Import all the classes into api module 2 | from . import kickbox 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *.so 3 | *.egg 4 | *.egg-info 5 | dist 6 | build 7 | eggs 8 | parts 9 | bin 10 | var 11 | sdist 12 | develop-eggs 13 | .installed.cfg 14 | lib 15 | lib64 16 | __pycache__ 17 | pip-log.txt 18 | .coverage 19 | .tox 20 | nosetests.xml 21 | -------------------------------------------------------------------------------- /kickbox/http_client/response.py: -------------------------------------------------------------------------------- 1 | class Response(object): 2 | 3 | """Response object contains the response returned by the client""" 4 | 5 | def __init__(self, body, code, headers): 6 | self.body = body 7 | self.code = code 8 | self.headers = headers 9 | -------------------------------------------------------------------------------- /kickbox/error/client_error.py: -------------------------------------------------------------------------------- 1 | class ClientError(Exception): 2 | 3 | """ClientError is used when the API returns an error""" 4 | 5 | def __init__(self, message, code): 6 | super(ClientError, self).__init__() 7 | self.message = message 8 | self.code = code 9 | -------------------------------------------------------------------------------- /kickbox/client.py: -------------------------------------------------------------------------------- 1 | from .http_client import HttpClient 2 | 3 | # Assign all the api classes 4 | from .api.kickbox import Kickbox 5 | 6 | 7 | class Client(object): 8 | 9 | def __init__(self, auth={}, options={}): 10 | self.http_client = HttpClient(auth, options) 11 | 12 | def kickbox(self): 13 | """ 14 | """ 15 | return Kickbox(self.http_client) 16 | 17 | -------------------------------------------------------------------------------- /kickbox/http_client/response_handler.py: -------------------------------------------------------------------------------- 1 | class ResponseHandler(object): 2 | 3 | """ResponseHandler takes care of decoding the response body into suitable type""" 4 | 5 | @staticmethod 6 | def get_body(response): 7 | typ = response.headers.get('content-type') 8 | body = response.text 9 | 10 | # Response body is in JSON 11 | if typ.find('json') != -1: 12 | body = response.json() 13 | 14 | return body 15 | -------------------------------------------------------------------------------- /kickbox/api/kickbox.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | class Kickbox(object): 4 | 5 | """ 6 | """ 7 | 8 | def __init__(self, client): 9 | self.client = client 10 | 11 | def verify(self, email, options={}): 12 | """Email Verification 13 | 14 | '/verify?email=:email&timeout=:timeout' GET 15 | 16 | Args: 17 | email: Email address to verify 18 | """ 19 | body = options['query'] if 'query' in options else {} 20 | 21 | email = six.moves.urllib.parse.quote(email) 22 | timeout = options['timeout'] if 'timeout' in options else 6000 23 | 24 | response = self.client.get('/verify?email=' + email + '&timeout=' + str(timeout), body, options) 25 | 26 | return response 27 | 28 | -------------------------------------------------------------------------------- /kickbox/http_client/auth_handler.py: -------------------------------------------------------------------------------- 1 | class AuthHandler(object): 2 | 3 | """AuthHandler takes care of devising the auth type and using it""" 4 | 5 | HTTP_HEADER = 1 6 | 7 | def __init__(self, auth): 8 | self.auth = auth 9 | 10 | def get_auth_type(self): 11 | """Calculating the Authentication Type""" 12 | 13 | if 'http_header' in self.auth: 14 | return self.HTTP_HEADER 15 | 16 | return -1 17 | 18 | def set(self, request): 19 | if len(self.auth.keys()) == 0: 20 | raise StandardError("Server requires authentication to proceed further. Please check") 21 | 22 | auth = self.get_auth_type() 23 | flag = False 24 | 25 | if auth == self.HTTP_HEADER: 26 | request = self.http_header(request) 27 | flag = True 28 | 29 | if not flag: 30 | raise StandardError("Unable to calculate authorization method. Please check") 31 | 32 | return request 33 | 34 | def http_header(self, request): 35 | """Authorization with HTTP header""" 36 | request['headers']['Authorization'] = 'token ' + self.auth['http_header'] 37 | return request 38 | 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | setup( 10 | name='kickbox', 11 | version='2.0.4', 12 | description='Official kickbox API library client for python', 13 | author='Chaitanya Surapaneni', 14 | author_email='chaitanya.surapaneni@kickbox.com', 15 | url='https://kickbox.com', 16 | license='MIT', 17 | install_requires=[ 18 | 'requests >= 2.1.0', 19 | 'six >= 1.9.0' 20 | ], 21 | packages=[ 22 | 'kickbox', 23 | 'kickbox.api', 24 | 'kickbox.error', 25 | 'kickbox.http_client' 26 | ], 27 | classifiers=[ 28 | 'Development Status :: 5 - Production/Stable', 29 | 'Intended Audience :: Developers', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Operating System :: OS Independent', 32 | 'Programming Language :: Python :: 2.6', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3.2', 35 | 'Programming Language :: Python :: 3.3', 36 | 'Programming Language :: Python :: 3.4', 37 | 'Topic :: Software Development :: Libraries :: Python Modules', 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /kickbox/http_client/error_handler.py: -------------------------------------------------------------------------------- 1 | from ..error import ClientError 2 | from .response_handler import ResponseHandler 3 | 4 | 5 | class ErrorHandler(object): 6 | 7 | """ErrorHandler takes care of getting the error message from response body""" 8 | 9 | @staticmethod 10 | def check_error(response, *args, **kwargs): 11 | code = response.status_code 12 | typ = response.headers.get('content-type') 13 | 14 | if code in range(500, 600): 15 | raise ClientError('Error ' + str(code), code) 16 | elif code in range(400, 500): 17 | body = ResponseHandler.get_body(response) 18 | message = '' 19 | 20 | # If HTML, whole body is taken 21 | if isinstance(body, str): 22 | message = body 23 | 24 | # If JSON, a particular field is taken and used 25 | if typ.find('json') != -1 and isinstance(body, dict): 26 | if 'message' in body: 27 | message = body['message'] 28 | else: 29 | message = 'Unable to select error message from json returned by request responsible for error' 30 | 31 | if message == '': 32 | message = 'Unable to understand the content type of response returned by request responsible for error' 33 | 34 | raise ClientError(message, code) 35 | -------------------------------------------------------------------------------- /kickbox/http_client/request_handler.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import json 3 | 4 | 5 | class RequestHandler(object): 6 | 7 | """RequestHandler takes care of encoding the request body into format given by options""" 8 | 9 | @staticmethod 10 | def render_key(parents): 11 | depth, new = 0, '' 12 | 13 | for x in parents: 14 | old = '[%s]' if depth > 0 else '%s' 15 | new += old % x 16 | depth += 1 17 | 18 | return new 19 | 20 | @staticmethod 21 | def urlencode(data, parents=None, pairs=None): 22 | if pairs is None: 23 | pairs = {} 24 | 25 | if parents is None: 26 | parents = [] 27 | 28 | if isinstance(data, dict): 29 | for key, value in data.items(): 30 | RequestHandler.urlencode(value, parents + [key], pairs) 31 | elif isinstance(data, list): 32 | for key, value in enumerate(data): 33 | RequestHandler.urlencode(value, parents + [key], pairs) 34 | else: 35 | pairs[RequestHandler.render_key(parents)] = data 36 | 37 | return pairs 38 | 39 | @staticmethod 40 | def set_body(request): 41 | typ = request['request_type'] if 'request_type' in request else 'raw' 42 | 43 | if typ == 'raw': 44 | if 'content-type' in request['headers']: 45 | del request['headers']['content-type'] 46 | 47 | if 'request_type' in request: 48 | del request['request_type'] 49 | 50 | return request 51 | -------------------------------------------------------------------------------- /kickbox/http_client/__init__.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import copy 3 | 4 | try: 5 | import urlparse 6 | except ImportError: 7 | import urllib.parse as urlparse 8 | 9 | from .auth_handler import AuthHandler 10 | from .error_handler import ErrorHandler 11 | from .request_handler import RequestHandler 12 | from .response import Response 13 | from .response_handler import ResponseHandler 14 | 15 | 16 | class HttpClient(object): 17 | 18 | """Main HttpClient which is used by API classes""" 19 | 20 | def __init__(self, auth, options): 21 | 22 | if isinstance(auth, str): 23 | auth = {'http_header': auth} 24 | 25 | self.options = { 26 | 'base': 'https://api.kickbox.com', 27 | 'api_version': 'v2', 28 | 'user_agent': 'kickbox-python/2.0.0 (https://github.com/kickboxio/kickbox-python)' 29 | } 30 | 31 | self.options.update(options) 32 | 33 | self.base = self.options['base'] 34 | 35 | self.headers = { 36 | 'user-agent': self.options['user_agent'] 37 | } 38 | 39 | if 'headers' in self.options: 40 | self.headers.update(self.dict_key_lower(self.options['headers'])) 41 | del self.options['headers'] 42 | 43 | self.auth = AuthHandler(auth) 44 | 45 | def get(self, path, params={}, options={}): 46 | options.update({'query': params}) 47 | return self.request(path, None, 'get', options) 48 | 49 | def post(self, path, body={}, options={}): 50 | return self.request(path, body, 'post', options) 51 | 52 | def patch(self, path, body={}, options={}): 53 | return self.request(path, body, 'patch', options) 54 | 55 | def delete(self, path, body={}, options={}): 56 | return self.request(path, body, 'delete', options) 57 | 58 | def put(self, path, body={}, options={}): 59 | return self.request(path, body, 'put', options) 60 | 61 | def request(self, path, body, method, options): 62 | """Intermediate function which does three main things 63 | 64 | - Transforms the body of request into correct format 65 | - Creates the requests with given parameters 66 | - Returns response body after parsing it into correct format 67 | """ 68 | kwargs = copy.deepcopy(self.options) 69 | kwargs.update(options) 70 | 71 | kwargs['headers'] = copy.deepcopy(self.headers) 72 | 73 | if 'headers' in options: 74 | kwargs['headers'].update(self.dict_key_lower(options['headers'])) 75 | 76 | kwargs['data'] = body 77 | kwargs['allow_redirects'] = True 78 | 79 | kwargs['params'] = kwargs['query'] if 'query' in kwargs else {} 80 | 81 | if 'query' in kwargs: 82 | del kwargs['query'] 83 | 84 | if 'body' in kwargs: 85 | del kwargs['body'] 86 | 87 | del kwargs['base'] 88 | del kwargs['user_agent'] 89 | 90 | if method != 'get': 91 | kwargs = self.set_body(kwargs) 92 | 93 | kwargs['hooks'] = dict(response=ErrorHandler.check_error) 94 | 95 | kwargs = self.auth.set(kwargs) 96 | 97 | response = self.create_request(method, path, kwargs) 98 | 99 | return Response( 100 | self.get_body(response), response.status_code, response.headers 101 | ) 102 | 103 | def create_request(self, method, path, options): 104 | """Creating a request with the given arguments 105 | 106 | If api_version is set, appends it immediately after host 107 | """ 108 | version = '/' + options['api_version'] if 'api_version' in options else '' 109 | 110 | path = urlparse.urljoin(self.base, version + path) 111 | 112 | if 'api_version' in options: 113 | del options['api_version'] 114 | 115 | if 'response_type' in options: 116 | del options['response_type'] 117 | 118 | return requests.request(method, path, **options) 119 | 120 | def get_body(self, response): 121 | """Get response body in correct format""" 122 | return ResponseHandler.get_body(response) 123 | 124 | def set_body(self, request): 125 | """Set request body in correct format""" 126 | return RequestHandler.set_body(request) 127 | 128 | def dict_key_lower(self, dic): 129 | """Make dict keys all lower case""" 130 | return dict(zip(map(self.key_lower, dic.keys()), dic.values())) 131 | 132 | def key_lower(self, key): 133 | """Make a function for lower case""" 134 | return key.lower() 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |