Skip to content

feat: add zstd request body compression - #1218

Open
apify-factory wants to merge 15 commits into
masterfrom
sync/0e1932b28308
Open

apify-factory wants to merge 15 commits into
masterfrom
sync/0e1932b28308

Conversation

@apify-factory

@apify-factory apify-factory commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Reference change

apify/apify-client-js#1200

Ported change

  • HttpCompressionAlgorithm (apify_client.types) now accepts 'zstd', and resolve_compressor maps it to the new compressor. The compression argument of ApifyClient / ApifyClientAsync and of ActorServerClient.request therefore accepts 'zstd'.
  • New public apify_client.http_compressors.ZstdHttpCompressor (http_compressors/_zstd.py), modeled on the gzip and brotli compressors. It reports content_encoding = 'zstd' and takes a keyword-only quality from 1 to 19, defaulting to 3. Levels 20–22 are rejected because their window exceeds the 8 MB that RFC 9659 requires HTTP decoders to support. The body-selection rules (minimum size, already-compressed content types, caller-set Content-Encoding) are the same as for the other algorithms.
  • Python-specific differences from the reference:
    • Source of zstd. Python 3.14+ uses the standard library's compression.zstd. Older versions use the official backports.zstd package through a new optional zstd extra (pip install "apify-client[zstd]"), following the pattern of the brotli extra.
    • Missing zstd. If the extra is missing, or a 3.14+ Python was built without zstd, import apify_client still works. Only accessing ZstdHttpCompressor or passing compression='zstd' raises a clear ImportError.
    • Supporting changes. try_import now treats a missing parent package of a dotted dependency name as the dependency being missing, and accepts extra_name=None for dependencies no extra can install. [tool.ty.analysis] allowed-unresolved-imports lists backports.zstd, because ty checks against Python 3.11 even in 3.14+ environments, where the package is not installed.
    • Invalid quality. An invalid quality raises ValueError, like the existing Python compressors, rather than the JS ArgumentValidationError.

@github-actions github-actions Bot added the t-tooling Issues with this label are in the ownership of the tooling team. label Nov 10, 2026
@codecov

codecov Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.69767% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.69%. Comparing base (eb8ded4) to head (845edaf).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/apify_client/http_compressors/_zstd.py 91.30% 2 Missing ⚠️
src/apify_client/http_compressors/__init__.py 90.90% 1 Missing ⚠️
src/apify_client/types.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1218      +/-   ##
==========================================
- Coverage   95.76%   95.69%   -0.07%     
==========================================
  Files          63       64       +1     
  Lines        6676     6712      +36     
==========================================
+ Hits         6393     6423      +30     
- Misses        283      289       +6     
Flag Coverage Δ
integration 90.98% <58.13%> (+0.31%) ⬆️
unit 91.07% <90.69%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Pijukatel
Pijukatel requested a review from vdusek November 10, 2026 10:17

@vdusek vdusek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a self-review / staff review to the porting process?

Comment thread pyproject.toml
brotli = ["brotli>=1.0.9"]
httpx2 = ["httpx2>=2.0.0"]
# zstd is in the standard library (`compression.zstd`) from Python 3.14; older versions use its official backport.
zstd = ["backports.zstd>=1.0.0; python_version < '3.14'"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uv.lock isn't updated for the new zstd extra, so uv lock --check fails. CI passes because it doesn't sync with --locked, but every uv sync rewrites the lock and leaves the tree dirty. Please run uv lock and commit the result.

✍️ Drafted by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 19d5957: uv.lock now includes backports-zstd for the zstd extra, and uv lock --check passes.

Comment on lines +17 to +18
# zstd is in the standard library from Python 3.14, though a Python build can leave it out, which no extra can fix.
# Older versions need the `backports.zstd` package from the `zstd` extra. Either way it's wrapped in try_import.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The note about builds without zstd repeats the comment in _zstd.py, and the last sentence says what the next lines show. One line about the version split is enough.

Suggested change
# zstd is in the standard library from Python 3.14, though a Python build can leave it out, which no extra can fix.
# Older versions need the `backports.zstd` package from the `zstd` extra. Either way it's wrapped in try_import.
# zstd is in the standard library from Python 3.14. Older versions need `backports.zstd` from the `zstd` extra.

✍️ Drafted by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 845edaf.


return BrotliHttpCompressor()
if compression == 'zstd':
# Imported here for the same reason as brotli: before Python 3.14 zstd needs the `zstd` extra.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import also fails on a Python 3.14+ build without zstd, not only before 3.14. The short form covers both cases.

Suggested change
# Imported here for the same reason as brotli: before Python 3.14 zstd needs the `zstd` extra.
# Imported here for the same reason as brotli.

✍️ Drafted by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already covered: the comment was updated before this review landed and now reads "Imported here so a missing zstd extra, or a Python build without zstd, raises ImportError at call time." That names both cases, so I left it as is.


## Enabling zstd

Python 3.14 and newer ship zstd in the standard library as `compression.zstd`, so pass `compression='zstd'` and nothing else is needed, as long as your Python build includes zstd support, which the python.org builds do. On older Python versions, install the optional extra, which brings in the `backports.zstd` package:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence joins four clauses. Could you split it?

Suggested change
Python 3.14 and newer ship zstd in the standard library as `compression.zstd`, so pass `compression='zstd'` and nothing else is needed, as long as your Python build includes zstd support, which the python.org builds do. On older Python versions, install the optional extra, which brings in the `backports.zstd` package:
Python 3.14 and newer include zstd in the standard library as `compression.zstd`, so you only need to pass `compression='zstd'`. Your Python build must include zstd support. The python.org builds do. On older Python versions, install the optional extra, which brings in the `backports.zstd` package:

✍️ Drafted by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 7f623d3: the sentence about builds without zstd was dropped. The paragraph now reads "Python 3.14 and newer ship zstd in the standard library as compression.zstd, so compression='zstd' works there without an extra. On older Python versions, install the optional extra, which brings in the backports.zstd package:". The no-zstd build case is still covered by the ImportError note below the install block.

Comment thread docs/01_introduction/index.mdx Outdated
</TabItem>
</Tabs>

To compress with `zstd` instead, pass `compression='zstd'`. On Python 3.14 and newer it uses the standard library; on older versions install the optional `zstd` extra with `pip install "apify-client[zstd]"`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split at the semicolon into two sentences, as the docs style asks.

Suggested change
To compress with `zstd` instead, pass `compression='zstd'`. On Python 3.14 and newer it uses the standard library; on older versions install the optional `zstd` extra with `pip install "apify-client[zstd]"`.
To compress with `zstd` instead, pass `compression='zstd'`. On Python 3.14 and newer it uses the standard library. On older versions, install the optional `zstd` extra with `pip install "apify-client[zstd]"`.

✍️ Drafted by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 845edaf.

Comment thread README.md Outdated
Comment on lines +60 to +61
To compress with `zstd`, pass `compression='zstd'`. Python 3.14 and newer have it in the standard library; on older
versions install the optional `zstd` extra: `pip install "apify-client[zstd]"`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: split at the semicolon.

Suggested change
To compress with `zstd`, pass `compression='zstd'`. Python 3.14 and newer have it in the standard library; on older
versions install the optional `zstd` extra: `pip install "apify-client[zstd]"`.
To compress with `zstd`, pass `compression='zstd'`. Python 3.14 and newer have it in the standard library. On older
versions, install the optional `zstd` extra: `pip install "apify-client[zstd]"`.

✍️ Drafted by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 845edaf.

@Pijukatel
Pijukatel requested a review from vdusek November 10, 2026 14:57

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-client-sync t-tooling Issues with this label are in the ownership of the tooling team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants