feat(streaming): add max_errors error-recovery parameter to streaming helpers (#b854ab)#45
Conversation
…ter (#b854ab) Covers: budget tolerance, budget exhaustion, auth/rate-limit bypass, counter reset on success, infinite tolerance (max_errors=None), and exponential backoff verification for both stream_alerts and stream_incident_updates.
…tream_incident_updates (#b854ab) Both generators now accept max_errors (default 3) to tolerate N consecutive poll-cycle failures with exponential backoff before re-raising. Backoff uses retry_config.initial_delay/backoff_factor/max_delay for consistency with the per-request retry layer. HyperpingAuthError and HyperpingRateLimitError bypass tolerance and re-raise immediately. Adds retry_config attribute declaration to _AsyncClientProtocol so mypy can verify access.
|
Reviewer context (not a merge request): Adds a Where to focus review: Risks / verify: Backoff reuses request-level retry config, so total delay compounds with CI status: No checks triggered (base is Notes: Stacked on #39. Looks safe and well-tested. |
Brings in main (via the async-streaming base) and the removal of the premature OTel files. This PR is scoped to the streaming max_errors helper; OTel is delivered solely by the PY-11 PR.
Summary
Adds a
max_errorsparameter tostream_alertsandstream_incident_updatesthat tolerates N consecutive poll-cycle failures (with exponential backoff) before re-raising. Baseline and seen-set state are preserved across transient outages. Auth and rate-limit errors bypass tolerance and re-raise immediately. Implements Approach A from SPIKE-2d10f5.What changed
src/hyperping/_protocols.pyretry_config: RetryConfigattribute declaration to_AsyncClientProtocol(TYPE_CHECKING import to avoid circular)src/hyperping/_async_streaming_mixin.pytests/unit/test_streaming.pyTestStreamErrorRecovery; updated exception importsDesign reasoning
The streaming
max_errorslayer sits above_request's per-request retry budget:_requesthandles transient HTTP failures;max_errorstolerates repeated poll-cycle failures after that budget is exhausted. Backoff reusesretry_config.initial_delay,backoff_factor, andmax_delayso the behavior stays consistent with existing retry configuration. Inlining the try/except in each generator (rather than a shared helper) keeps the control flow readable without awkward callbacks for a ~12-line block.Verification matrix
uv run pytest tests/unit/test_streaming.py -vuv run pytest tests/ --ignore=tests/unit/test_otel.py -quv run mypy src/hyperping/_async_streaming_mixin.py src/hyperping/_protocols.pyuv run ruff check src/ tests/unit/test_streaming.pyuv run ruff format --check src/ tests/unit/test_streaming.pytest_otel.py::TestSyncClientSpans::test_request_creates_span)Acceptance criteria
max_errorsparameter added tostream_alertsandstream_incident_updateswith default 3HyperpingAuthErrorandHyperpingRateLimitErrorbypass tolerance and re-raise immediatelymax_errors=Noneprovides infinite toleranceretry_configparameters; delays are capped atmax_delayFollow-up items
stream_incident_updatesauth/rate-limit bypass tests (medium). Theexcept (HyperpingAuthError, HyperpingRateLimitError): raiseblock insidestream_incident_updates(_async_streaming_mixin.py:176-177) has zero test coverage (only uncovered line in the file). Addtest_stream_incident_updates_reraises_auth_error_immediatelyandtest_stream_incident_updates_reraises_rate_limit_error_immediatelytoTestStreamErrorRecovery, symmetric to the existingstream_alertstests.Symmetric error-recovery tests for
stream_incident_updates(low). Themax_errors=Noneinfinite-tolerance, counter-reset-on-success, and backoff-progression tests exist only forstream_alerts. The two generators use identical error-handling code, so the risk is low, but coverage should be symmetric to guard against future divergence.HyperpingNotFoundErrorabsorption after first poll (low).HyperpingNotFoundErroris a subclass ofHyperpingAPIErrorand is therefore absorbed by the error counter instream_incident_updates. If an incident is deleted after streaming starts, the caller does not learn of the deletion untilmax_errorscycles later. The docstring impliesHyperpingNotFoundErroris only raised "on the first poll," but this is not enforced. Document the behavior explicitly or add it to the bypass list in a follow-up.