api_base.py 4.86 KB
Newer Older
1 2 3
"""
This package contains the Zoe library, with modules used by more than one of the Zoe components. This library can also be used to write new clients for Zoe.
"""
4 5 6 7
from functools import wraps
import logging
import time

8 9 10 11 12 13
import requests
import requests.exceptions

from zoe_lib.version import ZOE_API_VERSION
from zoe_lib.exceptions import ZoeAPIException

14 15 16
log = logging.getLogger(__name__)


17
def retry(exception_to_check, tries=4, delay=3, backoff=2):
18 19 20 21 22
    """Retry calling the decorated function using an exponential backoff.

    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry

23
    :param exception_to_check: the exception to check. may be a tuple of
24
        exceptions to check
25
    :type exception_to_check: Exception or tuple
26 27 28 29 30 31 32 33
    :param tries: number of times to try (not retry) before giving up
    :type tries: int
    :param delay: initial delay between retries in seconds
    :type delay: int
    :param backoff: backoff multiplier e.g. value of 2 will double the delay
        each retry
    :type backoff: int
    """
34 35 36
    def deco_retry(func):
        """Decorator to wrap calls that need to be retried."""
        @wraps(func)
37
        def f_retry(*args, **kwargs):
38
            """The actual decorator."""
39 40 41
            mtries, mdelay = tries, delay
            while mtries > 1:
                try:
42 43
                    return func(*args, **kwargs)
                except exception_to_check as e:
44 45 46 47 48
                    msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
                    log.warning(msg)
                    time.sleep(mdelay)
                    mtries -= 1
                    mdelay *= backoff
49
            return func(*args, **kwargs)
50 51 52 53 54

        return f_retry  # true decorator

    return deco_retry

55 56

class ZoeAPIBase:
57
    """Base class for the Zoe Client API."""
58 59 60 61 62
    def __init__(self, url, user, password):
        self.url = url
        self.user = user
        self.password = password

63 64 65 66
    @retry(ZoeAPIException)
    def _rest_get_stream(self, path):
        """
        :type path: str
67
        :rtype: Tuple[requests.Response, int]
68 69 70 71 72 73 74 75 76 77 78 79 80
        """
        url = self.url + '/api/' + ZOE_API_VERSION + path
        try:
            req = requests.get(url, auth=(self.user, self.password), stream=True)
        except requests.exceptions.Timeout:
            raise ZoeAPIException('HTTP connection timeout')
        except requests.exceptions.HTTPError:
            raise ZoeAPIException('Invalid HTTP response')
        except requests.exceptions.ConnectionError as e:
            raise ZoeAPIException('Connection error: {}'.format(e))

        return req, req.status_code

81
    @retry(ZoeAPIException)
82 83 84 85 86 87 88
    def _rest_get(self, path):
        """
        :type path: str
        :rtype: (dict, int)
        """
        url = self.url + '/api/' + ZOE_API_VERSION + path
        try:
89
            req = requests.get(url, auth=(self.user, self.password))
90 91 92 93 94 95 96 97
        except requests.exceptions.Timeout:
            raise ZoeAPIException('HTTP connection timeout')
        except requests.exceptions.HTTPError:
            raise ZoeAPIException('Invalid HTTP response')
        except requests.exceptions.ConnectionError as e:
            raise ZoeAPIException('Connection error: {}'.format(e))

        try:
98
            data = req.json()
99 100
        except ValueError:
            data = None
101
        return data, req.status_code
102

103
    @retry(ZoeAPIException)
104 105 106 107 108 109 110
    def _rest_post(self, path, payload):
        """
        :type path: str
        :rtype: (dict, int)
        """
        url = self.url + '/api/' + ZOE_API_VERSION + path
        try:
111
            req = requests.post(url, auth=(self.user, self.password), json=payload)
112 113 114 115 116 117 118 119
        except requests.exceptions.Timeout:
            raise ZoeAPIException('HTTP connection timeout')
        except requests.exceptions.HTTPError:
            raise ZoeAPIException('Invalid HTTP response')
        except requests.exceptions.ConnectionError as e:
            raise ZoeAPIException('Connection error: {}'.format(e))

        try:
120
            data = req.json()
121 122
        except ValueError:
            data = None
123
        return data, req.status_code
124

125
    @retry(ZoeAPIException)
126 127 128 129 130 131 132
    def _rest_delete(self, path):
        """
        :type path: str
        :rtype: (dict, int)
        """
        url = self.url + '/api/' + ZOE_API_VERSION + path
        try:
133
            req = requests.delete(url, auth=(self.user, self.password))
134 135 136 137 138 139 140 141
        except requests.exceptions.Timeout:
            raise ZoeAPIException('HTTP connection timeout')
        except requests.exceptions.HTTPError:
            raise ZoeAPIException('Invalid HTTP response')
        except requests.exceptions.ConnectionError as e:
            raise ZoeAPIException('Connection error: {}'.format(e))

        try:
142
            data = req.json()
143 144
        except ValueError:
            data = None
145
        return data, req.status_code