diff --git a/docs/HISTORY.rst b/docs/HISTORY.rst index ecfd130..26e44d7 100644 --- a/docs/HISTORY.rst +++ b/docs/HISTORY.rst @@ -3,6 +3,11 @@ History ======= +0.4.6 (2015/10/12) +------------------ + +- Added rate limiting to the client + 0.4.5 (2015/10/01) ------------------ diff --git a/duedil/api.py b/duedil/api.py index e0cf657..2461624 100644 --- a/duedil/api.py +++ b/duedil/api.py @@ -29,6 +29,8 @@ import requests from requests.exceptions import HTTPError +from retrying import retry + try: # pragma: no cover long except NameError: # pragma: no cover @@ -49,6 +51,17 @@ API_KEY = os.environ.get('DUEDIL_API_KEY', None) +def retry_throttling(exception): + if isinstance(exception, HTTPError): + if exception.response.status_code == 403: + if exception.response.reason == "Forbidden - Over rate limit": + if 'Developer Over Qps' in exception.response.text: + return True + elif 'Developer Over Rate' in exception.response.text: + return True + return False + + class Client(object): cache = None base_url = None @@ -77,6 +90,7 @@ def set_api(self, api_key=None, sandbox=False): def get(self, endpoint, data=None): return self._get(endpoint, data) + @retry(retry_on_exception=retry_throttling, wait_exponential_multiplier=1000, wait_exponential_max=10000) def _get(self, endpoint, data=None): 'this should become the private interface to all reequests to the api' @@ -89,8 +103,6 @@ def _get(self, endpoint, data=None): else: resp_format = '' - - url = "{base_url}/{endpoint}{format}" prepared_url = url.format(base_url=self.base_url, endpoint=endpoint, diff --git a/setup.py b/setup.py index 2760717..2c741f3 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def run_tests(self): sys.exit(errno) -version = '0.4.5' +version = '0.4.6' setup(name='duedil', version=version, @@ -54,7 +54,8 @@ def run_tests(self): install_requires=[ # -*- Extra requirements: -*- 'requests>=2,<3', - 'six==1.9.0' + 'six==1.9.0', + 'retrying' ], tests_require=['pytest', 'requests_mock'], cmdclass = {'test': PyTest}, diff --git a/tests/test_client.py b/tests/test_client.py index 6e0e53b..006fb1e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -110,6 +110,31 @@ def test_other_http_error(self, m): response = client.get('12345') self.assertIsNone(response) + @requests_mock.mock() + def test_throttling_qps(self, m): + client = TestClient(API_KEY) + url = 'http://duedil.io/v3/12345.json' + m.register_uri('GET', (url + '?api_key=' + API_KEY), [ + {'status_code': 403, 'reason': "Forbidden - Over rate limit", "text": "

Developer Over Qps

"}, + {'status_code': 200, 'json': {'name': 'Duedil', 'id': '12345'}}, + ]) + data = {'name': 'Duedil', 'id': '12345'} + response = client.get('12345') + self.assertEqual(response, {'name': 'Duedil', 'id': '12345'}) + + @requests_mock.mock() + def test_throttling_quota(self, m): + client = TestClient(API_KEY) + url = 'http://duedil.io/v3/12345.json' + m.register_uri('GET', (url + '?api_key=' + API_KEY), [ + {'status_code': 403, 'reason': "Forbidden - Over rate limit", "text": "

Developer Over Rate

"}, + {'status_code': 200, 'json': {'name': 'Duedil', 'id': '12345'}}, + ]) + data = {'name': 'Duedil', 'id': '12345'} + response = client.get('12345') + self.assertEqual(response, {'name': 'Duedil', 'id': '12345'}) + + def test_incorrect_query_params(self): with self.assertRaises(ValueError): TestClient(API_KEY)._build_search_string(q='searchthing')