blob: 34a06f073af5221625c2c689745811b73cad3029 [file] [log] [blame]
#!/usr/bin/env python
# coding=utf-8
# Copyright [2020] [Apache Software Foundation]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Http Client Module.
"""
import requests
import math
from .utils import to_json
# Use six to create code compatible with Python 2 and 3.
# See http://pythonhosted.org/six/
from .log import get_logger
from .exceptions import HTTPException
__all__ = ['HttpClient', 'ListResultSet', 'HttpResponse', 'ApiClient']
logger = get_logger('http_client')
class HttpClient(object):
"""
Http REST client
used as superclass for the specific API classes
override the `host` property method to return the api server host
usage:
class MyApiClient(object):
@property
def host(self):
return "http://myapiurl:8000"
"""
def url(self, path):
"""
Build url for the specified path.
:param path: [string] the url path for the api endpoint
:return: [string] formated url
"""
if 'http://' in path or 'https://' in path:
return path
else:
return self.host + path
def parse_response(self, response):
"""
Parse the response and build a `scanboo_common.http_client.HttpResponse` object.
For successful responses, convert the json data into a dict.
:param response: the `requests` response
:return: [HttpResponse] response object
"""
status = response.status_code
if response.ok:
data = response.json()
return HttpResponse(ok=response.ok, status=status, errors=None, data=data)
else:
try:
errors = response.json()
except ValueError:
errors = response.content
return HttpResponse(ok=response.ok, status=status, errors=errors, data=None)
def request_header(self):
"""
Build a headers dict with:
- the content type as json
:return: [dict] headers dict
"""
return {
'Content-Type': 'application/json',
'Csrf-Token': 'nocheck',
}
def get_all(self, path, data=None, limit=100):
"""Encapsulates GET all requests"""
return ListResultSet(path=path, data=data or {}, limit=limit)
def get(self, path, data=None):
"""Encapsulates GET requests"""
data = data or {}
response = requests.get(
self.url(path), params=data, headers=self.request_header())
return self.parse_response(response)
def post(self, path, data=None):
"""Encapsulates POST requests"""
data = data or {}
response = requests.post(self.url(path), data=to_json(
data), headers=self.request_header())
return self.parse_response(response)
def put(self, path, data=None):
"""Encapsulates PUT requests"""
data = data or {}
response = requests.put(self.url(path), data=to_json(
data), headers=self.request_header())
return self.parse_response(response)
def delete(self, path, data=None):
"""Encapsulates DELETE requests"""
data = data or {}
response = requests.delete(self.url(path), data=to_json(
data), headers=self.request_header())
return self.parse_response(response)
class ListResultSet(object):
"""
Used to encapsulate the result of requests of lists.
"""
def __init__(self, path, data=None, limit=50, page=1):
self.path = path
self.params = data or {}
self.limit = limit
self.page = page
self.response = None
self._objects = []
self._process()
def __len__(self):
return self.response.data.get('total', 0)
def __iter__(self):
more_results = True
while more_results:
for item in self._objects:
yield item
next_page = self._next_page()
if next_page:
self.page = next_page
self._process()
else:
more_results = False
def _next_page(self):
new_page = None
if math.ceil(self.response.data['total'] / float(self.limit)) >= (self.page + 1):
new_page = self.page + 1
return new_page
def _process(self):
url = api_client.url(self.path)
self.params.update({'page': self.page, 'per_page': self.limit})
response = requests.get(url, params=self.params,
headers=api_client.request_header())
response = api_client.parse_response(response)
try:
self._objects = response.data['objects']
except TypeError:
raise HTTPException(response.errors)
self.response = response
class ApiClient(HttpClient):
"""
Data Api client abstracttion.
See `scanboo_common.http_client.HttpClient` for more info.
"""
@property
def host(self):
# url = Configuration.get('api.url')
url = 'http://localhost:8000'
return url[:-1] if url.endswith('/') else url
class HttpResponse(object):
"""
Http response utility class.
:attr ok: [bool] request returned ok
:attr status: [int] http status code
:attr data: [dict] dictionary containing the returned data
:attr errors: [dict | str] dict when errors is json parsable or the raw error string
"""
def __init__(self, ok, status, errors, data):
self.ok = ok
self.status = status
self.errors = errors
self.data = data
# ApiClient "singleton"
api_client = ApiClient()