Skip to content

Commit

Permalink
Refactored API call exception detection and added helpful rate-limiti…
Browse files Browse the repository at this point in the history
…ng message
  • Loading branch information
mschwager committed Sep 24, 2016
1 parent 51efc60 commit 537afb2
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 28 deletions.
14 changes: 13 additions & 1 deletion lib/gitem/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,19 @@ def main():

ghapi = api.Api(args.oauth2_token)

dispatch[args.command](ghapi, **vars(args))
try:
dispatch[args.command](ghapi, **vars(args))
except api.ApiCallException as e:
if e.rate_limiting:
leftpad_print(
"Your API requests are being rate-limited. " +
"Please include an OAuth2 token and read the following:",
leftpad_length=0
)
leftpad_print(e.rate_limiting_url, leftpad_length=0)
else:
# Re-raise original exception
raise

if __name__ == "__main__":
main()
25 changes: 25 additions & 0 deletions lib/gitem/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,35 @@ class AuthenticationRequiredException(Exception):

class ApiCallException(Exception):

rate_limiting_url = 'https://developer.github.com/v3/#rate-limiting'

def __init__(self, code, message):
self.code = code
self.message = message

@property
def bad_request(self):
return self.code == requests.codes.BAD_REQUEST

@property
def unprocessable_entity(self):
return self.code == requests.codes.UNPROCESSABLE_ENTITY

@property
def forbidden(self):
return self.code == requests.codes.FORBIDDEN

@property
def unauthorized(self):
return self.code == requests.codes.UNAUTHORIZED

@property
def rate_limiting(self):
return (
self.forbidden and
self.message.get('documentation_url') == self.rate_limiting_url
)

def __str__(self):
return "{}: {}".format(self.code, json.dumps(self.message))

Expand Down
32 changes: 5 additions & 27 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,6 @@ class TestApi(unittest.TestCase):
def assertOk(self, status_code):
self.assertEqual(status_code, requests.codes.OK)

def assertBadRequest(self, status_code):
self.assertEqual(status_code, requests.codes.BAD_REQUEST)

def assertUnprocessableEntity(self, status_code):
self.assertEqual(status_code, requests.codes.UNPROCESSABLE_ENTITY)

def assertForbidden(self, status_code):
self.assertEqual(status_code, requests.codes.FORBIDDEN)

def assertUnauthorized(self, status_code):
self.assertEqual(status_code, requests.codes.UNAUTHORIZED)

@staticmethod
def api_will_return(json_return_value, status_code=requests.codes.OK, oauth2_token=None):
assert isinstance(json_return_value, dict)
Expand Down Expand Up @@ -96,9 +84,7 @@ def test_invalid_json(self):
with self.assertRaises(api.ApiCallException) as e:
mocked_api.get_public_organization("unused")

status_code = e.exception.code

self.assertBadRequest(status_code)
self.assertTrue(e.exception.bad_request)

def test_invalid_json_argument_type(self):
will_return = mocked_api_results.BAD_JSON_VALUES_RESULT
Expand All @@ -110,9 +96,7 @@ def test_invalid_json_argument_type(self):
with self.assertRaises(api.ApiCallException) as e:
mocked_api.get_public_organization("unused")

status_code = e.exception.code

self.assertBadRequest(status_code)
self.assertTrue(e.exception.bad_request)

def test_invalid_json_field(self):
will_return = mocked_api_results.INVALID_FIELDS_RESULT
Expand All @@ -124,9 +108,7 @@ def test_invalid_json_field(self):
with self.assertRaises(api.ApiCallException) as e:
mocked_api.get_public_organization("unused")

status_code = e.exception.code

self.assertUnprocessableEntity(status_code)
self.assertTrue(e.exception.unprocessable_entity)

def test_bad_credentials(self):
will_return = mocked_api_results.BAD_CREDENTIALS_RESULT
Expand All @@ -138,9 +120,7 @@ def test_bad_credentials(self):
with self.assertRaises(api.ApiCallException) as e:
mocked_api.get_public_organization("unused")

status_code = e.exception.code

self.assertUnauthorized(status_code)
self.assertTrue(e.exception.unauthorized)

def test_maximum_bad_credentials(self):
will_return = mocked_api_results.MAXIMUM_BAD_CREDENTIALS_RESULT
Expand All @@ -152,9 +132,7 @@ def test_maximum_bad_credentials(self):
with self.assertRaises(api.ApiCallException) as e:
mocked_api.get_public_organization("unused")

status_code = e.exception.code

self.assertForbidden(status_code)
self.assertTrue(e.exception.forbidden)

def test_authenticated_endpoint_ok(self):
will_return = mocked_api_results.STANDARD_API_RESULT
Expand Down

0 comments on commit 537afb2

Please sign in to comment.