|
import io |
|
import json |
|
import re |
|
|
|
import requests |
|
from requests.adapters import HTTPAdapter |
|
from six.moves.urllib.parse import urlencode |
|
from urllib3 import Retry |
|
|
|
|
|
class RESTResponse(io.IOBase): |
|
|
|
def __init__(self, resp): |
|
self.status = resp.status_code |
|
self.reason = resp.reason |
|
self.resp = resp |
|
self.headers = resp.headers |
|
|
|
def getheaders(self): |
|
return self.headers |
|
|
|
|
|
class RESTClientObject(object): |
|
def __init__(self, connection=None): |
|
self.connection = connection or requests.Session() |
|
retry_strategy = Retry( |
|
total=3, |
|
backoff_factor=2, |
|
status_forcelist=[429, 500, 502, 503, 504], |
|
allowed_methods=[ |
|
"HEAD", |
|
"GET", |
|
"OPTIONS", |
|
"DELETE", |
|
], |
|
) |
|
self.connection.mount("https://", HTTPAdapter(max_retries=retry_strategy)) |
|
self.connection.mount("http://", HTTPAdapter(max_retries=retry_strategy)) |
|
|
|
def request( |
|
self, |
|
method, |
|
url, |
|
query_params=None, |
|
headers=None, |
|
body=None, |
|
post_params=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
"""Perform requests. |
|
|
|
:param method: http request method |
|
:param url: http request url |
|
:param query_params: query parameters in the url |
|
:param headers: http request headers |
|
:param body: request json body, for `application/json` |
|
:param post_params: request post parameters, |
|
`application/x-www-form-urlencoded` |
|
and `multipart/form-data` |
|
:param _preload_content: if False, the urllib3.HTTPResponse object will |
|
be returned without reading/decoding response |
|
data. Default is True. |
|
:param _request_timeout: timeout setting for this request. If one |
|
number provided, it will be total request |
|
timeout. It can also be a pair (tuple) of |
|
(connection, read) timeouts. |
|
""" |
|
method = method.upper() |
|
assert method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"] |
|
|
|
if post_params and body: |
|
raise ValueError( |
|
"body parameter cannot be used with post_params parameter." |
|
) |
|
|
|
post_params = post_params or {} |
|
headers = headers or {} |
|
|
|
timeout = _request_timeout if _request_timeout is not None else (120, 120) |
|
|
|
if "Content-Type" not in headers: |
|
headers["Content-Type"] = "application/json" |
|
|
|
try: |
|
|
|
if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]: |
|
if query_params: |
|
url += "?" + urlencode(query_params) |
|
if re.search( |
|
"json", headers["Content-Type"], re.IGNORECASE |
|
) or isinstance(body, str): |
|
request_body = "{}" |
|
if body is not None: |
|
request_body = json.dumps(body) |
|
if isinstance(body, str): |
|
request_body = request_body.strip('"') |
|
r = self.connection.request( |
|
method, url, data=request_body, timeout=timeout, headers=headers |
|
) |
|
else: |
|
|
|
msg = """Cannot prepare a request message for provided |
|
arguments. Please check that your arguments match |
|
declared content type.""" |
|
raise ApiException(status=0, reason=msg) |
|
|
|
else: |
|
r = self.connection.request( |
|
method, url, params=query_params, timeout=timeout, headers=headers |
|
) |
|
except Exception as e: |
|
msg = "{0}\n{1}".format(type(e).__name__, str(e)) |
|
raise ApiException(status=0, reason=msg) |
|
|
|
if _preload_content: |
|
r = RESTResponse(r) |
|
|
|
if r.status == 401 or r.status == 403: |
|
raise AuthorizationException(http_resp=r) |
|
|
|
if not 200 <= r.status <= 299: |
|
raise ApiException(http_resp=r) |
|
|
|
return r |
|
|
|
def GET( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"GET", |
|
url, |
|
headers=headers, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
query_params=query_params, |
|
) |
|
|
|
def HEAD( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"HEAD", |
|
url, |
|
headers=headers, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
query_params=query_params, |
|
) |
|
|
|
def OPTIONS( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
post_params=None, |
|
body=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"OPTIONS", |
|
url, |
|
headers=headers, |
|
query_params=query_params, |
|
post_params=post_params, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
body=body, |
|
) |
|
|
|
def DELETE( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
body=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"DELETE", |
|
url, |
|
headers=headers, |
|
query_params=query_params, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
body=body, |
|
) |
|
|
|
def POST( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
post_params=None, |
|
body=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"POST", |
|
url, |
|
headers=headers, |
|
query_params=query_params, |
|
post_params=post_params, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
body=body, |
|
) |
|
|
|
def PUT( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
post_params=None, |
|
body=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"PUT", |
|
url, |
|
headers=headers, |
|
query_params=query_params, |
|
post_params=post_params, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
body=body, |
|
) |
|
|
|
def PATCH( |
|
self, |
|
url, |
|
headers=None, |
|
query_params=None, |
|
post_params=None, |
|
body=None, |
|
_preload_content=True, |
|
_request_timeout=None, |
|
): |
|
return self.request( |
|
"PATCH", |
|
url, |
|
headers=headers, |
|
query_params=query_params, |
|
post_params=post_params, |
|
_preload_content=_preload_content, |
|
_request_timeout=_request_timeout, |
|
body=body, |
|
) |
|
|
|
|
|
class ApiException(Exception): |
|
|
|
def __init__(self, status=None, reason=None, http_resp=None, body=None): |
|
if http_resp: |
|
self.status = http_resp.status |
|
self.code = http_resp.status |
|
self.reason = http_resp.reason |
|
self.body = http_resp.resp.text |
|
try: |
|
if http_resp.resp.text: |
|
error = json.loads(http_resp.resp.text) |
|
self.message = error["message"] |
|
else: |
|
self.message = http_resp.resp.text |
|
except Exception as e: |
|
self.message = http_resp.resp.text |
|
self.headers = http_resp.getheaders() |
|
else: |
|
self.status = status |
|
self.code = status |
|
self.reason = reason |
|
self.body = body |
|
self.message = body |
|
self.headers = None |
|
|
|
def __str__(self): |
|
"""Custom error messages for exception""" |
|
error_message = "({0})\n" "Reason: {1}\n".format(self.status, self.reason) |
|
if self.headers: |
|
error_message += "HTTP response headers: {0}\n".format(self.headers) |
|
|
|
if self.body: |
|
error_message += "HTTP response body: {0}\n".format(self.body) |
|
|
|
return error_message |
|
|
|
def is_not_found(self) -> bool: |
|
return self.code == 404 |
|
|
|
|
|
class AuthorizationException(ApiException): |
|
def __init__(self, status=None, reason=None, http_resp=None, body=None): |
|
try: |
|
data = json.loads(http_resp.resp.text) |
|
if "error" in data: |
|
self._error_code = data["error"] |
|
else: |
|
self._error_code = "" |
|
except Exception: |
|
self._error_code = "" |
|
super().__init__(status, reason, http_resp, body) |
|
|
|
@property |
|
def error_code(self): |
|
return self._error_code |
|
|
|
@property |
|
def status_code(self): |
|
return self.status |
|
|
|
@property |
|
def token_expired(self) -> bool: |
|
return self._error_code == "EXPIRED_TOKEN" |
|
|
|
@property |
|
def invalid_token(self) -> bool: |
|
return self._error_code == "INVALID_TOKEN" |
|
|
|
def __str__(self): |
|
"""Custom error messages for exception""" |
|
error_message = f"authorization error: {self._error_code}. status_code: {self.status}, reason: {self.reason}" |
|
|
|
if self.headers: |
|
error_message += f", headers: {self.headers}" |
|
|
|
if self.body: |
|
error_message += f", response: {self.body}" |
|
|
|
return error_message |
|
|