Uh oh!
There was an error while loading. Please reload this page.
Use standard libary Zstandard for Python 3.14+ - #3613
Conversation
lovelydinosaur
commented
Jul 27, 2025
Ooh interesting, thanks. Any idea on how widely zstd is currently supported? (Wrt both browsers and servers.) Related to this... compression is one of the currently unimplemented features in the httpx 1.0 prerelease... https://www.encode.io/httpnext/ |
| def _new_decompressor(self) -> None: | ||
| decompressor = zstandard.ZstdDecompressor() | ||
| if hasattr(decompressor, "decompressobj"): | ||
| self.decompressor = decompressor.decompressobj() # prgama: no cover | ||
| else: | ||
| self.decompressor = decompressor # pragma: no cover |
There was a problem hiding this comment.
Could you explain this part?
There was a problem hiding this comment.
This is admittedly a little awkward. Python upstreamed the pyzstd package into compress.zstd because it's API was closer to existing standard library compression APIs. The zstandard package provides a ZstdDecompressObj facade that implements the standard library style API.
An alternative would be to import the libraries under separate names so the method would look more like this:
def_new_compressor(self) ->None:
ifcompression_zstdisnotNone:
self.decompressor=compression_zstd.ZstdDecompressor()
else:
self.decompressor=zstandard.ZstdDecompressor().decompressobj()
What do you think?
There was a problem hiding this comment.
if the requirement in pyproject.toml is changed to the 3.9+ backport at backports.zstd published by the pyzstd maintainer (which got moved into cpython 3.14 standard library):
backports-zstd==1.0.0 ; python_version < "3.14"
this diff would get much simpler right?
maybe with a simpler diff, this PR has a better chance of landing?
reading from chunked streams gets streamlined like that: aio-libs/aiohttp@df8ad83
lilydjwg
commented
Jul 28, 2025
At least crates.io, packagist and sourceforge support it. (My tests blow up because I cache http responses and there are zstd responses from other Python versions; the "zstandard" module doesn't support 3.14 yet.) |
mollymorphous
left a comment
There was a problem hiding this comment.
Thanks for the review! Firefox and Chrome currently support zstd (caniuse). Safari does not yet, but plans to: WebKit/standards-positions#168
I wasn't able to find a hard number on server-side deployments, the answer seems to be not a lot, but CloudFlare recently added support to the CDN.
| def _new_decompressor(self) -> None: | ||
| decompressor = zstandard.ZstdDecompressor() | ||
| if hasattr(decompressor, "decompressobj"): | ||
| self.decompressor = decompressor.decompressobj() # prgama: no cover | ||
| else: | ||
| self.decompressor = decompressor # pragma: no cover |
There was a problem hiding this comment.
This is admittedly a little awkward. Python upstreamed the pyzstd package into compress.zstd because it's API was closer to existing standard library compression APIs. The zstandard package provides a ZstdDecompressObj facade that implements the standard library style API.
An alternative would be to import the libraries under separate names so the method would look more like this:
def_new_compressor(self) ->None:
ifcompression_zstdisnotNone:
self.decompressor=compression_zstd.ZstdDecompressor()
else:
self.decompressor=zstandard.ZstdDecompressor().decompressobj()
What do you think?
lovelydinosaur
commented
Jul 29, 2025
From a bit of time reviewing this I've not been able to track down good examples of URLs to use for comparison purposes here. Eg...
Here's one example of a URL that does support zstd, though in this particular case it appears less efficient than gzip... The CloudFlare pitch for zstd https://blog.cloudflare.com/new-standards/ isn't neccessarily convincing... gzip is essentially just as fast, and looks to have slightly less efficient though notably more stable compression ratios. I'm reviewing this for the purposes of Does anyone have some useful real world examples that'd help verify if that is / isn't a good policy? |
I suggest adding Python 3.14 to the test suite as in: |
tuffnatty
commented
Sep 8, 2025
I would suggest dropping |
tuffnatty
commented
Sep 8, 2025
gzip is definitely not just as fast (without hardware offloading), it's just that they measure the whole response time, where the compression speed difference does not seem to matter much on average.
I had used this to stream huge JSONL chunks over HTTP, and while it does not make traffic much less or a great overall speed improvement, the CPU load situation has improved a lot (relative to gzip). |
cclauss
commented
Sep 17, 2025
@tuffnatty Would you be willing to create an alternative pull request that uses the backport and adds automated tests on Py3.14 like |
lovelydinosaur
commented
Sep 17, 2025
Okay, so my review of this was that supporting gzip only would be a sensible policy. |
lilydjwg
commented
Sep 17, 2025
Would there be a mechanism to add support for other compression methods via third-party code then? I'm a bit worried about interoperability with not-so-good servers and proxies. |
lovelydinosaur
commented
Sep 17, 2025
That's a good question... ...I can't answer that fully at the moment. There might not be any API explicitly for that purpose. Here's how interop. with the streams class would be... # A custom stream on top of the streams API...classZstdStream(httpx.Stream):
def__init__(self, stream: httpx.Stream):
self._wrapped=stream# Implement `.read()` and `.close()`# Usage...stream=ZStdStream(response.stream)
body=stream.read()We probably don't want specific dials to "support for other compression methods via third-party code", since the less config the better. However we do want the tooling itself to be flexible enough to support customisation. There's a current example in the docs demo'ing a custom client which is vastly more simple than with httpx 0.28. Perhaps building out that example to also demo custom response classes would be a good way to point users towards adaptability without increasing API surface area. |
tuffnatty
commented
Sep 18, 2025
@cclauss Thanks but the issue has been resolved in another way. |
fwiw, the major alternatives to httpx support zstd (and for sure brotli): supporting only gzip/deflate might be insufficient in 2025. especially brotli accounts for 33% of compressed http responses already in 2021 (Figure 22.4) and 44% of compressed javascript versus 41% gzip in 2024 (Figure 1.21) |
cclauss
commented
Oct 3, 2025
How was the issue resolved? |
tuffnatty
commented
Oct 3, 2025
|
ddelange
commented
Oct 8, 2025
disregard my last comment, I see that they're all supported 👍 |
…ixes - apps/docs: full Docusaurus 3.7 documentation site (29 pages) covering premise, architecture, agent interface, pitfalls, ADRs, and roadmap; webpack pinned to 5.95.0 to avoid ProgressPlugin regression in 5.100+; excluded from npm workspaces to prevent dependency hoisting conflicts - SeedBrainBanner: shows in project view when decision_count=0; two-field form POSTs to /brain/agent-log so the brain has context before first session - DRIFT_SEMANTIC_THRESHOLD=0.72 in docker-compose (was 0.55); prevents 15-30 triage alerts/day at 10-agent scale - brain-writer: decision.summary coalesced to "" to prevent Neo4j ParameterMissing on LLM responses that omit the field - eval:link-following: updated test PR to encode/httpx#3613 (prior PR deleted); passes 4/4 checks end-to-end including GitHub PR citation in recall query - drift-detector: DriftAlert.reason persisted to Neo4j and surfaced in UI - query.ts: fire-and-forget QueryLog write for brain_query_results_count - WriteBackQualityBadge: extremes-only signaling (green/amber, no red) - R1 mitigations: Stop hook, schema validation gate, Python SDK auto-flush - Content: articles tracker, launch posts, Mem0/Zep comparison, viability piece Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ixes - apps/docs: full Docusaurus 3.7 documentation site (29 pages) covering premise, architecture, agent interface, pitfalls, ADRs, and roadmap; webpack pinned to 5.95.0 to avoid ProgressPlugin regression in 5.100+; excluded from npm workspaces to prevent dependency hoisting conflicts - SeedBrainBanner: shows in project view when decision_count=0; two-field form POSTs to /brain/agent-log so the brain has context before first session - DRIFT_SEMANTIC_THRESHOLD=0.72 in docker-compose (was 0.55); prevents 15-30 triage alerts/day at 10-agent scale - brain-writer: decision.summary coalesced to "" to prevent Neo4j ParameterMissing on LLM responses that omit the field - eval:link-following: updated test PR to encode/httpx#3613 (prior PR deleted); passes 4/4 checks end-to-end including GitHub PR citation in recall query - drift-detector: DriftAlert.reason persisted to Neo4j and surfaced in UI - query.ts: fire-and-forget QueryLog write for brain_query_results_count - WriteBackQualityBadge: extremes-only signaling (green/amber, no red) - R1 mitigations: Stop hook, schema validation gate, Python SDK auto-flush - Content: articles tracker, launch posts, Mem0/Zep comparison, viability piece Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
PEP 784 add a Zstandard implementation to the Python standard library under
compression.zstd, and is scheduled for release in Python 3.14. This PR adaptsZStandardDecoderto work with either the standard library implementation or the implementation from thezstandardpackage.This has the implication that Zstandard content decoding is available by default on Python 3.14 and later, without the need to install the
zstdextra.Checklist