Skip to content

Commit a03a9b2

Browse files
committed
error handling in sliderule client has two classes of exceptions: fatal and transient
1 parent abb7d72 commit a03a9b2

File tree

4 files changed

+52
-46
lines changed

4 files changed

+52
-46
lines changed

sliderule/sliderule.py

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@
5050
ps_refresh_token = None
5151
ps_access_token = None
5252

53-
max_attempts_per_request = 1
54-
5553
verbose = False
5654

5755
request_timeout = (10, 60) # (connection, read) in seconds
@@ -115,6 +113,16 @@
115113
12: "STRING"
116114
}
117115

116+
###############################################################################
117+
# CLIENT EXCEPTIONS
118+
###############################################################################
119+
120+
class FatalError(RuntimeError):
121+
pass
122+
123+
class TransientError(RuntimeError):
124+
pass
125+
118126
###############################################################################
119127
# UTILITIES
120128
###############################################################################
@@ -263,7 +271,7 @@ def __parse_native(data, callbacks):
263271
raw = b''.join(rec_size_rsps)
264272
rec_version, rec_type_size, rec_data_size = struct.unpack('>hhi', raw)
265273
if rec_version != 2:
266-
raise SystemError("Invalid record format: %d" % (rec_version))
274+
raise FatalError("Invalid record format: %d" % (rec_version))
267275
rec_size = rec_type_size + rec_data_size
268276
rec_size_rsps.clear()
269277
i += bytes_to_append
@@ -313,9 +321,9 @@ def __logeventrec(rec):
313321
eventlogger[rec['level']]('%s' % (rec["attr"]))
314322

315323
#
316-
# __raiseexceptrec
324+
# __exceptrec
317325
#
318-
def __raiseexceptrec(rec):
326+
def __exceptrec(rec):
319327
if verbose:
320328
if rec["code"] >= 0:
321329
eventlogger[rec["level"]]("Exception <%d>: %s", rec["code"], rec["text"])
@@ -325,7 +333,7 @@ def __raiseexceptrec(rec):
325333
#
326334
# Globals
327335
#
328-
__callbacks = {'eventrec': __logeventrec, 'exceptrec': __raiseexceptrec}
336+
__callbacks = {'eventrec': __logeventrec, 'exceptrec': __exceptrec}
329337

330338
###############################################################################
331339
# APIs
@@ -369,8 +377,7 @@ def source (api, parm={}, stream=False, callbacks={}):
369377
>>> print(rsps)
370378
{'time': 1300556199523.0, 'format': 'GPS'}
371379
'''
372-
global service_url, service_org, max_attempts_per_request
373-
attempts = max_attempts_per_request
380+
global service_url, service_org
374381
rqst = json.dumps(parm)
375382
rsps = {}
376383
headers = None
@@ -386,40 +393,37 @@ def source (api, parm={}, stream=False, callbacks={}):
386393
else:
387394
url = 'http://%s/source/%s' % (service_url, api)
388395
# Attempt Request #
389-
while attempts > 0:
390-
attempts -= 1
391-
try:
392-
# Perform Request
393-
if not stream:
394-
data = requests.get(url, data=rqst, headers=headers, timeout=request_timeout)
395-
else:
396-
data = requests.post(url, data=rqst, headers=headers, timeout=request_timeout, stream=True)
397-
data.raise_for_status()
398-
# Parse Response
399-
format = data.headers['Content-Type']
400-
if format == 'text/plain':
401-
rsps = __parse_json(data)
402-
elif format == 'application/octet-stream':
403-
rsps = __parse_native(data, callbacks)
404-
else:
405-
raise TypeError('unsupported content type: %s' % (format))
406-
# Complete
407-
break
408-
except requests.exceptions.SSLError as e:
409-
logger.error("Unable to verify SSL certificate: {}".format(e))
410-
except requests.ConnectionError as e:
411-
logger.error("Failed to connect to endpoint {} ... retrying request".format(url))
412-
except requests.Timeout as e:
413-
logger.error("Timed-out waiting for response from endpoint {} ... retrying request".format(url))
414-
except requests.exceptions.ChunkedEncodingError as e:
415-
logger.error("Unexpected termination of response from endpoint {} ... retrying request".format(url))
416-
except requests.HTTPError as e:
417-
if e.response.status_code == 503:
418-
logger.error("Server experiencing heavy load, stalling on request to {} ... will retry".format(url))
419-
else:
420-
logger.error("HTTP error {} from endpoint {} ... retrying request".format(e.response.status_code, url))
421-
except:
422-
raise
396+
try:
397+
# Perform Request
398+
if not stream:
399+
data = requests.get(url, data=rqst, headers=headers, timeout=request_timeout)
400+
else:
401+
data = requests.post(url, data=rqst, headers=headers, timeout=request_timeout, stream=True)
402+
data.raise_for_status()
403+
# Parse Response
404+
format = data.headers['Content-Type']
405+
if format == 'text/plain':
406+
rsps = __parse_json(data)
407+
elif format == 'application/octet-stream':
408+
rsps = __parse_native(data, callbacks)
409+
else:
410+
raise FatalError('unsupported content type: %s' % (format))
411+
except requests.exceptions.SSLError as e:
412+
raise FatalError("Unable to verify SSL certificate: {}".format(e))
413+
except requests.ConnectionError as e:
414+
raise FatalError("Failed to connect to endpoint {}".format(url))
415+
except requests.Timeout as e:
416+
raise TransientError("Timed-out waiting for response from endpoint {}".format(url))
417+
except requests.exceptions.ChunkedEncodingError as e:
418+
raise RuntimeError("Unexpected termination of response from endpoint {}".format(url))
419+
except requests.HTTPError as e:
420+
if e.response.status_code == 503:
421+
raise TransientError("Server experiencing heavy load, stalling on request to {}".format(url))
422+
else:
423+
raise FatalError("HTTP error {} from endpoint {}".format(e.response.status_code, url))
424+
except:
425+
raise
426+
# Return Response
423427
return rsps
424428

425429
#
@@ -501,7 +505,7 @@ def set_rqst_timeout (timeout):
501505
if type(timeout) == tuple:
502506
request_timeout = timeout
503507
else:
504-
raise TypeError('timeout must be a tuple (<connection timeout>, <read timeout>)')
508+
raise FatalError('timeout must be a tuple (<connection timeout>, <read timeout>)')
505509

506510
#
507511
# AUTHENTICATE

tests/test_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def test_gps2utc(self):
2020
@pytest.mark.network
2121
class TestRemote:
2222
def test_init_badurl(self):
23-
with pytest.raises( (ConnectTimeout, ConnectionError) ):
23+
with pytest.raises( (sliderule.FatalError) ):
2424
sliderule.set_rqst_timeout((1, 60))
2525
sliderule.set_url('incorrect.org:8877')
26+
sliderule.source("version")

tests/test_icesat2.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ def test_toregion(self):
3838
@pytest.mark.network
3939
class TestRemote:
4040
def test_init_badurl(self):
41-
with pytest.raises( (ConnectTimeout, ConnectionError) ):
41+
with pytest.raises( (sliderule.FatalError) ):
4242
icesat2.init('incorrect.org:8877')
43+
sliderule.source("version")
4344

4445
def test_get_version(self, server, organization):
4546
icesat2.init(server, organization=organization)

tests/test_luaerr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,4 @@ def test_timeout(self, server, asset, organization):
6868
}
6969
rsps = sliderule.source("atl06", rqst, stream=True, callbacks=GLOBAL_callbacks)
7070
assert(len(rsps) == 0)
71-
assert("request for {} timed-out after 10 seconds".format(resource) == GLOBAL_message)
71+
assert("{} timed-out after 10 seconds".format(resource) in GLOBAL_message)

0 commit comments

Comments
 (0)