From 8e4a7d61c2e7f4ad6ae41eb5783dccb4387ab3b6 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 10 Nov 2026 22:13:04 +0300 Subject: [PATCH] Fix type checking with ty 0.0.100 Refs #203 --- .../middleware/resilience/circuit_breaker.py | 17 ++++---- tests/test_bulkhead.py | 8 ++-- tests/test_circuit_breaker.py | 40 +++++++++---------- tests/test_circuit_breaker_sync.py | 32 +++++++-------- tests/test_keyed_circuit_breaker.py | 8 ++-- tests/test_keyed_circuit_breaker_sync.py | 8 ++-- tests/test_observability.py | 12 +++--- tests/test_optional_extras_otel_missing.py | 2 +- tests/test_retry.py | 40 +++++++++---------- tests/test_retry_sync.py | 24 +++++------ tests/test_timeout.py | 8 ++-- 11 files changed, 100 insertions(+), 99 deletions(-) diff --git a/src/httpware/middleware/resilience/circuit_breaker.py b/src/httpware/middleware/resilience/circuit_breaker.py index 4e589e3..04e14fe 100644 --- a/src/httpware/middleware/resilience/circuit_breaker.py +++ b/src/httpware/middleware/resilience/circuit_breaker.py @@ -324,8 +324,9 @@ def _reject(self, request: httpx2.Request, now: float, retry_after: float | None raise _RejectionError(retry_after, self.take_events()) def _record_closed(self, request: httpx2.Request, *, failed: bool) -> None: - if self._rate_mode: - self._record_outcome(request, failed=failed) + window = self._window + if window is not None: + self._record_outcome(request, window, failed=failed) elif failed: self._consecutive_failures += 1 if self._consecutive_failures >= self._failure_threshold: @@ -337,8 +338,9 @@ def _record_probe_success(self, request: httpx2.Request) -> None: self._move_to(CircuitState.CLOSED) self._consecutive_failures = 0 self._consecutive_successes = 0 - if self._rate_mode: - self._window.clear() # ty: ignore[unresolved-attribute] + window = self._window + if window is not None: + window.clear() attributes = {"rejected": self._episode_rejections} self._queue(request, "circuit.closed", logging.INFO, "circuit closed — service recovered", attributes) @@ -377,11 +379,10 @@ def _open_rate(self, request: httpx2.Request, *, total: int, failures: int) -> N }, ) - def _record_outcome(self, request: httpx2.Request, *, failed: bool) -> None: - # Only reached in rate mode, where _window and _failure_rate_threshold are non-None. + def _record_outcome(self, request: httpx2.Request, window: _RollingWindow, *, failed: bool) -> None: now = self._now() - self._window.record(now, failed=failed) # ty: ignore[unresolved-attribute] - total, failures = self._window.totals(now) # ty: ignore[unresolved-attribute] + window.record(now, failed=failed) + total, failures = window.totals(now) threshold = self._failure_rate_threshold if threshold is not None and total >= self._minimum_calls and failures / total >= threshold: self._open_rate(request, total=total, failures=failures) diff --git a/tests/test_bulkhead.py b/tests/test_bulkhead.py index ac4c13c..6fdb525 100644 --- a/tests/test_bulkhead.py +++ b/tests/test_bulkhead.py @@ -482,10 +482,10 @@ async def test_bulkhead_rejected_emits_observability_event(caplog: pytest.LogCap assert len(rejected_records) == 1 record = rejected_records[0] assert record.levelno == logging.WARNING - assert record.max_concurrent == 1 # ty: ignore[unresolved-attribute] - assert record.acquire_timeout == 0.0 # ty: ignore[unresolved-attribute] - assert record.method == "GET" # ty: ignore[unresolved-attribute] - assert "example.test/y" in record.url # ty: ignore[unresolved-attribute] + assert record.max_concurrent == 1 # ty: ignore[missing-attribute] + assert record.acquire_timeout == 0.0 # ty: ignore[missing-attribute] + assert record.method == "GET" # ty: ignore[missing-attribute] + assert "example.test/y" in record.url # ty: ignore[missing-attribute] # ───── Single-event-loop guard ────────────────────────────────────────────── diff --git a/tests/test_circuit_breaker.py b/tests/test_circuit_breaker.py index b4c9b02..4d13f00 100644 --- a/tests/test_circuit_breaker.py +++ b/tests/test_circuit_breaker.py @@ -133,13 +133,13 @@ async def test_open_emits_opened_event_and_rejects(caplog: pytest.LogCaptureFixt opened = [r for r in records if "opened" in r.message] rejected = [r for r in records if "rejecting" in r.message] assert len(opened) == 1 - assert opened[0].event == "circuit.opened" # ty: ignore[unresolved-attribute] - assert opened[0].failure_threshold == 2 # noqa: PLR2004 # ty: ignore[unresolved-attribute] - assert opened[0].failures == 2 # noqa: PLR2004 # ty: ignore[unresolved-attribute] + assert opened[0].event == "circuit.opened" # ty: ignore[missing-attribute] + assert opened[0].failure_threshold == 2 # noqa: PLR2004 # ty: ignore[missing-attribute] + assert opened[0].failures == 2 # noqa: PLR2004 # ty: ignore[missing-attribute] assert len(rejected) == 1 - assert rejected[0].event == "circuit.rejected" # ty: ignore[unresolved-attribute] - assert rejected[0].retry_after is not None # ty: ignore[unresolved-attribute] - assert rejected[0].method == "GET" # ty: ignore[unresolved-attribute] + assert rejected[0].event == "circuit.rejected" # ty: ignore[missing-attribute] + assert rejected[0].retry_after is not None # ty: ignore[missing-attribute] + assert rejected[0].method == "GET" # ty: ignore[missing-attribute] async def test_success_resets_failure_streak() -> None: @@ -342,8 +342,8 @@ async def test_probe_failure_reopens_circuit(caplog: pytest.LogCaptureFixture) - r for r in caplog.records if r.name == "httpware.circuit_breaker" - and r.event == "circuit.opened" # ty: ignore[unresolved-attribute] - and r.failures == 1 # ty: ignore[unresolved-attribute] + and r.event == "circuit.opened" # ty: ignore[missing-attribute] + and r.failures == 1 # ty: ignore[missing-attribute] ] assert len(reopen_records) == 1 @@ -664,11 +664,11 @@ async def test_rate_mode_open_event_carries_rate_attributes(caplog: pytest.LogCa await client.get("https://example.test/x") for _ in range(2): await client.get("https://example.test/x") - opened = [r for r in caplog.records if r.event == "circuit.opened"] # ty: ignore[unresolved-attribute] + opened = [r for r in caplog.records if r.event == "circuit.opened"] # ty: ignore[missing-attribute] assert opened, "expected a circuit.opened record" rec = opened[-1] - assert rec.failure_rate_threshold == 0.5 # noqa: PLR2004 # ty: ignore[unresolved-attribute] - assert rec.observed_calls >= 4 # noqa: PLR2004 # ty: ignore[unresolved-attribute] + assert rec.failure_rate_threshold == 0.5 # noqa: PLR2004 # ty: ignore[missing-attribute] + assert rec.observed_calls >= 4 # noqa: PLR2004 # ty: ignore[missing-attribute] assert hasattr(rec, "failure_rate") assert not hasattr(rec, "failure_threshold") # classic attribute absent in rate mode @@ -732,10 +732,10 @@ async def test_rate_mode_probe_failure_reopens_with_classic_attributes(caplog: p with pytest.raises(InternalServerError): await probe.get("https://example.test/x") assert breaker.state is CircuitState.OPEN # the failed probe re-opened it - opened = [r for r in caplog.records if r.event == "circuit.opened"] # ty: ignore[unresolved-attribute] + opened = [r for r in caplog.records if r.event == "circuit.opened"] # ty: ignore[missing-attribute] assert opened rec = opened[-1] - assert rec.failures == 1 # ty: ignore[unresolved-attribute] # classic probe-failure shape + assert rec.failures == 1 # ty: ignore[missing-attribute] # classic probe-failure shape assert hasattr(rec, "failure_threshold") assert not hasattr(rec, "failure_rate") # NOT the rate shape @@ -929,8 +929,8 @@ async def _unreachable(request: httpx2.Request) -> httpx2.Response: # pragma: n clock.advance(step) with pytest.raises(CircuitOpenError): await breaker(httpx2.Request("GET", "https://example.test/x"), _unreachable) - rejected = [r for r in caplog.records if r.event == "circuit.rejected"] # ty: ignore[unresolved-attribute] - assert [r.suppressed for r in rejected] == [0, 4] # ty: ignore[unresolved-attribute] + rejected = [r for r in caplog.records if r.event == "circuit.rejected"] # ty: ignore[missing-attribute] + assert [r.suppressed for r in rejected] == [0, 4] # ty: ignore[missing-attribute] # ── rejection accounting runs per outage episode ── @@ -962,7 +962,7 @@ async def test_first_rejection_of_a_new_outage_is_emitted(caplog: pytest.LogCapt await breaker(httpx2.Request("GET", "https://example.test/x"), _ok) await _open_now(breaker) await _reject(breaker) - assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[unresolved-attribute] + assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[missing-attribute] async def test_closed_event_reports_every_rejection_of_the_outage(caplog: pytest.LogCaptureFixture) -> None: @@ -978,8 +978,8 @@ async def test_closed_event_reports_every_rejection_of_the_outage(caplog: pytest await _reject(breaker) clock.advance(5.0) await breaker(httpx2.Request("GET", "https://example.test/x"), _ok) - assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 1] # ty: ignore[unresolved-attribute] - assert [r.rejected for r in _events(caplog, "circuit.closed")] == [4] # ty: ignore[unresolved-attribute] + assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 1] # ty: ignore[missing-attribute] + assert [r.rejected for r in _events(caplog, "circuit.closed")] == [4] # ty: ignore[missing-attribute] async def test_suppressed_never_counts_a_previous_outage(caplog: pytest.LogCaptureFixture) -> None: @@ -993,7 +993,7 @@ async def test_suppressed_never_counts_a_previous_outage(caplog: pytest.LogCaptu await breaker(httpx2.Request("GET", "https://example.test/x"), _ok) await _open_now(breaker) await _reject(breaker) - assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[unresolved-attribute] + assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[missing-attribute] async def test_events_of_one_call_arrive_in_transition_order(caplog: pytest.LogCaptureFixture) -> None: @@ -1004,7 +1004,7 @@ async def test_events_of_one_call_arrive_in_transition_order(caplog: pytest.LogC await _reject(breaker) clock.advance(1.0) await breaker(httpx2.Request("GET", "https://example.test/x"), _ok) - assert [r.event for r in caplog.records] == [ # ty: ignore[unresolved-attribute] + assert [r.event for r in caplog.records] == [ # ty: ignore[missing-attribute] "circuit.opened", "circuit.rejected", "circuit.half_open", diff --git a/tests/test_circuit_breaker_sync.py b/tests/test_circuit_breaker_sync.py index 524099e..f7dc2ed 100644 --- a/tests/test_circuit_breaker_sync.py +++ b/tests/test_circuit_breaker_sync.py @@ -98,13 +98,13 @@ def test_open_emits_opened_event_and_rejects(caplog: pytest.LogCaptureFixture) - opened = [r for r in records if "opened" in r.message] rejected = [r for r in records if "rejecting" in r.message] assert len(opened) == 1 - assert opened[0].event == "circuit.opened" # ty: ignore[unresolved-attribute] - assert opened[0].failure_threshold == 2 # noqa: PLR2004 # ty: ignore[unresolved-attribute] - assert opened[0].failures == 2 # noqa: PLR2004 # ty: ignore[unresolved-attribute] + assert opened[0].event == "circuit.opened" # ty: ignore[missing-attribute] + assert opened[0].failure_threshold == 2 # noqa: PLR2004 # ty: ignore[missing-attribute] + assert opened[0].failures == 2 # noqa: PLR2004 # ty: ignore[missing-attribute] assert len(rejected) == 1 - assert rejected[0].event == "circuit.rejected" # ty: ignore[unresolved-attribute] - assert rejected[0].retry_after is not None # ty: ignore[unresolved-attribute] - assert rejected[0].method == "GET" # ty: ignore[unresolved-attribute] + assert rejected[0].event == "circuit.rejected" # ty: ignore[missing-attribute] + assert rejected[0].retry_after is not None # ty: ignore[missing-attribute] + assert rejected[0].method == "GET" # ty: ignore[missing-attribute] def test_success_resets_failure_streak() -> None: @@ -247,8 +247,8 @@ def test_probe_failure_reopens_circuit(caplog: pytest.LogCaptureFixture) -> None r for r in caplog.records if r.name == "httpware.circuit_breaker" - and r.event == "circuit.opened" # ty: ignore[unresolved-attribute] - and r.failures == 1 # ty: ignore[unresolved-attribute] + and r.event == "circuit.opened" # ty: ignore[missing-attribute] + and r.failures == 1 # ty: ignore[missing-attribute] ] assert len(reopen_records) == 1 @@ -587,10 +587,10 @@ def test_rate_mode_probe_failure_reopens_with_classic_attributes(caplog: pytest. with pytest.raises(InternalServerError): probe.get("https://example.test/x") assert breaker.state is CircuitState.OPEN # the failed probe re-opened it - opened = [r for r in caplog.records if r.event == "circuit.opened"] # ty: ignore[unresolved-attribute] + opened = [r for r in caplog.records if r.event == "circuit.opened"] # ty: ignore[missing-attribute] assert opened rec = opened[-1] - assert rec.failures == 1 # ty: ignore[unresolved-attribute] # classic probe-failure shape + assert rec.failures == 1 # ty: ignore[missing-attribute] # classic probe-failure shape assert hasattr(rec, "failure_threshold") assert not hasattr(rec, "failure_rate") # NOT the rate shape @@ -797,8 +797,8 @@ def test_rejected_event_is_emitted_at_most_once_per_second(caplog: pytest.LogCap clock.advance(step) with pytest.raises(CircuitOpenError): breaker(httpx2.Request("GET", "https://example.test/x"), _unreachable) - rejected = [r for r in caplog.records if r.event == "circuit.rejected"] # ty: ignore[unresolved-attribute] - assert [r.suppressed for r in rejected] == [0, 4] # ty: ignore[unresolved-attribute] + rejected = [r for r in caplog.records if r.event == "circuit.rejected"] # ty: ignore[missing-attribute] + assert [r.suppressed for r in rejected] == [0, 4] # ty: ignore[missing-attribute] class _LockProbe(logging.Handler): @@ -946,7 +946,7 @@ def test_first_rejection_of_a_new_outage_is_emitted(caplog: pytest.LogCaptureFix breaker(httpx2.Request("GET", "https://example.test/x"), _ok) _open_now(breaker) _reject(breaker) - assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[unresolved-attribute] + assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[missing-attribute] def test_closed_event_reports_every_rejection_of_the_outage(caplog: pytest.LogCaptureFixture) -> None: @@ -962,8 +962,8 @@ def test_closed_event_reports_every_rejection_of_the_outage(caplog: pytest.LogCa _reject(breaker) clock.advance(5.0) breaker(httpx2.Request("GET", "https://example.test/x"), _ok) - assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 1] # ty: ignore[unresolved-attribute] - assert [r.rejected for r in _events(caplog, "circuit.closed")] == [4] # ty: ignore[unresolved-attribute] + assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 1] # ty: ignore[missing-attribute] + assert [r.rejected for r in _events(caplog, "circuit.closed")] == [4] # ty: ignore[missing-attribute] def test_suppressed_never_counts_a_previous_outage(caplog: pytest.LogCaptureFixture) -> None: @@ -977,7 +977,7 @@ def test_suppressed_never_counts_a_previous_outage(caplog: pytest.LogCaptureFixt breaker(httpx2.Request("GET", "https://example.test/x"), _ok) _open_now(breaker) _reject(breaker) - assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[unresolved-attribute] + assert [r.suppressed for r in _events(caplog, "circuit.rejected")] == [0, 0] # ty: ignore[missing-attribute] def test_locked_admission_into_a_circuit_closed_since_the_unlocked_read_gets_the_closed_ticket() -> None: diff --git a/tests/test_keyed_circuit_breaker.py b/tests/test_keyed_circuit_breaker.py index 0ac2dfd..486a846 100644 --- a/tests/test_keyed_circuit_breaker.py +++ b/tests/test_keyed_circuit_breaker.py @@ -191,8 +191,8 @@ async def test_events_carry_the_circuit_key(caplog: pytest.LogCaptureFixture) -> with pytest.raises(CircuitOpenError): await client.get("https://a.test/x") records = [r for r in caplog.records if r.name == "httpware.circuit_breaker"] - assert [r.event for r in records] == ["circuit.opened", "circuit.rejected"] # ty: ignore[unresolved-attribute] - assert all(r.circuit_key == "https://a.test" for r in records) # ty: ignore[unresolved-attribute] + assert [r.event for r in records] == ["circuit.opened", "circuit.rejected"] # ty: ignore[missing-attribute] + assert all(r.circuit_key == "https://a.test" for r in records) # ty: ignore[missing-attribute] async def test_circuit_key_is_the_normalized_origin_without_userinfo(caplog: pytest.LogCaptureFixture) -> None: @@ -201,7 +201,7 @@ async def test_circuit_key_is_the_normalized_origin_without_userinfo(caplog: pyt with caplog.at_level(logging.WARNING, logger="httpware.circuit_breaker"): await _fail_n(client, "https://user:secret@A.test:443/x?q=1", 1) await _fail_n(client, "http://a.test:8080/x", 1) - keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[unresolved-attribute] + keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[missing-attribute] assert keys == ["https://a.test", "http://a.test:8080"] @@ -211,7 +211,7 @@ async def test_circuit_key_of_a_custom_key_is_its_str(caplog: pytest.LogCaptureF async with _client(handler, breaker=breaker) as client: with caplog.at_level(logging.WARNING, logger="httpware.circuit_breaker"): await _fail_n(client, "https://a.test/x", 1) - keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[unresolved-attribute] + keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[missing-attribute] assert keys == ["('a.test', 1)"] diff --git a/tests/test_keyed_circuit_breaker_sync.py b/tests/test_keyed_circuit_breaker_sync.py index 150f747..6835634 100644 --- a/tests/test_keyed_circuit_breaker_sync.py +++ b/tests/test_keyed_circuit_breaker_sync.py @@ -190,8 +190,8 @@ def test_events_carry_the_circuit_key(caplog: pytest.LogCaptureFixture) -> None: with pytest.raises(CircuitOpenError): client.get("https://a.test/x") records = [r for r in caplog.records if r.name == "httpware.circuit_breaker"] - assert [r.event for r in records] == ["circuit.opened", "circuit.rejected"] # ty: ignore[unresolved-attribute] - assert all(r.circuit_key == "https://a.test" for r in records) # ty: ignore[unresolved-attribute] + assert [r.event for r in records] == ["circuit.opened", "circuit.rejected"] # ty: ignore[missing-attribute] + assert all(r.circuit_key == "https://a.test" for r in records) # ty: ignore[missing-attribute] def test_circuit_key_is_the_normalized_origin_without_userinfo(caplog: pytest.LogCaptureFixture) -> None: @@ -202,7 +202,7 @@ def test_circuit_key_is_the_normalized_origin_without_userinfo(caplog: pytest.Lo ): _fail_n(client, "https://user:secret@A.test:443/x?q=1", 1) _fail_n(client, "http://a.test:8080/x", 1) - keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[unresolved-attribute] + keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[missing-attribute] assert keys == ["https://a.test", "http://a.test:8080"] @@ -214,7 +214,7 @@ def test_circuit_key_of_a_custom_key_is_its_str(caplog: pytest.LogCaptureFixture caplog.at_level(logging.WARNING, logger="httpware.circuit_breaker"), ): _fail_n(client, "https://a.test/x", 1) - keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[unresolved-attribute] + keys = [r.circuit_key for r in caplog.records if r.name == "httpware.circuit_breaker"] # ty: ignore[missing-attribute] assert keys == ["('a.test', 1)"] diff --git a/tests/test_observability.py b/tests/test_observability.py index 4e00044..a29d11f 100644 --- a/tests/test_observability.py +++ b/tests/test_observability.py @@ -27,9 +27,9 @@ def test_emit_event_logs_at_warning_with_extra_fields(caplog: pytest.LogCaptureF record = caplog.records[0] assert record.levelno == logging.WARNING assert record.message == "something interesting happened" - assert record.foo == 1 # ty: ignore[unresolved-attribute] - assert record.bar == "x" # ty: ignore[unresolved-attribute] - assert record.event == "test.event" # ty: ignore[unresolved-attribute] + assert record.foo == 1 # ty: ignore[missing-attribute] + assert record.bar == "x" # ty: ignore[missing-attribute] + assert record.event == "test.event" # ty: ignore[missing-attribute] def test_emit_event_redacts_url_secret_in_log_record(caplog: pytest.LogCaptureFixture) -> None: @@ -44,7 +44,7 @@ def test_emit_event_redacts_url_secret_in_log_record(caplog: pytest.LogCaptureFi ) record = caplog.records[0] - assert record.url == "https://example.test/x?api_key=REDACTED&X-Amz-Signature=REDACTED" # ty: ignore[unresolved-attribute] + assert record.url == "https://example.test/x?api_key=REDACTED&X-Amz-Signature=REDACTED" # ty: ignore[missing-attribute] def test_emit_event_redacts_url_secret_in_otel_event() -> None: @@ -138,7 +138,7 @@ def test_emit_event_works_when_otel_installed_but_no_active_span( record = caplog.records[0] assert record.levelno == logging.WARNING assert record.message == "real-otel-but-no-tracer" - assert record.event == "test.event" # ty: ignore[unresolved-attribute] + assert record.event == "test.event" # ty: ignore[missing-attribute] def test_emit_event_swallows_add_event_failure() -> None: @@ -180,4 +180,4 @@ def test_emit_event_drops_none_attributes_from_otel_event_only(caplog: pytest.Lo "test.event", attributes={"url": "https://example.test/x?token=REDACTED", "attempts": 3}, ) - assert caplog.records[0].last_status is None # ty: ignore[unresolved-attribute] + assert caplog.records[0].last_status is None # ty: ignore[missing-attribute] diff --git a/tests/test_optional_extras_otel_missing.py b/tests/test_optional_extras_otel_missing.py index d19699a..7b40aac 100644 --- a/tests/test_optional_extras_otel_missing.py +++ b/tests/test_optional_extras_otel_missing.py @@ -42,7 +42,7 @@ def test_emit_event_logs_record_without_otel(caplog: pytest.LogCaptureFixture) - assert len(caplog.records) == 1 record = caplog.records[0] assert record.message == "works without otel" - assert record.x == 1 # ty: ignore[unresolved-attribute] + assert record.x == 1 # ty: ignore[missing-attribute] def test_emit_event_does_not_call_opentelemetry_apis_when_flag_false() -> None: diff --git a/tests/test_retry.py b/tests/test_retry.py index 97db121..415db15 100644 --- a/tests/test_retry.py +++ b/tests/test_retry.py @@ -643,11 +643,11 @@ async def test_retry_giving_up_emits_observability_event(caplog: pytest.LogCaptu assert len(giving_up_records) == 1 record = giving_up_records[0] assert record.levelno == logging.WARNING - assert record.attempts == 3 # noqa: PLR2004 — 3 matches max_attempts=3 literal above # ty: ignore[unresolved-attribute] - assert record.method == "GET" # ty: ignore[unresolved-attribute] - assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[unresolved-attribute] - assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[unresolved-attribute] - assert record.reason == "max_attempts" # ty: ignore[unresolved-attribute] + assert record.attempts == 3 # noqa: PLR2004 — 3 matches max_attempts=3 literal above # ty: ignore[missing-attribute] + assert record.method == "GET" # ty: ignore[missing-attribute] + assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[missing-attribute] + assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[missing-attribute] + assert record.reason == "max_attempts" # ty: ignore[missing-attribute] async def test_retry_after_exceeding_max_delay_emits_giving_up_event(caplog: pytest.LogCaptureFixture) -> None: @@ -658,17 +658,17 @@ async def test_retry_after_exceeding_max_delay_emits_giving_up_event(caplog: pyt with caplog.at_level(logging.WARNING, logger="httpware.retry"), pytest.raises(ServiceUnavailableError): await client.get("https://example.test/x") - records = [r for r in caplog.records if r.name == "httpware.retry" and r.event == "retry.giving_up"] # ty: ignore[unresolved-attribute] + records = [r for r in caplog.records if r.name == "httpware.retry" and r.event == "retry.giving_up"] # ty: ignore[missing-attribute] assert len(records) == 1 record = records[0] assert record.levelno == logging.WARNING - assert record.reason == "retry_after_exceeds_max_delay" # ty: ignore[unresolved-attribute] - assert record.attempts == 1 # ty: ignore[unresolved-attribute] - assert record.method == "GET" # ty: ignore[unresolved-attribute] - assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[unresolved-attribute] - assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[unresolved-attribute] - assert record.retry_after == 9999.0 # noqa: PLR2004 — matches the header above # ty: ignore[unresolved-attribute] - assert record.max_delay == 2.5 # noqa: PLR2004 — matches max_delay above # ty: ignore[unresolved-attribute] + assert record.reason == "retry_after_exceeds_max_delay" # ty: ignore[missing-attribute] + assert record.attempts == 1 # ty: ignore[missing-attribute] + assert record.method == "GET" # ty: ignore[missing-attribute] + assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[missing-attribute] + assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[missing-attribute] + assert record.retry_after == 9999.0 # noqa: PLR2004 — matches the header above # ty: ignore[missing-attribute] + assert record.max_delay == 2.5 # noqa: PLR2004 — matches max_delay above # ty: ignore[missing-attribute] async def test_retry_budget_refused_emits_observability_event(caplog: pytest.LogCaptureFixture) -> None: @@ -688,9 +688,9 @@ async def test_retry_budget_refused_emits_observability_event(caplog: pytest.Log budget_records = [r for r in retry_records if "budget" in r.message] assert len(budget_records) == 1 record = budget_records[0] - assert record.attempts == 1 # ty: ignore[unresolved-attribute] - assert record.method == "GET" # ty: ignore[unresolved-attribute] - assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[unresolved-attribute] + assert record.attempts == 1 # ty: ignore[missing-attribute] + assert record.method == "GET" # ty: ignore[missing-attribute] + assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[missing-attribute] async def test_retry_streaming_refused_emits_observability_event(caplog: pytest.LogCaptureFixture) -> None: @@ -713,8 +713,8 @@ async def streamed_body() -> typing.AsyncIterator[bytes]: streaming_records = [r for r in retry_records if "stream" in r.message] assert len(streaming_records) == 1 record = streaming_records[0] - assert record.method == "PUT" # ty: ignore[unresolved-attribute] - assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[unresolved-attribute] + assert record.method == "PUT" # ty: ignore[missing-attribute] + assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[missing-attribute] class _CountingBudget(RetryBudget): @@ -755,8 +755,8 @@ async def test_retry_event_url_attribute_masks_query_secret(caplog: pytest.LogCa giving_up = [r for r in caplog.records if r.name == "httpware.retry" and r.message.startswith("retry gave up")] assert len(giving_up) == 1 - assert "topsecret" not in giving_up[0].url # ty: ignore[unresolved-attribute] - assert "api_key=REDACTED" in giving_up[0].url # ty: ignore[unresolved-attribute] + assert "topsecret" not in giving_up[0].url # ty: ignore[missing-attribute] + assert "api_key=REDACTED" in giving_up[0].url # ty: ignore[missing-attribute] async def test_retry_after_exceeds_max_delay_does_not_consume_budget_token() -> None: diff --git a/tests/test_retry_sync.py b/tests/test_retry_sync.py index a229a02..4fc2d96 100644 --- a/tests/test_retry_sync.py +++ b/tests/test_retry_sync.py @@ -488,9 +488,9 @@ def test_emits_giving_up_log_event(caplog: pytest.LogCaptureFixture) -> None: client = _client(handler, retry=Retry(_sleep=sleeper, base_delay=0.01, max_attempts=2)) with caplog.at_level(logging.WARNING, logger="httpware.retry"), pytest.raises(ServiceUnavailableError): client.get("https://example.test/x") - giving_up = [r for r in caplog.records if r.name == "httpware.retry" and r.event == "retry.giving_up"] # ty: ignore[unresolved-attribute] + giving_up = [r for r in caplog.records if r.name == "httpware.retry" and r.event == "retry.giving_up"] # ty: ignore[missing-attribute] assert len(giving_up) == 1 - assert giving_up[0].reason == "max_attempts" # ty: ignore[unresolved-attribute] + assert giving_up[0].reason == "max_attempts" # ty: ignore[missing-attribute] def test_retry_after_exceeding_max_delay_emits_giving_up_event(caplog: pytest.LogCaptureFixture) -> None: @@ -501,17 +501,17 @@ def test_retry_after_exceeding_max_delay_emits_giving_up_event(caplog: pytest.Lo with caplog.at_level(logging.WARNING, logger="httpware.retry"), pytest.raises(ServiceUnavailableError): client.get("https://example.test/x") - records = [r for r in caplog.records if r.name == "httpware.retry" and r.event == "retry.giving_up"] # ty: ignore[unresolved-attribute] + records = [r for r in caplog.records if r.name == "httpware.retry" and r.event == "retry.giving_up"] # ty: ignore[missing-attribute] assert len(records) == 1 record = records[0] assert record.levelno == logging.WARNING - assert record.reason == "retry_after_exceeds_max_delay" # ty: ignore[unresolved-attribute] - assert record.attempts == 1 # ty: ignore[unresolved-attribute] - assert record.method == "GET" # ty: ignore[unresolved-attribute] - assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[unresolved-attribute] - assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[unresolved-attribute] - assert record.retry_after == 9999.0 # noqa: PLR2004 — matches the header above # ty: ignore[unresolved-attribute] - assert record.max_delay == 2.5 # noqa: PLR2004 — matches max_delay above # ty: ignore[unresolved-attribute] + assert record.reason == "retry_after_exceeds_max_delay" # ty: ignore[missing-attribute] + assert record.attempts == 1 # ty: ignore[missing-attribute] + assert record.method == "GET" # ty: ignore[missing-attribute] + assert record.last_status == HTTPStatus.SERVICE_UNAVAILABLE # ty: ignore[missing-attribute] + assert record.last_exception_type == "ServiceUnavailableError" # ty: ignore[missing-attribute] + assert record.retry_after == 9999.0 # noqa: PLR2004 — matches the header above # ty: ignore[missing-attribute] + assert record.max_delay == 2.5 # noqa: PLR2004 — matches max_delay above # ty: ignore[missing-attribute] def test_emits_budget_refused_log_event(caplog: pytest.LogCaptureFixture) -> None: @@ -604,8 +604,8 @@ def test_retry_event_url_attribute_masks_query_secret_sync(caplog: pytest.LogCap giving_up = [r for r in caplog.records if r.name == "httpware.retry" and r.message.startswith("retry gave up")] assert len(giving_up) == 1 - assert "topsecret" not in giving_up[0].url # ty: ignore[unresolved-attribute] - assert "api_key=REDACTED" in giving_up[0].url # ty: ignore[unresolved-attribute] + assert "topsecret" not in giving_up[0].url # ty: ignore[missing-attribute] + assert "api_key=REDACTED" in giving_up[0].url # ty: ignore[missing-attribute] def test_retry_after_exceeds_max_delay_does_not_consume_budget_token_sync() -> None: diff --git a/tests/test_timeout.py b/tests/test_timeout.py index 04a1aac..817120f 100644 --- a/tests/test_timeout.py +++ b/tests/test_timeout.py @@ -49,10 +49,10 @@ async def _next(request: httpx2.Request) -> httpx2.Response: records = [r for r in caplog.records if r.name == "httpware.timeout"] assert len(records) == 1 assert records[0].levelno == logging.WARNING - assert records[0].event == "timeout.exceeded" # ty: ignore[unresolved-attribute] - assert records[0].timeout == 0.01 # noqa: PLR2004 # ty: ignore[unresolved-attribute] - assert records[0].method == "GET" # ty: ignore[unresolved-attribute] - assert "example.test/x" in records[0].url # ty: ignore[unresolved-attribute] + assert records[0].event == "timeout.exceeded" # ty: ignore[missing-attribute] + assert records[0].timeout == 0.01 # noqa: PLR2004 # ty: ignore[missing-attribute] + assert records[0].method == "GET" # ty: ignore[missing-attribute] + assert "example.test/x" in records[0].url # ty: ignore[missing-attribute] async def test_inner_timeout_propagates_unchanged() -> None: