|
""" Generic API Client """ |
|
from copy import deepcopy |
|
import json |
|
import requests |
|
|
|
try: |
|
from urlparse import urljoin |
|
except ImportError: |
|
from urllib.parse import urljoin |
|
|
|
|
|
class ApiClient(object): |
|
""" Client to interact with a generic Rest API. |
|
|
|
Subclasses should implement functionality accordingly with the provided |
|
service methods, i.e. ``get``, ``post``, ``put`` and ``delete``. |
|
""" |
|
|
|
accept_type = 'application/xml' |
|
api_base = None |
|
|
|
def __init__( |
|
self, |
|
base_url, |
|
username=None, |
|
api_key=None, |
|
status_endpoint=None, |
|
timeout=60 |
|
): |
|
""" Initialise client. |
|
|
|
Args: |
|
base_url (str): The base URL to the service being used. |
|
username (str): The username to authenticate with. |
|
api_key (str): The API key to authenticate with. |
|
timeout (int): Maximum time before timing out. |
|
""" |
|
self.base_url = base_url |
|
self.username = username |
|
self.api_key = api_key |
|
self.status_endpoint = urljoin(self.base_url, status_endpoint) |
|
self.timeout = timeout |
|
|
|
@staticmethod |
|
def encode(request, data): |
|
""" Add request content data to request body, set Content-type header. |
|
|
|
Should be overridden by subclasses if not using JSON encoding. |
|
|
|
Args: |
|
request (HTTPRequest): The request object. |
|
data (dict, None): Data to be encoded. |
|
|
|
Returns: |
|
HTTPRequest: The request object. |
|
""" |
|
if data is None: |
|
return request |
|
|
|
request.add_header('Content-Type', 'application/json') |
|
request.extracted_data = json.dumps(data) |
|
|
|
return request |
|
|
|
@staticmethod |
|
def decode(response): |
|
""" Decode the returned data in the response. |
|
|
|
Should be overridden by subclasses if something else than JSON is |
|
expected. |
|
|
|
Args: |
|
response (HTTPResponse): The response object. |
|
|
|
Returns: |
|
dict or None. |
|
""" |
|
try: |
|
return response.json() |
|
except ValueError as e: |
|
return e.message |
|
|
|
def get_credentials(self): |
|
""" Returns parameters to be added to authenticate the request. |
|
|
|
This lives on its own to make it easier to re-implement it if needed. |
|
|
|
Returns: |
|
dict: A dictionary containing the credentials. |
|
""" |
|
return {"username": self.username, "api_key": self.api_key} |
|
|
|
def call_api( |
|
self, |
|
method, |
|
url, |
|
headers=None, |
|
params=None, |
|
data=None, |
|
files=None, |
|
timeout=None, |
|
): |
|
""" Call API. |
|
|
|
This returns object containing data, with error details if applicable. |
|
|
|
Args: |
|
method (str): The HTTP method to use. |
|
url (str): Resource location relative to the base URL. |
|
headers (dict or None): Extra request headers to set. |
|
params (dict or None): Query-string parameters. |
|
data (dict or None): Request body contents for POST or PUT requests. |
|
files (dict or None: Files to be passed to the request. |
|
timeout (int): Maximum time before timing out. |
|
|
|
Returns: |
|
ResultParser or ErrorParser. |
|
""" |
|
headers = deepcopy(headers) or {} |
|
headers['Accept'] = self.accept_type if 'Accept' not in headers else headers['Accept'] |
|
params = deepcopy(params) or {} |
|
data = data or {} |
|
files = files or {} |
|
|
|
|
|
r = requests.request( |
|
method, |
|
url, |
|
headers=headers, |
|
params=params, |
|
files=files, |
|
data=data, |
|
timeout=timeout, |
|
) |
|
|
|
return r, r.status_code |
|
|
|
def get(self, url, params=None, **kwargs): |
|
""" Call the API with a GET request. |
|
|
|
Args: |
|
url (str): Resource location relative to the base URL. |
|
params (dict or None): Query-string parameters. |
|
|
|
Returns: |
|
ResultParser or ErrorParser. |
|
""" |
|
return self.call_api( |
|
"GET", |
|
url, |
|
params=params, |
|
**kwargs |
|
) |
|
|
|
def delete(self, url, params=None, **kwargs): |
|
""" Call the API with a DELETE request. |
|
|
|
Args: |
|
url (str): Resource location relative to the base URL. |
|
params (dict or None): Query-string parameters. |
|
|
|
Returns: |
|
ResultParser or ErrorParser. |
|
""" |
|
return self.call_api( |
|
"DELETE", |
|
url, |
|
params=params, |
|
**kwargs |
|
) |
|
|
|
def put(self, url, params=None, data=None, files=None, **kwargs): |
|
""" Call the API with a PUT request. |
|
|
|
Args: |
|
url (str): Resource location relative to the base URL. |
|
params (dict or None): Query-string parameters. |
|
data (dict or None): Request body contents. |
|
files (dict or None: Files to be passed to the request. |
|
|
|
Returns: |
|
An instance of ResultParser or ErrorParser. |
|
""" |
|
return self.call_api( |
|
"PUT", |
|
url, |
|
params=params, |
|
data=data, |
|
files=files, |
|
**kwargs |
|
) |
|
|
|
def post(self, url, params=None, data=None, files=None, **kwargs): |
|
""" Call the API with a POST request. |
|
|
|
Args: |
|
url (str): Resource location relative to the base URL. |
|
params (dict or None): Query-string parameters. |
|
data (dict or None): Request body contents. |
|
files (dict or None: Files to be passed to the request. |
|
|
|
Returns: |
|
An instance of ResultParser or ErrorParser. |
|
""" |
|
return self.call_api( |
|
method="POST", |
|
url=url, |
|
params=params, |
|
data=data, |
|
files=files, |
|
**kwargs |
|
) |
|
|
|
def service_status(self, **kwargs): |
|
""" Call the API to get the status of the service. |
|
|
|
Returns: |
|
An instance of ResultParser or ErrorParser. |
|
""" |
|
return self.call_api( |
|
'GET', |
|
self.status_endpoint, |
|
params={'format': 'json'}, |
|
**kwargs |
|
) |
|
|