moira-client
171 строка · 4.7 Кб
1from retry.api import retry_call2from requests import HTTPError3from requests.auth import HTTPBasicAuth4import requests5
6
7def raise_for_status_with_body(r):8try:9r.raise_for_status()10except HTTPError as e:11try:12body = r.json()13except ValueError:14body = r.text15e.args = e.args + (body,)16raise e17
18
19class ResponseStructureError(Exception):20def __init__(self, msg, content):21"""22
23:param msg: str error message
24:param content: dict response content
25"""
26self.msg = msg27self.content = content28
29
30class InvalidJSONError(Exception):31def __init__(self, content):32"""33
34:param content: bytes response content
35"""
36self.content = content37
38
39class RetryPolicy:40def __init__(self, max_tries=1, delay=0, backoff=1):41"""A helper object describing client retry policy.42
43:param max_tries: maximum number of attempts
44:param delay: delay between attempts in seconds
45:param backoff: multiplier applied to delay between attempts
46"""
47self.max_tries = max_tries48self.delay = delay49self.backoff = backoff50
51def _as_kwargs(self):52return {53"tries": self.max_tries,54"delay": self.delay,55"backoff": self.backoff,56}57
58
59class Client:60def __init__(self, api_url, auth_custom=None, auth_user=None, auth_pass=None, login=None, retry_policy=None):61"""62
63:param api_url: str Moira API URL
64:param auth_custom: dict auth custom headers
65:param auth_user: str auth user
66:param auth_pass: str auth password
67:param login: str auth login
68:param retry_policy: RetryPolicy
69"""
70if not api_url.endswith('/'):71self.api_url = api_url + '/'72else:73self.api_url = api_url74
75self.retry_policy = retry_policy or RetryPolicy()76
77self.auth = None78self.headers = {79'X-Webauth-User': login,80'Content-Type': 'application/json',81'User-Agent': 'Python Moira Client'82}83
84if auth_user and auth_pass:85self.auth = HTTPBasicAuth(auth_user, auth_pass)86
87if auth_custom:88self.headers.update(auth_custom)89
90def get(self, path='', **kwargs):91"""92
93:param path: str api path
94:param kwargs: additional parameters for request
95:return: dict response
96
97:raises: HTTPError
98:raises: InvalidJSONError
99"""
100return retry_call(101self._get, (path, ), kwargs,102**self.retry_policy._as_kwargs(),103)104
105def _get(self, path='', **kwargs):106r = requests.get(self._path_join(path), timeout=10, headers=self.headers, auth=self.auth, **kwargs)107raise_for_status_with_body(r)108try:109return r.json()110except ValueError:111raise InvalidJSONError(r.content)112
113def delete(self, path='', **kwargs):114"""115
116:param path: str api path
117:param kwargs: additional parameters for request
118:return: dict response
119
120:raises: HTTPError
121:raises: InvalidJSONError
122"""
123return retry_call(124self._delete, (path, ), kwargs,125**self.retry_policy._as_kwargs(),126)127
128def _delete(self, path='', **kwargs):129r = requests.delete(self._path_join(path), timeout=10, headers=self.headers, auth=self.auth, **kwargs)130raise_for_status_with_body(r)131# AD-13298: DELETE requests (sometimes?) return a 0-byte response132# and this is not an error133if len(r.content) == 0:134return None135else:136try:137return r.json()138except ValueError:139raise InvalidJSONError(r.content)140
141def put(self, path='', **kwargs):142"""143
144:param path: str api path
145:param kwargs: additional parameters for request
146:return: dict response
147
148:raises: HTTPError
149:raises: InvalidJSONError
150"""
151return retry_call(152self._put, (path, ), kwargs,153**self.retry_policy._as_kwargs(),154)155
156def _put(self, path='', **kwargs):157r = requests.put(self._path_join(path), timeout=10, headers=self.headers, auth=self.auth, **kwargs)158raise_for_status_with_body(r)159try:160return r.json()161except ValueError:162raise InvalidJSONError(r.content)163
164def _path_join(self, *args):165path = self.api_url166for part in args:167if part.startswith('/'):168path += part[1:]169else:170path += part171return path172