Skip to content

feat(core): typed serde, pagination, webhooks, tracing, and fixes#3

Merged
OmarAlJarrah merged 19 commits into
mainfrom
feat/sdk-platform-improvements
Jun 9, 2026
Merged

feat(core): typed serde, pagination, webhooks, tracing, and fixes#3
OmarAlJarrah merged 19 commits into
mainfrom
feat/sdk-platform-improvements

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Jun 9, 2026

Copy link
Copy Markdown
Member

Adds a batch of optional building blocks to dexpace-sdk-core and fixes several correctness issues. Everything lands in core; the transport packages are unchanged. No public symbol was removed, so existing code keeps working unmodified.

New capabilities

  • Tristate values (serde.tristate) — a three-way type distinguishing "a value", "explicit null", and "absent", so PATCH-style payloads can round-trip an explicit null without conflating it with an omitted field.
  • Typed model codec (serde.codec) — a small encode/decode layer over the existing Serde protocol for moving between typed models and wire bytes, standard-library only.
  • Webhook verification (http.webhooks) — verifies inbound webhook payloads (HMAC-SHA256, constant-time comparison, replay-window check) and unwraps the verified body.
  • Pagination (pagination) — sync and async paginators that iterate items or pages over a configured pipeline, with cursor/token, page-number, and RFC 5988 Link-header strategies. Each page fetch runs the full pipeline, so retry, auth, and tracing apply per page.
  • Idempotency-key and client-identity policies — the first stamps a key generated once per request and reused across retries onto write methods (so safe retries don't double-apply a side effect); the second sets a consistent User-Agent. Both are wired into the default pipeline.
  • HTTP tracer and log correlation — an adapter-style tracer (no-op by default) carried on the instrumentation context and emitted from the tracing/redirect/retry policies, plus trace/span ids published to contextvars and stamped onto log records.

Correctness fixes

  • Loggable response body no longer caches an empty body when the network drops mid-read — it keeps the partial bytes and re-raises; the one-time drain is now lock-guarded; both loggable bodies gain a bounded snapshot.
  • SSE parser strips a leading byte-order mark; async response and SSE cleanup runs to completion under cancellation and re-raises CancelledError; a circular import between the errors package and the streaming module is broken.
  • Digest auth negotiates its charset from the challenge (RFC 7616) instead of always using UTF-8.
  • MediaType parameter parsing handles first-= splits, quoted/escaped values, key-only lowercasing, and unknown charsets.
  • HTTP errors expose a retryable flag and a non-consuming body snapshot.
  • The retry, redirect, and tracing policies now share a single per-operation HttpTracer: attempt-level events (attempt_started/attempt_failed/attempt_retries_exhausted) and the operation/request/response lifecycle events land on the same tracer instance instead of separate ones.
  • An X-RateLimit-Reset wait is now jittered upward only, so a reconnect never wakes before the window resets — a downward jitter risked an immediate repeat 429. Retry-After is still respected and capped.
  • The Link-header paginator resolves a relative RFC 5988 target against the request URL instead of raising on the missing scheme.
  • The typed codec raises SerializationError when asked to encode non-UTF-8 bytes, rather than letting a UnicodeDecodeError escape.
  • The async shielded-cleanup helper preserves cancellation precedence: if a cleanup coroutine raises an ordinary exception while a cancellation is pending, the cancellation still propagates rather than being masked by the cleanup error.

Tests and tooling

Adds behavioural conformance fixtures, serialization snapshot tests, and an ast-based public-API surface baseline that fails on unintended API changes.

Verification

ruff check, ruff format --check, mypy --strict (212 source files), and the full pytest suite (999 passing) are all green.

OmarAlJarrah and others added 19 commits June 9, 2026 12:55
Tristate carries three states (value / explicit null / absent) so PATCH-style payloads can round-trip an explicit null without conflating it with an omitted field. The codec is a small encode/decode layer over the existing Serde protocol for converting between typed models and wire bytes, standard-library only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verifies inbound webhook payloads (id/timestamp/signature headers, HMAC-SHA256, constant-time comparison, replay-window check) and unwraps the verified JSON body. Pure standard library.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A sync and async paginator that iterate items or pages over a configured pipeline, with cursor/token, page-number, and RFC 5988 Link-header strategies. Fetching each page runs the full pipeline, so retry, auth, and tracing apply per page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An adapter-style HttpTracer whose per-event methods default to no-ops, carried on the instrumentation context. Trace and span ids are published to contextvars so the client logger can stamp them onto every record.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The idempotency policy stamps a key generated once per request and reused across retries onto write methods, so safe automatic retries don't double-apply a side effect. The client-identity policy sets a consistent User-Agent. Both are wired into the default pipeline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…c retry

Retry now honours X-RateLimit-Reset, defaults to full jitter, and caps a server Retry-After. The tracing and redirect policies emit tracer lifecycle events. The async retry policy never catches or retries CancelledError.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a mid-drain error the response body now retains the bytes read so far and re-raises, instead of silently caching an empty body; the one-time drain is guarded by a lock; and both loggable bodies gain a bounded snapshot that previews at most N bytes without copying more.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The SSE parser strips a leading byte-order mark. Async response and SSE cleanup runs to completion even when the awaiting task is cancelled, then re-raises CancelledError. A circular import between the errors package and the streaming module is broken by importing the error type directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Digest credentials are encoded as UTF-8 only when the challenge advertises charset=UTF-8, and ISO-8859-1 otherwise, per RFC 7616.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parameters split on the first '=' only, quoted values are unquoted and unescaped, parameter keys are lowercased while values keep their case, and an unknown charset degrades to None.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HTTP errors carry a retryable flag the retry policy can read directly and a non-consuming body snapshot for previewing the error body without consuming it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Behavioural fixtures assert observable contracts (single-use bodies, replayability, header case-insensitivity, context eviction, pagination, webhook verification); snapshot tests pin serialized output; and an ast-based public-surface baseline fails on unintended API changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lockfile pinned a requests specifier that no longer matched the transport package's pyproject; re-sync corrects the drift. No dependency change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The retry policy minted its own HttpTracer from the factory instead of reusing the per-operation instance cached on the pipeline context, so attempt events landed on a different tracer than the operation-level events emitted by the tracing and redirect policies. It now resolves the shared tracer.

An X-RateLimit-Reset wait was also jittered downward, which could wake the client before the window actually reset and earn another rate-limit response. The jitter is now upward-only, so a reconnect never fires before the reset instant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RFC 5988 permits a relative next/prev target. LinkHeaderStrategy parsed the target directly and raised on the missing scheme; it now resolves the target against the originating request URL before reissuing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The codec's bytes-encoding branch let a UnicodeDecodeError escape. It now wraps it in SerializationError to match the documented Raises contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
If a shielded cleanup coroutine raised an ordinary exception while an outer cancellation was pending, the cancellation was dropped in favour of the cleanup error. The cleanup helpers now re-raise the pending CancelledError once the cleanup completes, surfacing a cleanup failure only when no cancellation is pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@OmarAlJarrah OmarAlJarrah changed the title Core platform improvements: typed serde, pagination, webhooks, tracing, and correctness fixes feat(core): typed serde, pagination, webhooks, tracing, and fixes Jun 9, 2026
@OmarAlJarrah OmarAlJarrah merged commit 481c09c into main Jun 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant