@@ -137,6 +137,11 @@ def __init__(self, message, http_body=None, http_status=None, json_body=None):
137
137
class AuthenticationError (CleverError ):
138
138
pass
139
139
140
+ class TooManyRequestsError (CleverError ):
141
+ def __init__ (self , message , res ):
142
+ super (TooManyRequestsError , self ).__init__ (message , res ['body' ], res ['code' ])
143
+ self .http_headers = res ['headers' ]
144
+
140
145
141
146
def convert_to_clever_object (klass , resp , auth ):
142
147
# TODO: to support includes we'll have to infer klass from resp['uri']
@@ -209,11 +214,12 @@ def jsonencode(cls, d):
209
214
return json .dumps (d )
210
215
211
216
def request (self , meth , url , params = {}):
212
- rbody , rcode , my_auth = self .request_raw (meth , url , params )
213
- resp = self .interpret_response (rbody , rcode )
217
+ res , my_auth = self .request_raw (meth , url , params )
218
+ resp = self .interpret_response (res )
214
219
return resp , my_auth
215
220
216
- def handle_api_error (self , rbody , rcode , resp ):
221
+ def handle_api_error (self , res , resp ):
222
+ rbody , rheaders , rcode = res ['body' ], res ['headers' ], res ['code' ]
217
223
try :
218
224
error = resp ['error' ]
219
225
except (KeyError , TypeError ):
@@ -224,6 +230,8 @@ def handle_api_error(self, rbody, rcode, resp):
224
230
raise InvalidRequestError (error , rbody , rcode , resp )
225
231
elif rcode == 401 :
226
232
raise AuthenticationError (error , rbody , rcode , resp )
233
+ elif rcode == 429 :
234
+ raise TooManyRequestsError (error , res )
227
235
else :
228
236
raise APIError (error , rbody , rcode , resp )
229
237
@@ -264,29 +272,30 @@ def request_raw(self, meth, url, params={}):
264
272
headers ['Authorization' ] = 'Basic {}' .format (base64 .b64encode (my_auth ['api_key' ] + ':' ))
265
273
elif my_auth .get ('token' , None ) != None :
266
274
headers ['Authorization' ] = 'Bearer {}' .format (my_auth ['token' ])
267
- if _httplib == 'requests' :
268
- rbody , rcode = self .requests_request ( meth , abs_url , headers , params )
269
- elif _httplib == 'pycurl' :
270
- rbody , rcode = self .pycurl_request ( meth , abs_url , headers , params )
271
- elif _httplib == 'urlfetch' :
272
- rbody , rcode = self . urlfetch_request ( meth , abs_url , headers , params )
273
- elif _httplib == 'urllib2' :
274
- rbody , rcode = self . urllib2_request (meth , abs_url , headers , params )
275
+ make_request = {
276
+ 'requests' : self .requests_request ,
277
+ 'pycurl' : self . pycurl_request ,
278
+ 'urlfetch' : self .urlfetch_request ,
279
+ 'urllib2' : self . urllib2_request
280
+ }
281
+ if _httplib in make_request :
282
+ res = make_request [ _httplib ] (meth , abs_url , headers , params )
275
283
else :
276
284
raise CleverError (
277
285
"Clever Python library bug discovered: invalid httplib %s. Please report to [email protected] " % (
_httplib , ))
278
286
logger .debug ('API request to %s returned (response code, response body) of (%d, %r)' %
279
- (abs_url , rcode , rbody ))
280
- return rbody , rcode , my_auth
287
+ (abs_url , res [ 'code' ], res [ 'body' ] ))
288
+ return res , my_auth
281
289
282
- def interpret_response (self , rbody , rcode ):
290
+ def interpret_response (self , http_res ):
291
+ rbody , rcode = http_res ['body' ], http_res ['code' ]
283
292
try :
284
- resp = json .loads (rbody )
293
+ resp = json .loads (rbody ) if rcode != 429 else { 'error' : 'Too Many Requests' }
285
294
except Exception :
286
295
raise APIError ("Invalid response body from API: %s (HTTP response code was %d)" %
287
296
(rbody , rcode ), rbody , rcode )
288
297
if not (200 <= rcode < 300 ):
289
- self .handle_api_error (rbody , rcode , resp )
298
+ self .handle_api_error (http_res , resp )
290
299
return resp
291
300
292
301
def requests_request (self , meth , abs_url , headers , params ):
@@ -324,11 +333,12 @@ def requests_request(self, meth, abs_url, headers, params):
324
333
# are succeptible to the same and should be updated.
325
334
content = result .content
326
335
status_code = result .status_code
336
+ headers = result .headers
327
337
except Exception , e :
328
338
# Would catch just requests.exceptions.RequestException, but can
329
339
# also raise ValueError, RuntimeError, etc.
330
340
self .handle_requests_error (e )
331
- return content , status_code
341
+ return { 'body' : content , 'headers' : headers , 'code' : status_code }
332
342
333
343
def handle_requests_error (self , e ):
334
344
if isinstance (e , requests .exceptions .RequestException ):
@@ -346,6 +356,7 @@ def handle_requests_error(self, e):
346
356
347
357
def pycurl_request (self , meth , abs_url , headers , params ):
348
358
s = StringIO .StringIO ()
359
+ rheader = StringIO .StringIO ()
349
360
curl = pycurl .Curl ()
350
361
351
362
meth = meth .lower ()
@@ -374,6 +385,7 @@ def pycurl_request(self, meth, abs_url, headers, params):
374
385
curl .setopt (pycurl .CONNECTTIMEOUT , 30 )
375
386
curl .setopt (pycurl .TIMEOUT , 80 )
376
387
curl .setopt (pycurl .HTTPHEADER , ['%s: %s' % (k , v ) for k , v in headers .iteritems ()])
388
+ curl .setopt (pycurl .HEADERFUNCTION , rheader .write )
377
389
if verify_ssl_certs :
378
390
curl .setopt (pycurl .CAINFO , CLEVER_CERTS )
379
391
else :
@@ -383,9 +395,7 @@ def pycurl_request(self, meth, abs_url, headers, params):
383
395
curl .perform ()
384
396
except pycurl .error , e :
385
397
self .handle_pycurl_error (e )
386
- rbody = s .getvalue ()
387
- rcode = curl .getinfo (pycurl .RESPONSE_CODE )
388
- return rbody , rcode
398
+ return {'body' : s .getvalue (), 'headers' : rheader .getvalue (), 'code' : curl .getinfo (pycurl .RESPONSE_CODE )}
389
399
390
400
def handle_pycurl_error (self , e ):
391
401
if e [0 ] in [pycurl .E_COULDNT_CONNECT ,
@@ -429,7 +439,7 @@ def urlfetch_request(self, meth, abs_url, headers, params):
429
439
result = urlfetch .fetch (** args )
430
440
except urlfetch .Error , e :
431
441
self .handle_urlfetch_error (e , abs_url )
432
- return result .content , result .status_code
442
+ return { 'body' : result .content , 'headers' : result .headers , 'code' : result . status_code }
433
443
434
444
def handle_urlfetch_error (self , e , abs_url ):
435
445
if isinstance (e , urlfetch .InvalidURLError ):
@@ -467,13 +477,15 @@ def urllib2_request(self, meth, abs_url, headers, params):
467
477
try :
468
478
response = urllib2 .urlopen (req )
469
479
rbody = response .read ()
480
+ rheader = response .info ()
470
481
rcode = response .code
471
482
except urllib2 .HTTPError , e :
472
483
rcode = e .code
484
+ rheader = None
473
485
rbody = e .read ()
474
486
except (urllib2 .URLError , ValueError ), e :
475
487
self .handle_urllib2_error (e , abs_url )
476
- return rbody , rcode
488
+ return { 'body' : rbody , 'headers' : rheader , 'code' : rcode }
477
489
478
490
def handle_urllib2_error (self , e , abs_url ):
479
491
msg = "Unexpected error communicating with Clever. If this problem persists, let us know at [email protected] ."
0 commit comments