deb-python-seamicroclient/seamicroclient/client.py

202 lines
6.9 KiB
Python

# All Rights Reserved.
#
# 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.
"""
Seamicro Client interface. Handles the REST calls and responses.
"""
import logging
import time
import requests
try:
import json
except ImportError:
import simplejson as json
from seamicroclient import exceptions
from seamicroclient import utils
class HTTPClient(object):
USER_AGENT = 'python-seamicroclient'
def __init__(self, user, password, auth_url=None,
timeout=None, http_log_debug=False, retries=3):
self.user = user
self.password = password
self.api_endpoint = auth_url
self.auth_url = auth_url.rstrip('/')
self.version = 'v2.0'
self.http_log_debug = http_log_debug
if timeout is not None:
self.timeout = float(timeout)
else:
self.timeout = None
self.retries = int(retries)
self.times = [] # [("item", starttime, endtime), ...]
self._logger = logging.getLogger(__name__)
if self.http_log_debug and not self._logger.handlers:
# Logging level is already set on the root logger
ch = logging.StreamHandler()
self._logger.addHandler(ch)
self._logger.propagate = False
if hasattr(requests, 'logging'):
rql = requests.logging.getLogger(requests.__name__)
rql.addHandler(ch)
# Since we have already setup the root logger on debug, we
# have to set it up here on WARNING (its original level)
# otherwise we will get all the requests logging messanges
rql.setLevel(logging.WARNING)
def get_timings(self):
return self.times
def reset_timings(self):
self.times = []
def http_log_req(self, method, url, kwargs):
if not self.http_log_debug:
return
string_parts = ['curl -i']
string_parts.append(" '%s'" % url)
string_parts.append(' -X %s' % method)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
string_parts.append(header)
if 'data' in kwargs:
string_parts.append(" -d '%s'" % (kwargs['data']))
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
def http_log_resp(self, resp):
if not self.http_log_debug:
return
self._logger.debug(
"RESP: [%s] %s\nRESP BODY: %s\n",
resp.status_code,
resp.headers,
resp.text)
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
kwargs['headers']['Accept'] = 'application/json'
if 'body' in kwargs:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs['body'])
del kwargs['body']
if self.timeout is not None:
kwargs.setdefault('timeout', self.timeout)
self.http_log_req(method, url, kwargs)
resp = requests.request(
method,
url,
**kwargs)
self.http_log_resp(resp)
if resp.text:
# httplib2 returns a connection refused event as a 400 response.
# To determine if it is a bad request or refused connection we need
# to check the body. httplib2 tests check for 'Connection refused'
# or 'actively refused' in the body, so that's what we'll do.
if resp.status_code == 400:
if ('Connection refused' in resp.text or
'actively refused' in resp.text):
raise exceptions.ConnectionRefused(resp.text)
try:
body = json.loads(resp.text)
except ValueError:
body = resp.text
else:
body = None
if resp.status_code >= 400:
raise exceptions.from_response(resp, body, url, method)
return resp, body
def _time_request(self, url, method, **kwargs):
start_time = time.time()
resp, body = self.request(url, method, **kwargs)
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
return resp, body
def _cs_request(self, url, method, **kwargs):
attempts = 0
retry_delay = 5
while True:
attempts += 1
if method in ['GET', 'DELETE']:
url = "%s?username=%s&password=%s" % (url, self.user,
self.password)
else:
kwargs.setdefault('body', {}).update({'username': self.user,
'password': self.password})
try:
resp, body = self._time_request(self.api_endpoint + url,
method,**kwargs)
return resp, body
except requests.exceptions.ConnectionError as e:
if attempts > self.retries:
raise
# Catch a connection refused from requests.request
# retry again with some time delay
self._logger.debug("Connection refused: %s" % e)
self._logger.debug("Failed attempt(%s of %s), "
"retrying in %s seconds" %
(attempts, self.retries,
retry_delay))
time.sleep(retry_delay)
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def get_client_class(version):
version_map = {
'2': 'seamicroclient.v2.client.Client',
}
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = "Invalid client version '%s'. must be one of: %s" % (
(version, ', '.join(version_map.keys())))
raise exceptions.UnsupportedVersion(msg)
return utils.import_class(client_path)
def Client(version, *args, **kwargs):
client_class = get_client_class(version)
return client_class(*args, **kwargs)