Skip to content

feat: Stream request bodies from files, iterables, and responses - #1060

Merged
vdusek merged 24 commits into
masterfrom
feat/streamed-request-bodies
Sep 30, 2026
Merged

vdusek merged 24 commits into
masterfrom
feat/streamed-request-bodies

Conversation

@vdusek

@vdusek vdusek commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

set_record, the run_input of start / call / metamorph, and HttpClient.call(data=...) accept an io.IOBase stream such as an open file, an iterable of bytes or str chunks, or a streamed HttpResponse. The client uploads it in chunks without holding it in memory, so one Actor's OUTPUT record can become another Actor's input. The async client also accepts async iterables and aiofiles-style readers.

Streaming is experimental. The docs say so, and the first streamed body in a process emits a UserWarning.

Behavior

  • An io.IOBase source is read in 64 KiB chunks. Any other object with a read method is read whole and sent as one chunk.
  • Only a seekable io.IOBase source is retried, rewound before each attempt. Other sources get a single attempt.
  • Streamed bodies are never compressed. A file-like set_record value used to be compressed, so uploading from a file handle now sends more bytes; file.read() keeps the old behavior.
  • If the API answers 408 because the body didn't arrive within about 5 minutes, the client logs a warning suggesting a temporary file.
  • send_request of a custom transport receives bytes | Iterator[bytes] | None (async: AsyncIterator[bytes]). It only sees an iterator when a caller passes a streamable value, so existing transports keep working.

Docs and tests

A "Streaming uploads" section on the streaming concept page, plus updates to the compression and HTTP client pages, the custom transport examples, and the README. Unit tests cover StreamedRequestBody, the retry pipeline, and set_record over Impit and HTTPX2; two integration tests cover a 3 MiB upload and a record piped between stores.

Issues

✍️ Drafted by Claude Code

@vdusek vdusek added the t-tooling Issues with this label are in the ownership of the tooling team. label Sep 11, 2026
@vdusek vdusek self-assigned this Sep 11, 2026
@codecov

codecov Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.08696% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.30%. Comparing base (88770f9) to head (ffad3a4).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
src/apify_client/types.py 0.00% 7 Missing ⚠️
src/apify_client/http_clients/_streamed_body.py 98.83% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1060      +/-   ##
==========================================
+ Coverage   95.22%   95.30%   +0.08%     
==========================================
  Files          59       60       +1     
  Lines        5548     5753     +205     
==========================================
+ Hits         5283     5483     +200     
- Misses        265      270       +5     
Flag Coverage Δ
integration 91.48% <70.86%> (-0.36%) ⬇️
unit 87.74% <96.08%> (+0.36%) ⬆️

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.

@vdusek
vdusek marked this pull request as ready for review September 16, 2026 10:13
@vdusek
vdusek requested a review from szaganek as a code owner September 16, 2026 10:13
@vdusek
vdusek requested a review from Pijukatel September 16, 2026 10:13
@apify-service-account apify-service-account added the tested Temporary label used only programatically for some analytics. label Sep 16, 2026
Comment thread src/apify_client/http_clients/_base.py Outdated
Comment thread src/apify_client/http_clients/_streamed_body.py
Comment thread src/apify_client/http_clients/_streamed_body.py
Pijukatel and others added 10 commits September 25, 2026 20:29
… or __aiter__

`StreamedRequestBody.is_source` accepted only an `Iterator` or `AsyncIterator`, so a class that yields chunks from a
generator method - one with `__iter__` or `__aiter__` but no `__next__` or `__anext__` - fell through to the JSON
path and was serialized with `default=str`, sending its repr as the body.

The detection now accepts any `Iterable` or `AsyncIterable` that is not a `Collection` or a pydantic model. That
keeps every value the client uploads whole or serializes as JSON - `str`, `bytes`, `bytearray`, `memoryview`, `list`,
`tuple`, `set`, `dict` and its views, `deque`, `range`, and a model whose `__iter__` walks its fields - out of the
streaming path, while admitting the `__iter__`-only and `__aiter__`-only wrappers. An object offering both protocols
is iterated synchronously, so it works with both clients.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GnzSJqRQkQRtQVeoDuwYQx
…st-bodies

# Conflicts:
#	src/apify_client/types.py

@Pijukatel Pijukatel 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.

I ran some tests with long-lasting streams. Currently, the API will return 408 (treated as not retryable) after around 300s-360s of streaming duration. There is no setting in the client to prevent this as far as I know.

This fails the upload and, in some cases, it can leave the source stream open.

Should there be an update to the API code?

This is not an edge case, as streaming makes a lot of sense for large objects or some incoming data source - so for it to last long is not that unusual.

The least we can do is to document this limit and clearly show it in the logs when it happens, but this limit makes the feature far less useful.

@vdusek vdusek changed the title feat: Stream request bodies from files, iterators, and responses feat: Stream request bodies from files, iterables, and responses Sep 30, 2026
@vdusek
vdusek merged commit 9be215b into master Sep 30, 2026
30 checks passed
@vdusek
vdusek deleted the feat/streamed-request-bodies branch September 30, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support streaming request bodies to avoid buffering large uploads in memory

3 participants