apify-client 3.2.1b4__tar.gz → 3.2.2b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/CHANGELOG.md +9 -2
  2. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/PKG-INFO +2 -2
  3. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/README.md +1 -1
  4. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/pyproject.toml +2 -1
  5. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/pyproject.toml.orig +5 -1
  6. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_consts.py +9 -0
  7. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/actor.py +24 -6
  8. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/key_value_store.py +18 -8
  9. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/run.py +8 -2
  10. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/encoding.py +28 -31
  11. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_clients/__init__.py +2 -0
  12. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_clients/_base.py +119 -32
  13. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_clients/_httpx2.py +3 -2
  14. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_clients/_impit.py +3 -2
  15. apify_client-3.2.2b1/src/apify_client/http_clients/_streamed_body.py +355 -0
  16. apify_client-3.2.2b1/src/apify_client/types.py +98 -0
  17. apify_client-3.2.1b4/src/apify_client/types.py +0 -53
  18. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/CONTRIBUTING.md +0 -0
  19. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/LICENSE +0 -0
  20. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/__init__.py +0 -0
  21. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_apify_client.py +0 -0
  22. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_client_registry.py +0 -0
  23. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_docs.py +0 -0
  24. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_literals.py +0 -0
  25. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_logging.py +0 -0
  26. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_models.py +0 -0
  27. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_pagination.py +0 -0
  28. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/__init__.py +0 -0
  29. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/_resource_client.py +0 -0
  30. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/actor_collection.py +0 -0
  31. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/actor_env_var.py +0 -0
  32. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/actor_env_var_collection.py +0 -0
  33. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/actor_version.py +0 -0
  34. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/actor_version_collection.py +0 -0
  35. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/build.py +0 -0
  36. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/build_collection.py +0 -0
  37. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/dataset.py +0 -0
  38. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/dataset_collection.py +0 -0
  39. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/key_value_store_collection.py +0 -0
  40. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/log.py +0 -0
  41. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/request_queue.py +0 -0
  42. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/request_queue_collection.py +0 -0
  43. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/run_collection.py +0 -0
  44. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/schedule.py +0 -0
  45. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/schedule_collection.py +0 -0
  46. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/store_collection.py +0 -0
  47. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/task.py +0 -0
  48. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/task_collection.py +0 -0
  49. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/user.py +0 -0
  50. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/webhook.py +0 -0
  51. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/webhook_collection.py +0 -0
  52. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/webhook_dispatch.py +0 -0
  53. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_resource_clients/webhook_dispatch_collection.py +0 -0
  54. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_statistics.py +0 -0
  55. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_status_message_watcher.py +0 -0
  56. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_streamed_log.py +0 -0
  57. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_typeddicts.py +0 -0
  58. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/__init__.py +0 -0
  59. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/crypto.py +0 -0
  60. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/errors.py +0 -0
  61. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/http.py +0 -0
  62. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/time.py +0 -0
  63. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/_utils/try_import.py +0 -0
  64. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/errors.py +0 -0
  65. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_compressors/__init__.py +0 -0
  66. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_compressors/_base.py +0 -0
  67. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_compressors/_brotli.py +0 -0
  68. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_compressors/_gzip.py +0 -0
  69. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/http_compressors/_resolve.py +0 -0
  70. {apify_client-3.2.1b4 → apify_client-3.2.2b1}/src/apify_client/py.typed +0 -0
@@ -3,7 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
5
  <!-- git-cliff-unreleased-start -->
6
- ## 3.2.1 - **not yet released**
6
+ ## 3.2.2 - **not yet released**
7
+
8
+ ### 🚀 Features
9
+
10
+ - Stream request bodies from files, iterables, and responses ([#1060](https://github.com/apify/apify-client-python/pull/1060)) ([9be215b](https://github.com/apify/apify-client-python/commit/9be215b23a13de0abc4a255a18450cc764e7a6c7)) by [@vdusek](https://github.com/vdusek), closes [#972](https://github.com/apify/apify-client-python/issues/972)
11
+
12
+
13
+ <!-- git-cliff-unreleased-end -->
14
+ ## [3.2.1](https://github.com/apify/apify-client-python/releases/tag/v3.2.1) (2026-09-25)
7
15
 
8
16
  ### 🐛 Bug Fixes
9
17
 
@@ -12,7 +20,6 @@ All notable changes to this project will be documented in this file.
12
20
  - Make `JsonSerializable` type alias read-only ([#1072](https://github.com/apify/apify-client-python/pull/1072)) ([bd82a26](https://github.com/apify/apify-client-python/commit/bd82a2645d3c64f0e4a30de3a016c16d92d48340)) by [@Pijukatel](https://github.com/Pijukatel)
13
21
 
14
22
 
15
- <!-- git-cliff-unreleased-end -->
16
23
  ## [3.2.0](https://github.com/apify/apify-client-python/releases/tag/v3.2.0) (2026-09-03)
17
24
 
18
25
  ### 🚀 Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apify_client
3
- Version: 3.2.1b4
3
+ Version: 3.2.2b1
4
4
  Summary: Apify API client for Python
5
5
  Keywords: apify,api,client,automation,crawling,scraping
6
6
  Author: Apify Technologies s.r.o.
@@ -170,7 +170,7 @@ For a guided walkthrough — authenticating, running an Actor, and reading its r
170
170
  - **Fully typed responses** — every method returns a [Pydantic](https://docs.pydantic.dev/) model generated from the Apify OpenAPI spec, with IDE autocomplete and runtime validation ([Typed models](https://docs.apify.com/api/client/python/docs/concepts/typed-models)).
171
171
  - **Automatic retries** — exponential backoff for network errors, HTTP 429, and 5xx responses, configurable per client ([Retries](https://docs.apify.com/api/client/python/docs/concepts/retries)).
172
172
  - **Tiered timeouts** — short / medium / long tiers picked per endpoint, overridable per call ([Timeouts](https://docs.apify.com/api/client/python/docs/concepts/timeouts)).
173
- - **Pagination and streaming** — iterate datasets, key-value store keys, or live logs without manual paging or buffering ([Pagination](https://docs.apify.com/api/client/python/docs/concepts/pagination), [Streaming](https://docs.apify.com/api/client/python/docs/concepts/streaming-resources)).
173
+ - **Pagination and streaming** — iterate datasets, key-value store keys, or live logs without manual paging or buffering, and upload large records straight from a file or another stream ([Pagination](https://docs.apify.com/api/client/python/docs/concepts/pagination), [Streaming](https://docs.apify.com/api/client/python/docs/concepts/streaming-resources)).
174
174
  - **Convenience methods** — `call()`, `wait_for_finish()`, nested resource access, and other shortcuts that hide platform quirks ([Convenience methods](https://docs.apify.com/api/client/python/docs/concepts/convenience-methods)).
175
175
  - **Pluggable HTTP layer** — use the default [Impit](https://github.com/apify/impit)-based client, opt in to the built-in [HTTPX2](https://github.com/pydantic/httpx2) client, or plug in any custom implementation ([HTTP clients](https://docs.apify.com/api/client/python/docs/concepts/custom-http-clients)).
176
176
  - **Structured errors** — every API error surfaces as an [`ApifyApiError`](https://docs.apify.com/api/client/python/reference/class/ApifyApiError) with HTTP-specific subclasses for precise handling ([Error handling](https://docs.apify.com/api/client/python/docs/concepts/error-handling)).
@@ -133,7 +133,7 @@ For a guided walkthrough — authenticating, running an Actor, and reading its r
133
133
  - **Fully typed responses** — every method returns a [Pydantic](https://docs.pydantic.dev/) model generated from the Apify OpenAPI spec, with IDE autocomplete and runtime validation ([Typed models](https://docs.apify.com/api/client/python/docs/concepts/typed-models)).
134
134
  - **Automatic retries** — exponential backoff for network errors, HTTP 429, and 5xx responses, configurable per client ([Retries](https://docs.apify.com/api/client/python/docs/concepts/retries)).
135
135
  - **Tiered timeouts** — short / medium / long tiers picked per endpoint, overridable per call ([Timeouts](https://docs.apify.com/api/client/python/docs/concepts/timeouts)).
136
- - **Pagination and streaming** — iterate datasets, key-value store keys, or live logs without manual paging or buffering ([Pagination](https://docs.apify.com/api/client/python/docs/concepts/pagination), [Streaming](https://docs.apify.com/api/client/python/docs/concepts/streaming-resources)).
136
+ - **Pagination and streaming** — iterate datasets, key-value store keys, or live logs without manual paging or buffering, and upload large records straight from a file or another stream ([Pagination](https://docs.apify.com/api/client/python/docs/concepts/pagination), [Streaming](https://docs.apify.com/api/client/python/docs/concepts/streaming-resources)).
137
137
  - **Convenience methods** — `call()`, `wait_for_finish()`, nested resource access, and other shortcuts that hide platform quirks ([Convenience methods](https://docs.apify.com/api/client/python/docs/concepts/convenience-methods)).
138
138
  - **Pluggable HTTP layer** — use the default [Impit](https://github.com/apify/impit)-based client, opt in to the built-in [HTTPX2](https://github.com/pydantic/httpx2) client, or plug in any custom implementation ([HTTP clients](https://docs.apify.com/api/client/python/docs/concepts/custom-http-clients)).
139
139
  - **Structured errors** — every API error surfaces as an [`ApifyApiError`](https://docs.apify.com/api/client/python/reference/class/ApifyApiError) with HTTP-specific subclasses for precise handling ([Error handling](https://docs.apify.com/api/client/python/docs/concepts/error-handling)).
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "apify_client"
7
- version = "3.2.1b4"
7
+ version = "3.2.2b1"
8
8
  description = "Apify API client for Python"
9
9
  license = "Apache-2.0"
10
10
  license-files = ["LICENSE"]
@@ -178,6 +178,7 @@ indent-style = "space"
178
178
  addopts = "-r a --verbose --dist worksteal"
179
179
  asyncio_default_fixture_loop_scope = "function"
180
180
  asyncio_mode = "auto"
181
+ filterwarnings = ["ignore:Streaming a request body is experimental:UserWarning"]
181
182
  pythonpath = ["."]
182
183
  timeout = 1800
183
184
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "apify_client"
7
- version = "3.2.1b4"
7
+ version = "3.2.2b1"
8
8
  description = "Apify API client for Python"
9
9
  authors = [{ name = "Apify Technologies s.r.o.", email = "support@apify.com" }]
10
10
  license = "Apache-2.0"
@@ -187,6 +187,10 @@ max-branches = 18
187
187
  addopts = "-r a --verbose --dist worksteal"
188
188
  asyncio_default_fixture_loop_scope = "function"
189
189
  asyncio_mode = "auto"
190
+ filterwarnings = [
191
+ # The tests stream request bodies throughout, and the warning has a unit test of its own.
192
+ "ignore:Streaming a request body is experimental:UserWarning",
193
+ ]
190
194
  pythonpath = ["."]
191
195
  timeout = 1800
192
196
 
@@ -42,6 +42,15 @@ A smaller body already fits in a single network packet, so compressing it costs
42
42
  saving a round trip.
43
43
  """
44
44
 
45
+ STREAMED_BODY_CHUNK_SIZE = 64 * 1024
46
+ """Size of the chunks a streamed request body reads from an `io.IOBase` source, in bytes from a binary stream and in
47
+ characters from a text-mode one.
48
+
49
+ A chunk is the most of a streamed body that is in memory at once, and in the asynchronous client every chunk costs
50
+ one worker-thread hop, so the size balances memory against per-chunk overhead. It matches the buffer size the
51
+ standard library and common HTTP libraries use to copy files.
52
+ """
53
+
45
54
  ALREADY_COMPRESSED_MEDIA_TYPE_PREFIXES = ('audio/', 'image/', 'video/')
46
55
  """Media type prefixes whose payloads carry their own compression, so compressing the request body is wasted work."""
47
56
 
@@ -236,7 +236,10 @@ class ActorClient(ResourceClient):
236
236
  https://docs.apify.com/api/v2#/reference/actors/run-collection/run-actor
237
237
 
238
238
  Args:
239
- run_input: The input to pass to the Actor run.
239
+ run_input: The input to pass to the Actor run. Accepts the same values as
240
+ `KeyValueStoreClient.set_record`, including an `io.IOBase` stream such as an open file, an iterable
241
+ of byte chunks, or a streamed `HttpResponse`, which are uploaded in chunks without being held in
242
+ memory. Streaming is experimental, and its behavior may change in future versions.
240
243
  content_type: The content type of the input.
241
244
  build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
242
245
  the run uses the build specified in the default run configuration for the Actor (typically latest).
@@ -315,7 +318,10 @@ class ActorClient(ResourceClient):
315
318
  https://docs.apify.com/api/v2#/reference/actors/run-collection/run-actor
316
319
 
317
320
  Args:
318
- run_input: The input to pass to the Actor run.
321
+ run_input: The input to pass to the Actor run. Accepts the same values as
322
+ `KeyValueStoreClient.set_record`, including an `io.IOBase` stream such as an open file, an iterable
323
+ of byte chunks, or a streamed `HttpResponse`, which are uploaded in chunks without being held in
324
+ memory. Streaming is experimental, and its behavior may change in future versions.
319
325
  content_type: The content type of the input.
320
326
  build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
321
327
  the run uses the build specified in the default run configuration for the Actor (typically latest).
@@ -540,7 +546,10 @@ class ActorClient(ResourceClient):
540
546
  """Validate an input for the Actor that defines an input schema.
541
547
 
542
548
  Args:
543
- run_input: The input to validate.
549
+ run_input: The input to validate. Accepts the same values as `KeyValueStoreClient.set_record`,
550
+ including an `io.IOBase` stream such as an open file, an iterable of byte chunks, or a streamed
551
+ `HttpResponse`, which are uploaded in chunks without being held in memory. Streaming is experimental,
552
+ and its behavior may change in future versions.
544
553
  build_tag: The Actor's build tag.
545
554
  content_type: The content type of the input.
546
555
  timeout: Timeout for the API HTTP request.
@@ -738,7 +747,10 @@ class ActorClientAsync(ResourceClientAsync):
738
747
  https://docs.apify.com/api/v2#/reference/actors/run-collection/run-actor
739
748
 
740
749
  Args:
741
- run_input: The input to pass to the Actor run.
750
+ run_input: The input to pass to the Actor run. Accepts the same values as
751
+ `KeyValueStoreClientAsync.set_record`, including an `io.IOBase` stream such as an open file, an iterable
752
+ of byte chunks, or a streamed `HttpResponse`, which are uploaded in chunks without being held in
753
+ memory. Streaming is experimental, and its behavior may change in future versions.
742
754
  content_type: The content type of the input.
743
755
  build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
744
756
  the run uses the build specified in the default run configuration for the Actor (typically latest).
@@ -817,7 +829,10 @@ class ActorClientAsync(ResourceClientAsync):
817
829
  https://docs.apify.com/api/v2#/reference/actors/run-collection/run-actor
818
830
 
819
831
  Args:
820
- run_input: The input to pass to the Actor run.
832
+ run_input: The input to pass to the Actor run. Accepts the same values as
833
+ `KeyValueStoreClientAsync.set_record`, including an `io.IOBase` stream such as an open file, an iterable
834
+ of byte chunks, or a streamed `HttpResponse`, which are uploaded in chunks without being held in
835
+ memory. Streaming is experimental, and its behavior may change in future versions.
821
836
  content_type: The content type of the input.
822
837
  build: Specifies the Actor build to run. It can be either a build tag or build number. By default,
823
838
  the run uses the build specified in the default run configuration for the Actor (typically latest).
@@ -1043,7 +1058,10 @@ class ActorClientAsync(ResourceClientAsync):
1043
1058
  """Validate an input for the Actor that defines an input schema.
1044
1059
 
1045
1060
  Args:
1046
- run_input: The input to validate.
1061
+ run_input: The input to validate. Accepts the same values as `KeyValueStoreClientAsync.set_record`,
1062
+ including an `io.IOBase` stream such as an open file, an iterable of byte chunks, or a streamed
1063
+ `HttpResponse`, which are uploaded in chunks without being held in memory. Streaming is experimental,
1064
+ and its behavior may change in future versions.
1047
1065
  build_tag: The Actor's build tag.
1048
1066
  content_type: The content type of the input.
1049
1067
  timeout: Timeout for the API HTTP request.
@@ -369,14 +369,19 @@ class KeyValueStoreClient(ResourceClient):
369
369
 
370
370
  Args:
371
371
  key: The key of the record to save the value to.
372
- value: The value to save into the record.
372
+ value: The value to save into the record. An `io.IOBase` stream such as an open file, an iterable of byte
373
+ chunks, or a streamed `HttpResponse` is uploaded in chunks as it is read, without being held in memory
374
+ whole or compressed. Any other object with a callable `read` is read whole and sent uncompressed. Only
375
+ a seekable `io.IOBase` value can be retried; any other streamed value gets a single attempt. See
376
+ `StreamedRequestBody` for details. Streaming is experimental, and its behavior may change in future
377
+ versions.
373
378
  content_type: The content type of the saved value.
374
379
  content_encoding: The encoding already applied to `value`, sent as the `Content-Encoding` header. Pass it
375
380
  to upload a pre-compressed value - the client then forwards the bytes as they are instead of
376
381
  compressing them itself. The API accepts `gzip`, `br`, `deflate`, and `identity`, and stores the
377
- record exactly as uploaded, so this also becomes the encoding the record is served with. Only a
378
- bytes-like `value`, or a file-like one that reads into bytes, can carry a compression - anything
379
- else raises `TypeError` instead of being stored under a header that misdescribes it.
382
+ record exactly as uploaded, so this also becomes the encoding the record is served with. A `str`, a
383
+ JSON-serializable object, or a text-mode file cannot be carrying a compression and raises
384
+ `TypeError`; a streamed value is taken at its word, since its bytes are only seen as they are sent.
380
385
  timeout: Timeout for the API HTTP request.
381
386
  """
382
387
  value, content_type = encode_key_value_store_record_value(
@@ -802,14 +807,19 @@ class KeyValueStoreClientAsync(ResourceClientAsync):
802
807
 
803
808
  Args:
804
809
  key: The key of the record to save the value to.
805
- value: The value to save into the record.
810
+ value: The value to save into the record. An `io.IOBase` stream such as an open file, an iterable of byte
811
+ chunks, or a streamed `HttpResponse` is uploaded in chunks as it is read, without being held in memory
812
+ whole or compressed. Any other object with a callable `read` is read whole and sent uncompressed. Only
813
+ a seekable `io.IOBase` value can be retried; any other streamed value gets a single attempt. See
814
+ `StreamedRequestBody` for details. Streaming is experimental, and its behavior may change in future
815
+ versions.
806
816
  content_type: The content type of the saved value.
807
817
  content_encoding: The encoding already applied to `value`, sent as the `Content-Encoding` header. Pass it
808
818
  to upload a pre-compressed value - the client then forwards the bytes as they are instead of
809
819
  compressing them itself. The API accepts `gzip`, `br`, `deflate`, and `identity`, and stores the
810
- record exactly as uploaded, so this also becomes the encoding the record is served with. Only a
811
- bytes-like `value`, or a file-like one that reads into bytes, can carry a compression - anything
812
- else raises `TypeError` instead of being stored under a header that misdescribes it.
820
+ record exactly as uploaded, so this also becomes the encoding the record is served with. A `str`, a
821
+ JSON-serializable object, or a text-mode file cannot be carrying a compression and raises
822
+ `TypeError`; a streamed value is taken at its word, since its bytes are only seen as they are sent.
813
823
  timeout: Timeout for the API HTTP request.
814
824
  """
815
825
  value, content_type = encode_key_value_store_record_value(
@@ -180,7 +180,10 @@ class RunClient(ResourceClient):
180
180
  target_actor_build: The build of the target Actor. It can be either a build tag or build number.
181
181
  By default, the run uses the build specified in the default run configuration for the target Actor
182
182
  (typically the latest build).
183
- run_input: The input to pass to the new run.
183
+ run_input: The input to pass to the new run. Accepts the same values as
184
+ `KeyValueStoreClient.set_record`, including an `io.IOBase` stream such as an open file, an iterable
185
+ of byte chunks, or a streamed `HttpResponse`, which are uploaded in chunks without being held in
186
+ memory. Streaming is experimental, and its behavior may change in future versions.
184
187
  content_type: The content type of the input.
185
188
  timeout: Timeout for the API HTTP request.
186
189
 
@@ -608,7 +611,10 @@ class RunClientAsync(ResourceClientAsync):
608
611
  target_actor_build: The build of the target Actor. It can be either a build tag or build number.
609
612
  By default, the run uses the build specified in the default run configuration for the target Actor
610
613
  (typically the latest build).
611
- run_input: The input to pass to the new run.
614
+ run_input: The input to pass to the new run. Accepts the same values as
615
+ `KeyValueStoreClientAsync.set_record`, including an `io.IOBase` stream such as an open file, an iterable
616
+ of byte chunks, or a streamed `HttpResponse`, which are uploaded in chunks without being held in
617
+ memory. Streaming is experimental, and its behavior may change in future versions.
612
618
  content_type: The content type of the input.
613
619
  timeout: Timeout for the API HTTP request.
614
620
 
@@ -1,32 +1,35 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import io
3
4
  import json
4
5
  from base64 import b64encode
5
6
  from functools import cache
6
- from inspect import isawaitable, iscoroutine
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
9
9
  from apify_client._models import WebhookCreate, WebhookRepresentation
10
+ from apify_client.http_clients._streamed_body import StreamedRequestBody
10
11
 
11
12
  if TYPE_CHECKING:
12
- from apify_client.types import WebhooksList
13
+ from apify_client.types import StreamedBodySource, WebhooksList
13
14
 
14
15
 
15
16
  def encode_key_value_store_record_value(
16
17
  value: Any, *, content_type: str | None = None, content_encoding: str | None = None
17
- ) -> tuple[bytes | bytearray | str, str]:
18
+ ) -> tuple[bytes | bytearray | str | StreamedBodySource, str]:
18
19
  """Encode a value for storage in a key-value store record.
19
20
 
20
21
  Args:
21
- value: The value to encode. Anything exposing a callable `read` is treated as a file-like object: `read`
22
- is called with no arguments, so the value is consumed from its current position and buffered in
23
- memory whole - the object is neither rewound nor closed, and async file-like objects are rejected.
24
- Any other value is JSON-serialized unless it is already bytes or a string.
25
- content_type: The content type; if None, it's inferred from the value type.
22
+ value: The value to encode. A file-like object (anything with a callable `read`), an iterable of byte chunks,
23
+ or a streamed `HttpResponse` is returned as it is, to be streamed to the API from its current position -
24
+ the object is neither rewound nor closed. See `StreamedRequestBody.is_streamable` for the exact rules. Any
25
+ other value is JSON-serialized unless it is already bytes or a string.
26
+ content_type: The content type; if None, it's inferred from the value type. An `io.TextIOBase`, which is what
27
+ the standard library returns for a file opened in text mode, is `text/plain; charset=utf-8`; any other
28
+ streamed value is `application/octet-stream`.
26
29
  content_encoding: The encoding the caller declares the value already carries, if any. Anything other than
27
- `identity` means the value is compressed, which only a bytes-like payload can be, so any other value
28
- is rejected. The check belongs here because a file-like value has to be read before its payload type
29
- is known, and reading it a second time in the caller is not possible.
30
+ `identity` means the value is compressed, which only a bytes-like payload can be, so a string, a
31
+ JSON-serialized object, or a text-mode file is rejected. Any other streamed value is taken at its word,
32
+ since its bytes are only seen as they are sent.
30
33
 
31
34
  Returns:
32
35
  A tuple of (encoded_value, content_type).
@@ -35,29 +38,23 @@ def encode_key_value_store_record_value(
35
38
  TypeError: If the value cannot be encoded into a body the transport accepts, or if it cannot be carrying
36
39
  the declared `content_encoding`.
37
40
  """
38
- # Read file-like values into memory; the transport only accepts bytes-like bodies. Detect them by a
39
- # callable `read` (not `io.IOBase`) so duck-typed file-likes are read, not JSON-serialized. Impit exposes
40
- # no streaming `content=` API, so the value has to be buffered whole.
41
- read = getattr(value, 'read', None)
42
- if callable(read):
43
- value = read()
44
-
45
- if isawaitable(value):
46
- if iscoroutine(value):
47
- value.close() # Prevent a "coroutine was never awaited" warning.
41
+ declared_encoding = (content_encoding or '').strip().lower()
42
+ declares_compression = declared_encoding not in ('', 'identity')
43
+
44
+ if StreamedRequestBody.is_streamable(value):
45
+ is_text = isinstance(value, io.TextIOBase)
46
+ if declares_compression and is_text:
48
47
  raise TypeError(
49
- 'Async file-like objects are not supported. Await the read yourself and pass the resulting '
50
- 'bytes or string.'
48
+ f'Cannot upload a file-like value opened in text mode with `Content-Encoding: {content_encoding}`. '
49
+ 'An encoding other than `identity` declares the value is already compressed, so pass the compressed '
50
+ 'bytes, or a binary file-like object that reads them.'
51
51
  )
52
+ return (value, content_type or ('text/plain; charset=utf-8' if is_text else 'application/octet-stream'))
52
53
 
53
- if not isinstance(value, (bytes, bytearray, str)):
54
- raise TypeError(f'Reading the file-like value returned {type(value).__name__}, expected bytes or str.')
55
-
56
- # A declared compression describes bytes the caller compressed. A string, a JSON-serializable object, or a
57
- # text-mode file cannot be carrying one, and would otherwise be stored under a header that misdescribes it -
58
- # the client forwards the header untouched and never inspects the body.
59
- declared_encoding = (content_encoding or '').strip().lower()
60
- if declared_encoding not in ('', 'identity') and not isinstance(value, (bytes, bytearray)):
54
+ # A declared compression describes bytes the caller compressed. A string or a JSON-serializable object cannot
55
+ # be carrying one, and would otherwise be stored under a header that misdescribes it - the client forwards the
56
+ # header untouched and never inspects the body.
57
+ if declares_compression and not isinstance(value, (bytes, bytearray)):
61
58
  raise TypeError(
62
59
  f'Cannot upload a {type(value).__name__} value with `Content-Encoding: {content_encoding}`. An encoding '
63
60
  'other than `identity` declares the value is already compressed, so pass the compressed bytes, or a '
@@ -2,6 +2,7 @@ from apify_client._utils.try_import import install_import_hook as _install_impor
2
2
  from apify_client._utils.try_import import try_import as _try_import
3
3
  from apify_client.http_clients._base import HttpClient, HttpClientAsync, HttpResponse
4
4
  from apify_client.http_clients._impit import ImpitHttpClient, ImpitHttpClientAsync
5
+ from apify_client.http_clients._streamed_body import StreamedRequestBody
5
6
 
6
7
  _install_import_hook(__name__)
7
8
 
@@ -22,6 +23,7 @@ __all__ = [
22
23
  'HttpResponse',
23
24
  'ImpitHttpClient',
24
25
  'ImpitHttpClientAsync',
26
+ 'StreamedRequestBody',
25
27
  ]
26
28
 
27
29
  if _httpx2_import.available: