From 8dd7fa36c9ff31d42249cd2c0983894e194b671c Mon Sep 17 00:00:00 2001 From: Alec Rosenbaum Date: Wed, 6 Apr 2022 16:17:34 -0400 Subject: [PATCH] Fix rate limiting backoff behavior (#113) --- .circleci/config.yml | 25 +++++++++++++++---------- closeio_api/__init__.py | 18 ++++++++++-------- requirements.txt | 6 +++--- setup.py | 8 ++++---- tests/test_api.py | 12 +++--------- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f40dcbb..fb252e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,9 +4,10 @@ workflows: version: 2 workflow: jobs: - - test-2.7 - - test-3.5 - - test-3.6 + - test-3.7 + - test-3.8 + - test-3.9 + - test-3.10 defaults: &defaults working_directory: ~/code @@ -14,21 +15,25 @@ defaults: &defaults - checkout - run: name: Install dependencies - command: sudo pip install -r requirements.txt + command: pip install -r requirements.txt - run: name: Test command: pytest jobs: - test-2.7: + test-3.7: <<: *defaults docker: - - image: circleci/python:2.7 - test-3.5: + - image: cimg/python:3.7 + test-3.8: <<: *defaults docker: - - image: circleci/python:3.5 - test-3.6: + - image: cimg/python:3.8 + test-3.9: <<: *defaults docker: - - image: circleci/python:3.6 + - image: cimg/python:3.9 + test-3.10: + <<: *defaults + docker: + - image: cimg/python:3.10 diff --git a/closeio_api/__init__.py b/closeio_api/__init__.py index 0f0d3ee..331df1c 100644 --- a/closeio_api/__init__.py +++ b/closeio_api/__init__.py @@ -1,3 +1,4 @@ +import contextlib import logging import time @@ -10,8 +11,8 @@ DEFAULT_RATE_LIMIT_DELAY = 2 # Seconds # To update the package version, change this variable. This variable is also -# read by setup.py when installing the package. -__version__ = '1.4' +# read by setup.py when installing the package. +__version__ = '2.0' class APIError(Exception): """Raised when sending a request to the API failed.""" @@ -145,12 +146,13 @@ def _get_rate_limit_sleep_time(self, response): """Get rate limit window expiration time from response if the response status code is 429. """ - try: - data = response.json() - return float(data['error']['rate_reset']) - except (AttributeError, KeyError, ValueError): - logging.exception('Error parsing rate limiting response') - return DEFAULT_RATE_LIMIT_DELAY + with contextlib.suppress(KeyError): + return float(response.headers["Retry-After"]) + with contextlib.suppress(KeyError): + return float(response.headers["RateLimit-Reset"]) + + logging.exception('Error parsing rate limiting response') + return DEFAULT_RATE_LIMIT_DELAY def _get_randomized_sleep_time_for_error(self, status_code, retries): """Get sleep time for a given status code before we can try the diff --git a/requirements.txt b/requirements.txt index 7421d67..9f52865 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -pytest==3.0.7 -requests==2.21.0 -responses==0.5.1 +pytest==7.1.1 +requests==2.27.1 +responses==0.20.0 diff --git a/setup.py b/setup.py index ce71eb4..038a0f6 100644 --- a/setup.py +++ b/setup.py @@ -19,11 +19,11 @@ ], classifiers=[ "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Operating System :: OS Independent", ] ) diff --git a/tests/test_api.py b/tests/test_api.py index 8f7d117..ed74103 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -114,16 +114,10 @@ def test_retry_on_rate_limit(api_client): rsps.add( responses.GET, 'https://api.close.com/api/v1/lead/lead_abcdefghijklmnop/', - body=json.dumps({ - "error": { - "rate_reset": 1, - "message": "API call count exceeded for this 15 second window", - "rate_limit": 600, - "rate_window": 15 - } - }), + body=json.dumps({}), status=429, - content_type='application/json' + content_type='application/json', + headers={"Retry-After": "1", "RateLimit-Reset": "1"} ) # Respond correctly to the second request.