From 89f433963ab57496bfca129303f6992145c74320 Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Thu, 15 Oct 2026 14:48:20 -0600 Subject: [PATCH] fix(session): log "Retrying" only when a retry follows A per-call max_retries=0 logged "Retrying... (1/0)" before raising, and the last of the default retries logged "(6/5)". --- tests/core/test_session_retry_log.py | 29 ++++++++++++++++++++++++++++ unshackle/core/session.py | 10 +++------- 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 tests/core/test_session_retry_log.py diff --git a/tests/core/test_session_retry_log.py b/tests/core/test_session_retry_log.py new file mode 100644 index 00000000..0b56cbf2 --- /dev/null +++ b/tests/core/test_session_retry_log.py @@ -0,0 +1,29 @@ +"""RnetSession.request logs "Retrying" only when a retry follows.""" + +import logging +from types import SimpleNamespace + +import pytest + +from unshackle.core import session as session_module +from unshackle.core.session import MaxRetriesError, RnetSession + + +@pytest.fixture +def rate_limited(monkeypatch): + s = RnetSession() + monkeypatch.setattr(s, "ensure_client", lambda: SimpleNamespace(request=lambda *a, **k: None)) + monkeypatch.setattr(s, "get_sleep_time", lambda *a: 0) + monkeypatch.setattr( + session_module, "RnetResponse", lambda raw: SimpleNamespace(status_code=429, reason="Too Many Requests") + ) + return s + + +@pytest.mark.parametrize("max_retries", [0, 2]) +def test_retry_log_counts_only_real_retries(rate_limited, caplog, max_retries): + with caplog.at_level(logging.WARNING), pytest.raises(MaxRetriesError): + rate_limited.get("https://example.invalid/x", max_retries=max_retries) + + retries = [r.getMessage() for r in caplog.records if "Retrying" in r.getMessage()] + assert retries == [f"429 Too Many Requests(/x). Retrying... ({n}/{max_retries})" for n in range(1, max_retries + 1)] diff --git a/unshackle/core/session.py b/unshackle/core/session.py index 8257e31d..c184ca71 100644 --- a/unshackle/core/session.py +++ b/unshackle/core/session.py @@ -835,19 +835,15 @@ class RnetSession: if response.status_code not in self.status_forcelist: return response last_exception = HTTPError(f"Received status code: {response.status_code}", response=response) - self.log.warning( - f"{response.status_code} {response.reason}({urlparse(url).path}). Retrying... " - f"({attempt + 1}/{max_retries})" - ) + failure = f"{response.status_code} {response.reason}" except self.catch_exceptions as e: last_exception = e response = None - self.log.warning( - f"{e.__class__.__name__}({urlparse(url).path}). Retrying... ({attempt + 1}/{max_retries})" - ) + failure = e.__class__.__name__ if attempt < max_retries: + self.log.warning(f"{failure}({urlparse(url).path}). Retrying... ({attempt + 1}/{max_retries})") if sleep_duration := self.get_sleep_time(response, attempt + 1): if sleep_duration > 0: time.sleep(sleep_duration)