diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 899de43a..6580d477 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: name: isort (python) args: ['--force-single-line-imports', '--profile', 'black'] - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 + rev: 7.1.1 hooks: - id: flake8 args: [ '--max-line-length', '100', '--max-doc-length', '120' ] diff --git a/CHANGES b/CHANGES index c1c617d7..24225161 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +0.x.y +----- + +* Consider the `Retry-After` header when handling retries + 0.25.8 ------ diff --git a/responses/__init__.py b/responses/__init__.py index 89cc9a5c..3f6e4743 100644 --- a/responses/__init__.py +++ b/responses/__init__.py @@ -39,7 +39,7 @@ try: from typing_extensions import Literal except ImportError: # pragma: no cover - from typing import Literal # type: ignore # pragma: no cover + from typing import Literal # pragma: no cover from io import BufferedReader from io import BytesIO @@ -1151,7 +1151,9 @@ def _on_request( # first validate that current request is eligible to be retried. # See ``urllib3.util.retry.Retry`` documentation. if retries.is_retry( - method=response.request.method, status_code=response.status_code # type: ignore[misc] + method=response.request.method, # type: ignore[misc] + status_code=response.status_code, # type: ignore[misc] + has_retry_after="Retry-After" in response.headers, # type: ignore[misc] ): try: retries = retries.increment( diff --git a/responses/tests/test_responses.py b/responses/tests/test_responses.py index 66711a51..b1754527 100644 --- a/responses/tests/test_responses.py +++ b/responses/tests/test_responses.py @@ -2717,6 +2717,51 @@ def run(): run() assert_reset() + def test_retry_with_retry_after_header(self): + """Validate that Retry-After header is detected and passed to retry logic""" + + @responses.activate(registry=registries.OrderedRegistry) + def run(): + url = "https://example.com" + # Add responses with Retry-After header + rsp1 = responses.get( + url, + body="Error", + status=429, + headers={"Retry-After": "1"}, + ) + rsp2 = responses.get( + url, + body="Error", + status=429, + headers={"Retry-After": "1"}, + ) + rsp3 = responses.get(url, body="OK", status=200) + + # Create session with retry configuration for 429 status + session = requests.Session() + adapter = requests.adapters.HTTPAdapter( + max_retries=Retry( + total=4, + backoff_factor=0.1, + status_forcelist=[429], + allowed_methods=["GET"], + raise_on_status=True, + respect_retry_after_header=True, + ) + ) + session.mount("https://", adapter) + + resp = session.get(url) + + assert resp.status_code == 200 + assert rsp1.call_count == 1 + assert rsp2.call_count == 1 + assert rsp3.call_count == 1 + + run() + assert_reset() + def test_request_object_attached_to_exception(): """Validate that we attach `request` object to custom exception supplied as body""" @@ -2729,7 +2774,7 @@ def run(): try: requests.get(url, timeout=1) except requests.ReadTimeout as exc: - assert type(exc.request) == requests.models.PreparedRequest + assert isinstance(exc.request, requests.models.PreparedRequest) run() assert_reset()