do_api_query: Reduce error handling code duplication.

Also fixes bugs where the retry code wouldn't work correctly if
verbose wasn't set, prints out which API query had the error, and is
more consistent about printing something to end the "..." sequences.

(imported from commit 15c1e0e4a14c5c5559b43bafe4ec268451ee04f5)
This commit is contained in:
Tim Abbott 2012-11-29 09:17:09 -05:00
parent 70d9281f7a
commit 3285d914c5

View file

@ -32,6 +32,7 @@ import os
# Check that we have a recent enough version # Check that we have a recent enough version
# Older versions don't provide the 'json' attribute on responses. # Older versions don't provide the 'json' attribute on responses.
assert(requests.__version__ > '0.12') assert(requests.__version__ > '0.12')
API_VERSTRING = "/api/v1/"
class HumbugAPI(object): class HumbugAPI(object):
def __init__(self, email, api_key=None, api_key_file=None, def __init__(self, email, api_key=None, api_key_file=None,
@ -54,34 +55,54 @@ class HumbugAPI(object):
self.client_name = client self.client_name = client
def do_api_query(self, request, url, longpolling = False): def do_api_query(self, request, url, longpolling = False):
had_error_retry = False
request["email"] = self.email request["email"] = self.email
request["api-key"] = self.api_key request["api-key"] = self.api_key
request["client"] = self.client_name request["client"] = self.client_name
failures = 0
for (key, val) in request.iteritems(): for (key, val) in request.iteritems():
if not (isinstance(val, str) or isinstance(val, unicode)): if not (isinstance(val, str) or isinstance(val, unicode)):
request[key] = simplejson.dumps(val) request[key] = simplejson.dumps(val)
query_state = {
'had_error_retry': False,
'request': request,
'failures': 0,
}
def error_retry(error_string):
if not self.retry_on_errors or query_state["failures"] >= 10:
return False
if self.verbose:
if not query_state["had_error_retry"]:
sys.stdout.write("humbug API(%s): connection error%s -- retrying." % \
(url.split(API_VERSTRING, 2)[1], error_string,))
query_state["had_error_retry"] = True
else:
sys.stdout.write(".")
sys.stdout.flush()
query_state["request"]["dont_block"] = simplejson.dumps(True)
time.sleep(1)
query_state["failures"] += 1
return True
def end_error_retry(succeeded):
if query_state["had_error_retry"] and self.verbose:
if succeeded:
print "Success!"
else:
print "Failed!"
while True: while True:
try: try:
res = requests.post(urlparse.urljoin(self.base_url, url), data=request, res = requests.post(urlparse.urljoin(self.base_url, url),
data=query_state["request"],
verify=True, timeout=55) verify=True, timeout=55)
# On 50x errors, try again after a short sleep # On 50x errors, try again after a short sleep
if str(res.status_code).startswith('5') and self.retry_on_errors and failures < 10: if str(res.status_code).startswith('5'):
if self.verbose: if error_retry(" (server %s)" % (res.status_code,)):
if not had_error_retry: continue
sys.stdout.write("connection error %s -- retrying." % (res.status_code,)) # Otherwise fall through and process the python-requests error normally
had_error_retry = True
request["dont_block"] = simplejson.dumps(True)
else:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1)
failures += 1
continue
except (requests.exceptions.Timeout, requests.exceptions.SSLError) as e: except (requests.exceptions.Timeout, requests.exceptions.SSLError) as e:
# Timeouts are either a Timeout or an SSLError; we # Timeouts are either a Timeout or an SSLError; we
# want the later exception handlers to deal with any # want the later exception handlers to deal with any
@ -94,21 +115,13 @@ class HumbugAPI(object):
# and the correct response is to just retry # and the correct response is to just retry
continue continue
else: else:
end_error_retry(False)
return {'msg': "Connection error:\n%s" % traceback.format_exc(), return {'msg': "Connection error:\n%s" % traceback.format_exc(),
"result": "connection-error"} "result": "connection-error"}
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
if self.retry_on_errors and failures < 10: if error_retry(""):
if self.verbose:
if not had_error_retry:
sys.stdout.write("connection error -- retrying.")
had_error_retry = True
request["dont_block"] = simplejson.dumps(True)
else:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1)
failures += 1
continue continue
end_error_retry(False)
return {'msg': "Connection error:\n%s" % traceback.format_exc(), return {'msg': "Connection error:\n%s" % traceback.format_exc(),
"result": "connection-error"} "result": "connection-error"}
except Exception: except Exception:
@ -116,10 +129,10 @@ class HumbugAPI(object):
return {'msg': "Unexpected error:\n%s" % traceback.format_exc(), return {'msg': "Unexpected error:\n%s" % traceback.format_exc(),
"result": "unexpected-error"} "result": "unexpected-error"}
if self.verbose and had_error_retry:
print "Success!"
if res.json is not None: if res.json is not None:
end_error_retry(True)
return res.json return res.json
end_error_retry(False)
return {'msg': res.text, "result": "http-error", return {'msg': res.text, "result": "http-error",
"status_code": res.status_code} "status_code": res.status_code}
@ -129,7 +142,7 @@ class HumbugAPI(object):
url = name url = name
def call(self, *args, **kwargs): def call(self, *args, **kwargs):
request = make_request(*args, **kwargs) request = make_request(*args, **kwargs)
return self.do_api_query(request, '/api/v1/' + url, **query_kwargs) return self.do_api_query(request, API_VERSTRING + url, **query_kwargs)
call.func_name = name call.func_name = name
setattr(cls, name, call) setattr(cls, name, call)