All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
A round of platform improvements to dexpace-sdk-core: new optional building
blocks (typed serialization, webhook verification, pagination, three pipeline
policies), tightened retry behaviour, a corrected per-operation tracing
lifecycle, and a batch of correctness fixes across bodies, SSE parsing, Digest
auth, and error reporting. Most of this lands in core; the transport adapters
additionally get consistent connect- vs read-phase timeout classification,
tighter resource release, and a set of edge-case corrections (status-code
reporting, chunked-framing detection, and content-length under content-encoding).
The only removed public symbol is the unused RetryConfig (see Removed);
existing code otherwise continues to work without modification — with one
behavioural note for hand-assembled pipelines (see Tracing lifecycle under
Changed).
- Tristate values (
serde.tristate). A three-way type distinguishing "set to a value", "explicitly set to null", and "absent", so partial updates (PATCH-style payloads) can round-trip an explicitnullwithout conflating it with an omitted field. - Typed model codec (
serde.codec). A small encode/decode layer over the existingSerdeprotocol for converting between typed models and wire bytes, built on the standard library only. This is the largest new surface and is worth a careful read before depending on it. - Webhook signature verification (
http.webhooks). Helpers to verify the authenticity of inbound webhook payloads using constant-time comparison. - Pagination (
pagination). A paginator abstraction with pluggable next-page strategies, aLinkheader parser, and a page model, so list endpoints can be iterated without each caller re-implementing cursor handling. - Idempotency-key policy (
pipeline.policies.idempotency, plus its async twin). Stamps a generated idempotency key onto retriable, non-idempotent requests so safe automatic retries don't double-apply a side effect. - Client-identity policy (
pipeline.policies.client_identity, plus its async twin). Sets a consistentUser-Agent/ client-identity header derived from the configured application id and SDK version. - Per-operation tracing policy (
OperationTracingPolicyand its async twinAsyncOperationTracingPolicy, with a new outermostStage.OPERATION). Emits the per-operationHttpTracerlifecycle (operation_started, then exactly oneoperation_succeeded/operation_failed) from outside the retry and redirect wrappers, so the reported outcome reflects the final result of the whole call rather than a single attempt or hop. Bothdefault_pipelineanddefault_async_pipelinewire it, so the async stack now reports the same lifecycle alongside the attempt-level events its retry / redirect policies already emit. OnlyTracingPolicy's per-attempt OpenTelemetry span policy remains sync-only. - HTTP tracer (
instrumentation.http_tracer). An adapter-style tracer base whose per-event methods default to no-ops, so a subclass overrides only the events it cares about. Wired through the tracing policy for span emission. - Log correlation (
instrumentation.correlation). Acontextvar-backed correlation id that flows through the pipeline and is attached to log records, so logs from a single logical request can be tied together. - Reconnecting SSE client (
http.sse.connection).SseConnectionandAsyncSseConnectionresume an interrupted event stream by replaying theLast-Event-IDheader and reconnecting with jittered backoff that honours the server'sretry:hint. Built on the shared dispatch seam (pipeline.dispatch), which lets both the SSE client and the paginator accept either a pipeline or a bare send-callable.
- Retry tuning (
pipeline.policies.retry/async_retry). More configurable backoff and clearer rules for which responses and exceptions are retried, including respectingRetry-After. The async retry path now observes cancellation cleanly between attempts. - Tracing and redirect policies now emit tracer events and carry correlation through redirects, with credentials stripped on cross-origin redirects.
- Tracing lifecycle (
pipeline.policies.tracing_policy). The per-operationHttpTracerlifecycle moved out ofTracingPolicyinto the newOperationTracingPolicy;TracingPolicynow emits only its per-attempt span and the per-request events (request_sent,response_headers_received,response_received).default_pipelinewires both, so callers who use it are unaffected. A pipeline assembled by hand that wants the operation lifecycle must now addOperationTracingPolicyalongsideTracingPolicy— a bareTracingPolicyno longer emitsoperation_started/operation_succeeded/operation_failed. So that change is not silent, aTracingPolicythat runs with a realHttpTracerbut noOperationTracingPolicybracketing it logs a one-time warning. - Default pipelines (
pipeline.defaults). The standard sync/async stacks now assemble the new idempotency and client-identity policies alongside the existing retry, redirect, logging, and tracing policies. - Loggable bodies (
http.request.loggable_request_body,http.response.loggable_response_body). Capture is bounded and repeatable reads behave correctly; the byte cap is honoured on the tap without truncating the primary write path. - Error reporting (
errors.http). HTTP errors now expose whether they areretryableand carry a bounded body snapshot for diagnostics, with the snapshot capped so an error never holds an unbounded payload. HttpRange.suffix(http.common.http_range) now returns a publicHttpRange(carrying anis_suffixflag) instead of a private helper type, so abytes=-Nsuffix range composes withHttpRange.format_manyalongside ordinary ranges.CallContext(http.context) is now anabc.ABC. It declares no abstract methods, so existing subclasses are unaffected; the change only prevents the base from being instantiated directly.
RetryConfig(pipeline/pipeline.step.config). It was exported but never wired into the retry policy, so it configured nothing;RetryPolicy's constructor is the real configuration surface. Code that importedRetryConfigshould configureRetryPolicydirectly.
- SSE parsing (
http.sse.parser) now strips a leading UTF-8 byte-order mark and cleans up the async stream deterministically on cancellation or exit. - Digest auth (
http.auth.digest) honours the server-advertised charset when computing the digest, fixing authentication against servers that send non-ASCII credentials. - MediaType (
http.common.media_type) handles parameter parsing edge cases (quoting, casing, and whitespace) more robustly. - Async response cancellation (
http.response.async_response,async_response_body). Cancelling an in-flight read now releases the underlying resources instead of leaking them, and re-raisesCancelledErrorafter cleanup. - Per-operation tracing outcome (
pipeline.policies.tracing_policy). A call retried after a failed first attempt no longer reportsoperation_failedfor the discarded attempt (it reports the singleoperation_succeededit ends on), and a redirect whose later hop fails no longer reportsoperation_succeededfor the earlier 3xx hop. The lifecycle now fires exactly once and reflects the final outcome. See Tracing lifecycle under Changed for the API shape. Content-LengthunderContent-Encoding(http.stdlib.urllib_http_client).UrllibHttpClientno longer drops a validContent-LengthwhenContent-Encodingis present:http.clientdoes not decode content codings, so the body it serves is the wire payload whose length the header describes, and the length is now surfaced as-is. (The decompressing requests/httpx/aiohttp adapters still drop it, since they hand back a decoded stream.)- Chunked-framing detection (
http.stdlib.asyncio_http_client). TheTransfer-Encodingcheck matches thechunkedcoding by token rather than substring, so a coding whose name merely containschunked(e.g.x-chunked) is no longer mistaken for chunked framing. - Out-of-range status reporting (
http.stdlib.urllib_http_client,asyncio_http_client). Both now raise aServiceResponseErrorwordedInvalid status code: …for a status outside 100–599, matching the other adapters.
mypy --strict,ruff check,ruff format --check, andpytestrun in CI across the supported Python matrix (3.12–3.14). New modules ship with tests under each package'stests/tree, andpy.typedcontinues to ship so downstream type-checkers consume the annotations.
The following were intentionally left out of this round and are not included:
- Default error map — error classification beyond the
retryableflag and body snapshot was deferred; callers still map status codes to domain errors themselves. sendfilefast-path — file bodies are streamed via the existingiter_bytespath; no zero-copysendfiletransport optimisation was added.- Async OpenTelemetry spans / logging — the per-attempt span policy
(
TracingPolicy) andLoggingPolicyship sync-only, sodefault_async_pipelineemits the per-operationHttpTracerlifecycle and attempt-level events but no OpenTelemetry spans or structured request / response logs. - MCP support — no Model Context Protocol integration is included.
- Java SDK items — the Java counterpart lives in a separate repository and was out of scope here.
- Code generation — no client/model code generation was added; all surfaces in this release are hand-written.