diff --git a/.gitignore b/.gitignore index 4c65bf3..a786a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ pip-log.txt .coverage .tox nosetests.xml +test.py diff --git a/kickbox/__init__.py b/kickbox/__init__.py index 3ff722b..8431fa6 100644 --- a/kickbox/__init__.py +++ b/kickbox/__init__.py @@ -1 +1 @@ -from .client import Client +from .api import Verification, Authentication diff --git a/kickbox/api.py b/kickbox/api.py new file mode 100644 index 0000000..9273031 --- /dev/null +++ b/kickbox/api.py @@ -0,0 +1,92 @@ +import requests +from .errors import ErrorHandler +from .response_handler import ResponseHandler + + +class Client(object): + + # Define base url + BASE_URL = "https://api.kickbox.io" + + def make_request(self, url, method='get', params={}): + headers = { + 'user-agent': + 'kickbox-python/3.0.0 (https://github.com/kickboxio/kickbox-python)' + } + + url = "%s%s" % (self.BASE_URL, url) + + if method is 'get': + response = requests.get( + url, headers=headers, params=params, hooks=dict( + response=ErrorHandler.check_error + ) + ) + else: + response = requests.post(url, headers=headers, params=params) + + return ResponseHandler.get_body(response) + + +class Verification(Client): + + """Verification.""" + + def __init__(self, api_key, version='v2'): + """ad. + + Args: + api_key: API key of the authentication app + """ + self.api_key = api_key + self.version = version + + def verify(self, email, timeout=6000): + """Verify an Email address. + + Args: + email: email to be verified + """ + url = "/%s/verify" % (self.version) + + response = self.make_request( + url=url, method="get", params={ + 'email': email, 'api_key': self.api_key, 'timeout': timeout + } + ) + return response + + +class Authentication(Client): + + def __init__(self, app_code, api_key): + """ + Args: + app_code: The code for the authentication app + api_key: Your API key of the authentication app + """ + self.app_code = app_code + self.api_key = api_key + super(Authentication, self).__init__() + + def authenticate(self, fingerprint): + """Send the authentication email. + + Args: + fingerprint: The fingerprint for the email address + """ + url = "/v2/authenticate/%s" % (self.app_code) + params = {'api_key': self.api_key, 'fingerprint': fingerprint} + response = self.make_request(url=url, method='post', params=params) + return response + + def getStatus(self, id): + """Get the status of an authentication. + + Args: + id: Authentication id returned from the authenticate request + """ + url = "/v2/authenticate/%s/%s" % (self.app_code, id) + params = {'api_key': self.api_key} + response = self.make_request(url=url, method='get', params=params) + return response diff --git a/kickbox/api/__init__.py b/kickbox/api/__init__.py deleted file mode 100644 index c35a358..0000000 --- a/kickbox/api/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Import all the classes into api module -from . import kickbox diff --git a/kickbox/api/kickbox.py b/kickbox/api/kickbox.py deleted file mode 100644 index 0885d57..0000000 --- a/kickbox/api/kickbox.py +++ /dev/null @@ -1,27 +0,0 @@ -import six - -class Kickbox(object): - - """ - """ - - def __init__(self, client): - self.client = client - - def verify(self, email, options={}): - """Email Verification - - '/verify?email=:email&timeout=:timeout' GET - - Args: - email: Email address to verify - """ - body = options['query'] if 'query' in options else {} - - email = six.moves.urllib.parse.quote(email) - timeout = options['timeout'] if 'timeout' in options else 6000 - - response = self.client.get('/verify?email=' + email + '&timeout=' + str(timeout), body, options) - - return response - diff --git a/kickbox/client.py b/kickbox/client.py deleted file mode 100644 index 8fc8a25..0000000 --- a/kickbox/client.py +++ /dev/null @@ -1,16 +0,0 @@ -from .http_client import HttpClient - -# Assign all the api classes -from .api.kickbox import Kickbox - - -class Client(object): - - def __init__(self, auth={}, options={}): - self.http_client = HttpClient(auth, options) - - def kickbox(self): - """ - """ - return Kickbox(self.http_client) - diff --git a/kickbox/error/__init__.py b/kickbox/error/__init__.py deleted file mode 100644 index 6055548..0000000 --- a/kickbox/error/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .client_error import ClientError diff --git a/kickbox/error/client_error.py b/kickbox/error/client_error.py deleted file mode 100644 index 9d46964..0000000 --- a/kickbox/error/client_error.py +++ /dev/null @@ -1,8 +0,0 @@ -class ClientError(Exception): - - """ClientError is used when the API returns an error""" - - def __init__(self, message, code): - super(ClientError, self).__init__() - self.message = message - self.code = code diff --git a/kickbox/errors.py b/kickbox/errors.py new file mode 100644 index 0000000..1ea2921 --- /dev/null +++ b/kickbox/errors.py @@ -0,0 +1,30 @@ +from .response_handler import ResponseHandler + + +class Error(Exception): + def __init__(self, message, code): + super(Error, self).__init__() + self.message = message + self.code = code + + +class ErrorHandler(object): + @staticmethod + def check_error(response, *args, **kwargs): + code = response.status_code + content_type = response.headers.get('content-type') + + if code in range(500, 600): + raise Error('Error ' + str(code), code) + + # handle rate limit + elif code is 429: + raise Error('Rate limit exceeded.') + elif code in range(400, 500): + body = ResponseHandler.get_body(response) + message = body + + if content_type.find('json') != -1: + message = body['message'] + + raise Error(message, code) diff --git a/kickbox/http_client/__init__.py b/kickbox/http_client/__init__.py deleted file mode 100644 index c96b550..0000000 --- a/kickbox/http_client/__init__.py +++ /dev/null @@ -1,134 +0,0 @@ -import requests -import copy - -try: - import urlparse -except ImportError: - import urllib.parse as urlparse - -from .auth_handler import AuthHandler -from .error_handler import ErrorHandler -from .request_handler import RequestHandler -from .response import Response -from .response_handler import ResponseHandler - - -class HttpClient(object): - - """Main HttpClient which is used by API classes""" - - def __init__(self, auth, options): - - if isinstance(auth, str): - auth = {'http_header': auth} - - self.options = { - 'base': 'https://api.kickbox.io', - 'api_version': 'v2', - 'user_agent': 'kickbox-python/2.0.0 (https://github.com/kickboxio/kickbox-python)' - } - - self.options.update(options) - - self.base = self.options['base'] - - self.headers = { - 'user-agent': self.options['user_agent'] - } - - if 'headers' in self.options: - self.headers.update(self.dict_key_lower(self.options['headers'])) - del self.options['headers'] - - self.auth = AuthHandler(auth) - - def get(self, path, params={}, options={}): - options.update({'query': params}) - return self.request(path, None, 'get', options) - - def post(self, path, body={}, options={}): - return self.request(path, body, 'post', options) - - def patch(self, path, body={}, options={}): - return self.request(path, body, 'patch', options) - - def delete(self, path, body={}, options={}): - return self.request(path, body, 'delete', options) - - def put(self, path, body={}, options={}): - return self.request(path, body, 'put', options) - - def request(self, path, body, method, options): - """Intermediate function which does three main things - - - Transforms the body of request into correct format - - Creates the requests with given parameters - - Returns response body after parsing it into correct format - """ - kwargs = copy.deepcopy(self.options) - kwargs.update(options) - - kwargs['headers'] = copy.deepcopy(self.headers) - - if 'headers' in options: - kwargs['headers'].update(self.dict_key_lower(options['headers'])) - - kwargs['data'] = body - kwargs['allow_redirects'] = True - - kwargs['params'] = kwargs['query'] if 'query' in kwargs else {} - - if 'query' in kwargs: - del kwargs['query'] - - if 'body' in kwargs: - del kwargs['body'] - - del kwargs['base'] - del kwargs['user_agent'] - - if method != 'get': - kwargs = self.set_body(kwargs) - - kwargs['hooks'] = dict(response=ErrorHandler.check_error) - - kwargs = self.auth.set(kwargs) - - response = self.create_request(method, path, kwargs) - - return Response( - self.get_body(response), response.status_code, response.headers - ) - - def create_request(self, method, path, options): - """Creating a request with the given arguments - - If api_version is set, appends it immediately after host - """ - version = '/' + options['api_version'] if 'api_version' in options else '' - - path = urlparse.urljoin(self.base, version + path) - - if 'api_version' in options: - del options['api_version'] - - if 'response_type' in options: - del options['response_type'] - - return requests.request(method, path, **options) - - def get_body(self, response): - """Get response body in correct format""" - return ResponseHandler.get_body(response) - - def set_body(self, request): - """Set request body in correct format""" - return RequestHandler.set_body(request) - - def dict_key_lower(self, dic): - """Make dict keys all lower case""" - return dict(zip(map(self.key_lower, dic.keys()), dic.values())) - - def key_lower(self, key): - """Make a function for lower case""" - return key.lower() diff --git a/kickbox/http_client/auth_handler.py b/kickbox/http_client/auth_handler.py deleted file mode 100644 index 3606295..0000000 --- a/kickbox/http_client/auth_handler.py +++ /dev/null @@ -1,38 +0,0 @@ -class AuthHandler(object): - - """AuthHandler takes care of devising the auth type and using it""" - - HTTP_HEADER = 1 - - def __init__(self, auth): - self.auth = auth - - def get_auth_type(self): - """Calculating the Authentication Type""" - - if 'http_header' in self.auth: - return self.HTTP_HEADER - - return -1 - - def set(self, request): - if len(self.auth.keys()) == 0: - raise StandardError("Server requires authentication to proceed further. Please check") - - auth = self.get_auth_type() - flag = False - - if auth == self.HTTP_HEADER: - request = self.http_header(request) - flag = True - - if not flag: - raise StandardError("Unable to calculate authorization method. Please check") - - return request - - def http_header(self, request): - """Authorization with HTTP header""" - request['headers']['Authorization'] = 'token ' + self.auth['http_header'] - return request - diff --git a/kickbox/http_client/error_handler.py b/kickbox/http_client/error_handler.py deleted file mode 100644 index 8ea412e..0000000 --- a/kickbox/http_client/error_handler.py +++ /dev/null @@ -1,34 +0,0 @@ -from ..error import ClientError -from .response_handler import ResponseHandler - - -class ErrorHandler(object): - - """ErrorHandler takes care of getting the error message from response body""" - - @staticmethod - def check_error(response, *args, **kwargs): - code = response.status_code - typ = response.headers.get('content-type') - - if code in range(500, 600): - raise ClientError('Error ' + str(code), code) - elif code in range(400, 500): - body = ResponseHandler.get_body(response) - message = '' - - # If HTML, whole body is taken - if isinstance(body, str): - message = body - - # If JSON, a particular field is taken and used - if typ.find('json') != -1 and isinstance(body, dict): - if 'message' in body: - message = body['message'] - else: - message = 'Unable to select error message from json returned by request responsible for error' - - if message == '': - message = 'Unable to understand the content type of response returned by request responsible for error' - - raise ClientError(message, code) diff --git a/kickbox/http_client/request_handler.py b/kickbox/http_client/request_handler.py deleted file mode 100644 index b563155..0000000 --- a/kickbox/http_client/request_handler.py +++ /dev/null @@ -1,50 +0,0 @@ -import urllib -import json - - -class RequestHandler(object): - - """RequestHandler takes care of encoding the request body into format given by options""" - - @staticmethod - def render_key(parents): - depth, new = 0, '' - - for x in parents: - old = '[%s]' if depth > 0 else '%s' - new += old % x - depth += 1 - - return new - - @staticmethod - def urlencode(data, parents=None, pairs=None): - if pairs is None: - pairs = {} - - if parents is None: - parents = [] - - if isinstance(data, dict): - for key, value in data.items(): - RequestHandler.urlencode(value, parents + [key], pairs) - elif isinstance(data, list): - for key, value in enumerate(data): - RequestHandler.urlencode(value, parents + [key], pairs) - else: - pairs[RequestHandler.render_key(parents)] = data - - return pairs - - @staticmethod - def set_body(request): - typ = request['request_type'] if 'request_type' in request else 'raw' - - if typ == 'raw': - if 'content-type' in request['headers']: - del request['headers']['content-type'] - - if 'request_type' in request: - del request['request_type'] - - return request diff --git a/kickbox/http_client/response.py b/kickbox/http_client/response.py deleted file mode 100644 index 3a5aecf..0000000 --- a/kickbox/http_client/response.py +++ /dev/null @@ -1,8 +0,0 @@ -class Response(object): - - """Response object contains the response returned by the client""" - - def __init__(self, body, code, headers): - self.body = body - self.code = code - self.headers = headers diff --git a/kickbox/http_client/response_handler.py b/kickbox/response_handler.py similarity index 61% rename from kickbox/http_client/response_handler.py rename to kickbox/response_handler.py index ad0df60..3334537 100644 --- a/kickbox/http_client/response_handler.py +++ b/kickbox/response_handler.py @@ -1,14 +1,13 @@ class ResponseHandler(object): - """ResponseHandler takes care of decoding the response body into suitable type""" + """ResponseHandler takes care of decoding the response body into suitable type.""" @staticmethod def get_body(response): - typ = response.headers.get('content-type') body = response.text + content_type = response.headers.get('content-type') - # Response body is in JSON - if typ.find('json') != -1: + if content_type.find('json') != -1: body = response.json() return body diff --git a/setup.py b/setup.py index b757f80..5ea7cce 100644 --- a/setup.py +++ b/setup.py @@ -8,21 +8,18 @@ setup( name='kickbox', - version='2.0.3', + version='3.0.0', description='Official kickbox API library client for python', author='Chaitanya Surapaneni', author_email='chaitanya.surapaneni@kickbox.io', url='http://kickbox.io', license='MIT', install_requires=[ - 'requests >= 2.1.0', + 'requests >= 2.10.0', 'six >= 1.9.0' ], packages=[ - 'kickbox', - 'kickbox.api', - 'kickbox.error', - 'kickbox.http_client' + 'kickbox' ], classifiers=[ 'Development Status :: 5 - Production/Stable',