Commit 6e4bac48 by Gabe Mulley

Initialize django project

This first pass includes:

* A django app for serving the API
* A django server to host the app
* A generic client that can be used to access the API from server side python code

Change-Id: Idd7e1a8e370e6fe216ec7413c26de66b8d51ff38
parents
import logging
import requests
import requests.exceptions
log = logging.getLogger(__name__)
class Client(object):
"""A client capable of retrieving the requested resources."""
DEFAULT_TIMEOUT = 0.1 # In seconds
DEFAULT_VERSION = 'v0'
def __init__(self, version=DEFAULT_VERSION):
"""
Initialize the Client.
Arguments:
version (str): When breaking changes are made to either the resource addresses or their returned data, this
value will change. Multiple clients can be made which adhere to each version.
"""
self.version = version
def get(self, resource, timeout=None):
"""
Retrieve the data for a resource.
Arguments:
resource (str): Path in the form of slash separated strings.
timeout (float): Continue to attempt to retrieve a resource for this many seconds before giving up and
raising an error.
Returns: A structure consisting of simple python types (dict, list, int, str etc).
Raises: ClientError if the resource cannot be retrieved for any reason.
"""
raise NotImplementedError
def has_resource(self, resource, timeout=None):
"""
Check if a resource exists.
Arguments:
resource (str): Path in the form of slash separated strings.
timeout (float): Continue to attempt to retrieve a resource for this many seconds before giving up and
raising an error.
Returns: True iff the resource exists.
"""
raise NotImplementedError
class RestClient(Client):
"""Retrieve resources from a remote REST API."""
DEFAULT_AUTH_TOKEN = ''
DEFAULT_BASE_URL = 'http://localhost:9090'
def __init__(self, base_url=DEFAULT_BASE_URL, auth_token=DEFAULT_AUTH_TOKEN):
"""
Initialize the RestClient.
Arguments:
base_url (str): A URL containing the scheme, netloc and port of the remote service.
auth_token (str): The token that should be used to authenticate requests made to the remote service.
"""
super(RestClient, self).__init__()
self.base_url = '{0}/api/{1}'.format(base_url, self.version)
self.auth_token = auth_token
def get(self, resource, timeout=None):
"""
Retrieve the data for a resource.
Inherited from `Client`.
Arguments:
resource (str): Path in the form of slash separated strings.
timeout (float): Continue to attempt to retrieve a resource for this many seconds before giving up and
raising an error.
Returns: A structure consisting of simple python types (dict, list, int, str etc).
Raises: ClientError if the resource cannot be retrieved for any reason.
"""
response = self._request(resource, timeout=timeout)
try:
return response.json()
except ValueError:
message = 'Unable to decode JSON response'
log.exception(message)
raise ClientError(message)
def has_resource(self, resource, timeout=None):
"""
Check if the server responds with a 200 OK status code when the resource is requested.
Inherited from `Client`.
Arguments:
resource (str): Path in the form of slash separated strings.
timeout (float): Continue to attempt to retrieve a resource for this many seconds before giving up and
raising an error.
Returns: True iff the resource exists.
"""
try:
self._request(resource, timeout=timeout)
return True
except ClientError:
return False
def _request(self, resource, timeout=None):
if timeout is None:
timeout = self.DEFAULT_TIMEOUT
headers = {
'Accept': 'application/json',
}
if self.auth_token:
headers['Authorization'] = 'Token ' + self.auth_token
try:
response = requests.get('{0}/{1}'.format(self.base_url, resource), headers=headers, timeout=timeout)
if response.status_code != requests.codes.ok: # pylint: disable=no-member
message = 'Resource "{0}" returned status code {1}'.format(resource, response.status_code)
log.error(message)
raise ClientError(message)
return response
except requests.exceptions.RequestException:
message = 'Unable to retrieve resource'
log.exception(message)
raise ClientError('{0} "{1}"'.format(message, resource))
# TODO: Provide more detailed errors as necessary.
class ClientError(Exception):
"""An error occurred that prevented the client from performing the requested operation."""
pass
from analyticsdataclient.client import ClientError
class Status(object):
"""
Query the status of the connection between the client and the remote service.
Arguments:
client (analyticsdataclient.client.Client): The client to use to access remote resources.
"""
def __init__(self, client):
"""
Initialize the Status.
Arguments:
client (analyticsdataclient.client.Client): The client to use to access remote resources.
"""
self.client = client
@property
def alive(self):
"""
A very fast shallow check to see if the service is functioning.
Returns: True iff the remote server responds to requests.
"""
return self.client.has_resource('status')
@property
def authenticated(self):
"""
Validate the client credentials.
Returns: True iff the client is successfully authenticated.
"""
return self.client.has_resource('authenticated')
@property
def healthy(self):
"""
A slow deep health check of the remote service.
Returns: True iff the remote service is reasonably confident that further operations will succeed.
"""
try:
health = self.client.get('health')
except ClientError:
return False
try:
return health['overall_status'] == 'OK'
except KeyError:
return False
import json
from unittest import TestCase
import httpretty
from analyticsdataclient.client import RestClient, ClientError
class RestClientTest(TestCase):
BASE_URI = 'http://localhost:9091'
VERSIONED_BASE_URI = BASE_URI + '/api/v0'
TEST_ENDPOINT = 'test'
TEST_URI = VERSIONED_BASE_URI + '/' + TEST_ENDPOINT
def setUp(self):
httpretty.enable()
self.client = RestClient(base_url=self.BASE_URI)
def tearDown(self):
httpretty.disable()
def test_has_resource(self):
httpretty.register_uri(httpretty.GET, self.TEST_URI, body='')
self.assertEquals(self.client.has_resource(self.TEST_ENDPOINT), True)
def test_missing_resource(self):
httpretty.register_uri(httpretty.GET, self.TEST_URI, body='', status=404)
self.assertEquals(self.client.has_resource(self.TEST_ENDPOINT), False)
def test_failed_authentication(self):
self.client = RestClient(base_url=self.BASE_URI, auth_token='atoken')
httpretty.register_uri(httpretty.GET, self.TEST_URI, body='', status=401)
self.assertEquals(self.client.has_resource(self.TEST_ENDPOINT), False)
self.assertEquals(httpretty.last_request().headers['Authorization'], 'Token atoken')
def test_get(self):
data = {
'foo': 'bar'
}
httpretty.register_uri(httpretty.GET, self.TEST_URI, body=json.dumps(data))
self.assertEquals(self.client.get(self.TEST_ENDPOINT), data)
def test_get_invalid_json(self):
data = {
'foo': 'bar'
}
httpretty.register_uri(httpretty.GET, self.TEST_URI, body=json.dumps(data)[:6])
with self.assertRaises(ClientError):
self.client.get(self.TEST_ENDPOINT)
from unittest import TestCase
from analyticsdataclient.client import Client, ClientError
from analyticsdataclient.status import Status
class StatusTest(TestCase):
def setUp(self):
self.client = InMemoryClient()
self.status = Status(self.client)
def test_alive(self):
self.assertEquals(self.status.alive, False)
self.client.resources['status'] = ''
self.assertEquals(self.status.alive, True)
def test_authenticated(self):
self.assertEquals(self.status.authenticated, False)
self.client.resources['authenticated'] = ''
self.assertEquals(self.status.authenticated, True)
def test_healthy(self):
self.client.resources['health'] = {
'overall_status': 'OK',
'detailed_status': {
'database_connection': 'OK'
}
}
self.assertEquals(self.status.healthy, True)
def test_not_healthy(self):
self.client.resources['health'] = {
'overall_status': 'UNAVAILABLE',
'detailed_status': {
'database_connection': 'UNAVAILABLE'
}
}
self.assertEquals(self.status.healthy, False)
def test_invalid_health_value(self):
self.client.resources['health'] = {}
self.assertEquals(self.status.healthy, False)
class InMemoryClient(Client):
def __init__(self):
super(InMemoryClient, self).__init__()
self.resources = {}
def has_resource(self, resource, timeout=None):
try:
self.get(resource, timeout=timeout)
return True
except ClientError:
return False
def get(self, resource, timeout=None):
try:
return self.resources[resource]
except KeyError:
raise ClientError('Unable to find requested resource')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment