韩宇
init
1b7e88c
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",
], # all the methods that are supposed to be idempotent
)
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:
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
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:
# Cannot generate the request from given parameters
msg = """Cannot prepare a request message for provided
arguments. Please check that your arguments match
declared content type."""
raise ApiException(status=0, reason=msg)
# For `GET`, `HEAD`
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