feat(streaming): async streaming helpers for long-poll endpoints (PY-10)#39
feat(streaming): async streaming helpers for long-poll endpoints (PY-10)#39KhaledSalhab-Develeap wants to merge 7 commits into
Conversation
Add opentelemetry-api>=1.20 as an optional runtime dep under [otel]. Add opentelemetry-api and opentelemetry-sdk to [dev] so tests can use TracerProvider and InMemorySpanExporter without requiring users to install the SDK.
…ransport Add _otel.py with get_tracer, start_request_span, start_rpc_span, and record_error helpers. Each client and transport stores self._tracer at construction time. The _request loop (sync and async) is wrapped in a start_request_span context; call_tool (sync and async) is wrapped in a start_rpc_span context. Errors are recorded with record_error on the active span before re-raising. All helpers are no-ops when opentelemetry-api is not installed, so install hyperping[otel] to enable tracing. Closes #6f36bd (PY-11)
Several files introduced in earlier commits did not pass ruff format --check. Format them so the full src/ + tests/ tree is consistently formatted. Co-Authored-By: Khaled Salhab <khaled.salhab@develeap.com>
Add poll-based async streaming helpers for alert and incident monitoring. Introduce Alert/AlertType provisional models derived from the monitors endpoint, and AsyncStreamingMixin with stream_alerts and stream_incident_updates. Wire the mixin into AsyncHyperpingClient and export Alert/AlertType from the public API. Rate-limit note: default 30s interval uses 2 req/min per stream_alerts call, approximately 0.67% of the 300 req/min account limit. Co-Authored-By: Khaled Salhab <khaled.salhab@develeap.com>
…(PY-10) 17 tests covering the Alert model (frozen, extra fields, aliases, enum values) and both streaming helpers (first-poll baseline, state transitions, dedup, invalid UUID, not-found propagation, and poll_interval forwarding). Co-Authored-By: Khaled Salhab <khaled.salhab@develeap.com>
|
Reviewer context (not a merge request): Foundational PY-10 PR: httpx2 migration plus async streaming helpers ( Where to focus review: Risks / verify: CI status: No checks triggered (targets Notes: Base for #45 and conceptually subsumed by #40/#44. Pick one lineage to avoid duplicate merges. |
…t conflicts - pyproject: keep both the cli/typer extras (now on main) and this branch's otel + opentelemetry dev deps; httpx2 dependency retained. - tests/unit/test_mcp_client.py: take main's version (adds MCP write-tool tests; this branch only reformatted one assert). - uv.lock: regenerated against the merged pyproject. Note: this branch's OTel tests (test_otel.py) require the client _tracer wiring that lives in the PY-11 follow-up (PR #40), so they fail standalone here.
|
Update (conflict resolution + caveat): Merged Caveat for reviewers: CI is red here, and it is a pre-existing defect, not a merge artifact. Commit Recommendation: treat #40 as the canonical OTel/streaming PR (it is a superset of this branch plus the wiring), or merge #39 and #40 together. This PR alone should not be merged. |
_otel.py and test_otel.py were included here, but the client-side wiring the tests exercise (HyperpingClient._tracer etc.) is part of PY-11 and lives only in the OTel auto-instrumentation PR. As shipped, _otel.py was unused dead code and test_otel.py failed standalone. Remove both files and the opentelemetry deps so this PR is scoped to PY-10 async streaming and its test suite passes. OTel is delivered solely by the PY-11 PR.
|
Resolved: the red is fixed. Removed the premature OTel scaffolding ( |
Summary
Adds async streaming helpers for long-poll monitoring of alerts and incidents. The SDK now exposes
stream_alerts()andstream_incident_updates()as async iterators, enabling event-driven integrations without explicit polling loops.Also includes OpenTelemetry auto-instrumentation for REST and MCP calls, providing observability into SDK operations.
What changed
src/hyperping/models/__init__.pyAlert,AlertTypesrc/hyperping/_async_streaming_mixin.pyAsyncStreamingMixinwithstream_alerts()andstream_incident_updates()src/hyperping/_async_client.pyAsyncStreamingMixin; add streaming methodssrc/hyperping/__init__.pyAlert,AlertTypefrom public APIpyproject.toml[otel]optional extra (opentelemetry-api>=1.20)src/hyperping/_otel.pyget_tracer(),start_request_span(),start_rpc_span(),record_error()helperssrc/hyperping/client.py_request()in OTel span instrumentationsrc/hyperping/_async_client.py_request()in OTel span instrumentationsrc/hyperping/_mcp_transport.pycall_tool()in OTel span instrumentationsrc/hyperping/_async_mcp_transport.pycall_tool()in OTel span instrumentationtests/unit/test_streaming.pystream_alerts()andstream_incident_updates()tests/unit/test_otel.pytests/unit/conftest.pyHTTPCoreMocker.add_targetsforhttpcore2respx compatibilityuv.lockWhy this shape
Streaming
The mixin pattern lets async client inherit streaming capabilities alongside core REST operations. Poll-based iteration (vs SSE) is compatible with both the current long-poll endpoint and future SSE, keeping the public API unchanged when Hyperping's backend evolves. Default 30s poll interval is approximately 0.67% of the 300 req/min account limit, making continuous monitoring practical for event-driven workflows.
AlertandAlertTypeare derived from the monitors endpoint'sdown: booland optionalalert_typefield, providing a minimal event model. The provisionalAlertType.DEGRADEDis unreachable until Hyperping ships a degraded state on the monitors endpoint.OTel instrumentation
Spans are placed at the
_request()/call_tool()level (one logical operation, wrapping the full retry loop) rather than per-attempt, matching HTTP semantic conventions. Usingself._tracer(set at construction fromget_tracer()) lets tests inject a localTracerProviderand lets power users override the tracer at runtime. Theopentelemetry-apiruntime dependency is lightweight; the SDK (exporters, processors) is the user's responsibility, keeping runtime footprint minimal.Verification matrix
uv run pytest tests/unit/test_streaming.py -vuv run pytest tests/unit/test_otel.py -vuv run pytest tests/ -quv run ruff check src/ tests/uv run mypy src/hyperping/from hyperping import Alert, AsyncHyperpingClientfrom hyperping._otel import HAS_OTELAcceptance criteria
async def stream_alerts(self) -> AsyncIterator[Alert]added toAsyncHyperpingClientasync def stream_incident_updates(uuid) -> AsyncIterator[IncidentUpdate]added toAsyncHyperpingClientAlertandAlertTypemodels exported from public APIhyperping[otel]optional extra pullsopentelemetry-api>=1.20http.request.method,url.full,server.address,hyperping.sdk.versionrpc.method,rpc.system,server.address,hyperping.sdk.versionFollowups not in this PR
Duplicate poll_interval test (
test_streaming.py:254-291):test_stream_alerts_respects_poll_intervalandtest_stream_alerts_custom_poll_intervalverify the same property with different values. Remove one in a test cleanup pass.AlertType.DEGRADEDis unreachable from currentstream_alertsimplementation (which only producesDOWN/UPfrommonitor.down: bool). When Hyperping ships a degraded state in the monitors endpoint, wire it up and add a test.Transient API error behavior undocumented: if
_requestraises (network timeout, 5xx) during a poll cycle, the exception propagates out of the generator and terminates it. Consider adding error-handling guidance to the docstring or amax_errorsparameter in a future pass.Empty-monitors edge case untested:
stream_alertswith an empty monitors list correctly yields nothing and sets an empty baseline, but this path has no test. Add in the next test-coverage pass.Untracked TypedDict test files:
tests/unit/test_typeddict_input.pyandtests/unit/test_async_typeddict_input.pyreference a non-existentcoerce_inputsymbol and break full-suite collection. Must be resolved (commit or delete) before the next feature branch adds tests.Response status code as span attribute: requires enriching
_execute_single_attemptto expose the raw response; deferred to a follow-up.README/docs update for streaming and OTel usage patterns (out of scope for this ticket).
CHANGELOG entry (done at merge time per repo convention).