CommentClientError now has sane subclasses that are meaningfully distinct, and each subclass is handled appropriately. Errors raised by the requests library are no longer handled by turning them into CommentClientErrors, since there is no meaningful handling we can do, and this way we will get more visibility into why errors are occurring. Also, HTTP status codes from the comments service indicating client error are correctly passed through to the client.
109 lines
2.8 KiB
Python
109 lines
2.8 KiB
Python
from contextlib import contextmanager
|
|
from dogapi import dog_stats_api
|
|
import json
|
|
import logging
|
|
import requests
|
|
import settings
|
|
from time import time
|
|
from uuid import uuid4
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def strip_none(dic):
|
|
return dict([(k, v) for k, v in dic.iteritems() if v is not None])
|
|
|
|
|
|
def strip_blank(dic):
|
|
def _is_blank(v):
|
|
return isinstance(v, str) and len(v.strip()) == 0
|
|
return dict([(k, v) for k, v in dic.iteritems() if not _is_blank(v)])
|
|
|
|
|
|
def extract(dic, keys):
|
|
if isinstance(keys, str):
|
|
return strip_none({keys: dic.get(keys)})
|
|
else:
|
|
return strip_none({k: dic.get(k) for k in keys})
|
|
|
|
|
|
def merge_dict(dic1, dic2):
|
|
return dict(dic1.items() + dic2.items())
|
|
|
|
|
|
@contextmanager
|
|
def request_timer(request_id, method, url):
|
|
start = time()
|
|
yield
|
|
end = time()
|
|
duration = end - start
|
|
dog_stats_api.histogram('comment_client.request.time', duration, end)
|
|
log.info(
|
|
"comment_client_request_log: request_id={request_id}, method={method}, "
|
|
"url={url}, duration={duration}".format(
|
|
request_id=request_id,
|
|
method=method,
|
|
url=url,
|
|
duration=duration
|
|
)
|
|
)
|
|
|
|
|
|
def perform_request(method, url, data_or_params=None, *args, **kwargs):
|
|
if data_or_params is None:
|
|
data_or_params = {}
|
|
headers = {'X-Edx-Api-Key': settings.API_KEY}
|
|
request_id = uuid4()
|
|
request_id_dict = {'request_id': request_id}
|
|
|
|
if method in ['post', 'put', 'patch']:
|
|
data = data_or_params
|
|
params = request_id_dict
|
|
else:
|
|
data = None
|
|
params = merge_dict(data_or_params, request_id_dict)
|
|
with request_timer(request_id, method, url):
|
|
response = requests.request(
|
|
method,
|
|
url,
|
|
data=data,
|
|
params=params,
|
|
headers=headers,
|
|
timeout=5
|
|
)
|
|
|
|
if 200 < response.status_code < 500:
|
|
raise CommentClientRequestError(response.text, response.status_code)
|
|
# Heroku returns a 503 when an application is in maintenance mode
|
|
elif response.status_code == 503:
|
|
raise CommentClientMaintenanceError(response.text)
|
|
elif response.status_code == 500:
|
|
raise CommentClient500Error(response.text)
|
|
else:
|
|
if kwargs.get("raw", False):
|
|
return response.text
|
|
else:
|
|
return json.loads(response.text)
|
|
|
|
|
|
class CommentClientError(Exception):
|
|
def __init__(self, msg):
|
|
self.message = msg
|
|
|
|
def __str__(self):
|
|
return repr(self.message)
|
|
|
|
|
|
class CommentClientRequestError(CommentClientError):
|
|
def __init__(self, msg, status_code=400):
|
|
super(CommentClientRequestError, self).__init__(msg)
|
|
self.status_code = status_code
|
|
|
|
|
|
class CommentClient500Error(CommentClientError):
|
|
pass
|
|
|
|
|
|
class CommentClientMaintenanceError(CommentClientError):
|
|
pass
|