modal 1.1.2.dev32__tar.gz → 1.1.2.dev34__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 (190) hide show
  1. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/PKG-INFO +1 -1
  2. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_container_entrypoint.py +12 -7
  3. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_functions.py +17 -6
  4. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/container_io_manager.py +10 -4
  5. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/container_io_manager.pyi +6 -4
  6. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/execution_context.py +18 -2
  7. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/execution_context.pyi +4 -1
  8. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/client.pyi +2 -2
  9. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/functions.pyi +14 -10
  10. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal.egg-info/PKG-INFO +1 -1
  11. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_version/__init__.py +1 -1
  12. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/LICENSE +0 -0
  13. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/README.md +0 -0
  14. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/__init__.py +0 -0
  15. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/__main__.py +0 -0
  16. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_clustered_functions.py +0 -0
  17. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_clustered_functions.pyi +0 -0
  18. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_ipython.py +0 -0
  19. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_location.py +0 -0
  20. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_object.py +0 -0
  21. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_output.py +0 -0
  22. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_partial_function.py +0 -0
  23. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_pty.py +0 -0
  24. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_resolver.py +0 -0
  25. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_resources.py +0 -0
  26. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/__init__.py +0 -0
  27. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/asgi.py +0 -0
  28. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  29. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/telemetry.py +0 -0
  30. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_runtime/user_code_imports.py +0 -0
  31. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_serialization.py +0 -0
  32. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_traceback.py +0 -0
  33. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_tunnel.py +0 -0
  34. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_tunnel.pyi +0 -0
  35. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_type_manager.py +0 -0
  36. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/__init__.py +0 -0
  37. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/app_utils.py +0 -0
  38. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/async_utils.py +0 -0
  39. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/auth_token_manager.py +0 -0
  40. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/blob_utils.py +0 -0
  41. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/bytes_io_segment_payload.py +0 -0
  42. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/deprecation.py +0 -0
  43. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/docker_utils.py +0 -0
  44. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/function_utils.py +0 -0
  45. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/git_utils.py +0 -0
  46. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/grpc_testing.py +0 -0
  47. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/grpc_utils.py +0 -0
  48. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/hash_utils.py +0 -0
  49. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/http_utils.py +0 -0
  50. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/jwt_utils.py +0 -0
  51. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/logger.py +0 -0
  52. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/mount_utils.py +0 -0
  53. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/name_utils.py +0 -0
  54. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/package_utils.py +0 -0
  55. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/pattern_utils.py +0 -0
  56. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/rand_pb_testing.py +0 -0
  57. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/shell_utils.py +0 -0
  58. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_utils/time_utils.py +0 -0
  59. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_vendor/__init__.py +0 -0
  60. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  61. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_vendor/cloudpickle.py +0 -0
  62. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_vendor/tblib.py +0 -0
  63. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/_watcher.py +0 -0
  64. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/app.py +0 -0
  65. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/app.pyi +0 -0
  66. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/2023.12.312.txt +0 -0
  67. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/2023.12.txt +0 -0
  68. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/2024.04.txt +0 -0
  69. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/2024.10.txt +0 -0
  70. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/2025.06.txt +0 -0
  71. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/PREVIEW.txt +0 -0
  72. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/README.md +0 -0
  73. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/builder/base-images.json +0 -0
  74. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/call_graph.py +0 -0
  75. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/__init__.py +0 -0
  76. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/_download.py +0 -0
  77. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/_traceback.py +0 -0
  78. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/app.py +0 -0
  79. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/cluster.py +0 -0
  80. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/config.py +0 -0
  81. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/container.py +0 -0
  82. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/dict.py +0 -0
  83. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/entry_point.py +0 -0
  84. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/environment.py +0 -0
  85. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/import_refs.py +0 -0
  86. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/launch.py +0 -0
  87. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/network_file_system.py +0 -0
  88. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/profile.py +0 -0
  89. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/programs/__init__.py +0 -0
  90. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/programs/launch_instance_ssh.py +0 -0
  91. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/programs/run_jupyter.py +0 -0
  92. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/programs/run_marimo.py +0 -0
  93. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/programs/vscode.py +0 -0
  94. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/queues.py +0 -0
  95. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/run.py +0 -0
  96. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/secret.py +0 -0
  97. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/token.py +0 -0
  98. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/utils.py +0 -0
  99. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cli/volume.py +0 -0
  100. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/client.py +0 -0
  101. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cloud_bucket_mount.py +0 -0
  102. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cloud_bucket_mount.pyi +0 -0
  103. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cls.py +0 -0
  104. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/cls.pyi +0 -0
  105. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/config.py +0 -0
  106. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/container_process.py +0 -0
  107. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/container_process.pyi +0 -0
  108. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/dict.py +0 -0
  109. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/dict.pyi +0 -0
  110. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/environments.py +0 -0
  111. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/environments.pyi +0 -0
  112. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/exception.py +0 -0
  113. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/experimental/__init__.py +0 -0
  114. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/experimental/flash.py +0 -0
  115. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/experimental/flash.pyi +0 -0
  116. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/experimental/ipython.py +0 -0
  117. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/file_io.py +0 -0
  118. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/file_io.pyi +0 -0
  119. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/file_pattern_matcher.py +0 -0
  120. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/functions.py +0 -0
  121. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/gpu.py +0 -0
  122. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/image.py +0 -0
  123. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/image.pyi +0 -0
  124. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/io_streams.py +0 -0
  125. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/io_streams.pyi +0 -0
  126. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/mount.py +0 -0
  127. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/mount.pyi +0 -0
  128. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/network_file_system.py +0 -0
  129. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/network_file_system.pyi +0 -0
  130. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/object.py +0 -0
  131. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/object.pyi +0 -0
  132. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/output.py +0 -0
  133. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/parallel_map.py +0 -0
  134. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/parallel_map.pyi +0 -0
  135. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/partial_function.py +0 -0
  136. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/partial_function.pyi +0 -0
  137. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/proxy.py +0 -0
  138. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/proxy.pyi +0 -0
  139. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/py.typed +0 -0
  140. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/queue.py +0 -0
  141. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/queue.pyi +0 -0
  142. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/retries.py +0 -0
  143. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/runner.py +0 -0
  144. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/runner.pyi +0 -0
  145. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/running_app.py +0 -0
  146. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/sandbox.py +0 -0
  147. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/sandbox.pyi +0 -0
  148. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/schedule.py +0 -0
  149. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/scheduler_placement.py +0 -0
  150. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/secret.py +0 -0
  151. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/secret.pyi +0 -0
  152. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/serving.py +0 -0
  153. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/serving.pyi +0 -0
  154. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/snapshot.py +0 -0
  155. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/snapshot.pyi +0 -0
  156. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/stream_type.py +0 -0
  157. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/token_flow.py +0 -0
  158. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/token_flow.pyi +0 -0
  159. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/volume.py +0 -0
  160. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal/volume.pyi +0 -0
  161. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal.egg-info/SOURCES.txt +0 -0
  162. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal.egg-info/dependency_links.txt +0 -0
  163. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal.egg-info/entry_points.txt +0 -0
  164. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal.egg-info/requires.txt +0 -0
  165. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal.egg-info/top_level.txt +0 -0
  166. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_docs/__init__.py +0 -0
  167. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_docs/gen_cli_docs.py +0 -0
  168. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_docs/gen_reference_docs.py +0 -0
  169. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_docs/mdmd/__init__.py +0 -0
  170. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_docs/mdmd/mdmd.py +0 -0
  171. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_docs/mdmd/signatures.py +0 -0
  172. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/__init__.py +0 -0
  173. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/api.proto +0 -0
  174. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/api_grpc.py +0 -0
  175. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/api_pb2.py +0 -0
  176. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/api_pb2.pyi +0 -0
  177. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/api_pb2_grpc.py +0 -0
  178. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/api_pb2_grpc.pyi +0 -0
  179. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/modal_api_grpc.py +0 -0
  180. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/modal_options_grpc.py +0 -0
  181. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/options.proto +0 -0
  182. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/options_grpc.py +0 -0
  183. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/options_pb2.py +0 -0
  184. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/options_pb2.pyi +0 -0
  185. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/options_pb2_grpc.py +0 -0
  186. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/options_pb2_grpc.pyi +0 -0
  187. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_proto/py.typed +0 -0
  188. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/modal_version/__main__.py +0 -0
  189. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/pyproject.toml +0 -0
  190. {modal-1.1.2.dev32 → modal-1.1.2.dev34}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.2.dev32
3
+ Version: 1.1.2.dev34
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -185,8 +185,9 @@ def call_function(
185
185
  ):
186
186
  async def run_input_async(io_context: IOContext) -> None:
187
187
  started_at = time.time()
188
- input_ids, function_call_ids = io_context.input_ids, io_context.function_call_ids
189
- reset_context = execution_context._set_current_context_ids(input_ids, function_call_ids)
188
+ reset_context = execution_context._set_current_context_ids(
189
+ io_context.input_ids, io_context.function_call_ids, io_context.attempt_tokens
190
+ )
190
191
  async with container_io_manager.handle_input_exception.aio(io_context, started_at):
191
192
  res = io_context.call_finalized_function()
192
193
  # TODO(erikbern): any exception below shouldn't be considered a user exception
@@ -195,9 +196,11 @@ def call_function(
195
196
  raise InvalidError(f"Async generator function returned value of type {type(res)}")
196
197
 
197
198
  # Send up to this many outputs at a time.
199
+ current_function_call_id = execution_context.current_function_call_id()
200
+ assert current_function_call_id is not None # Set above.
198
201
  generator_queue: asyncio.Queue[Any] = await container_io_manager._queue_create.aio(1024)
199
202
  async with container_io_manager.generator_output_sender(
200
- function_call_ids[0],
203
+ current_function_call_id,
201
204
  io_context.finalized_function.data_format,
202
205
  generator_queue,
203
206
  ):
@@ -230,8 +233,9 @@ def call_function(
230
233
 
231
234
  def run_input_sync(io_context: IOContext) -> None:
232
235
  started_at = time.time()
233
- input_ids, function_call_ids = io_context.input_ids, io_context.function_call_ids
234
- reset_context = execution_context._set_current_context_ids(input_ids, function_call_ids)
236
+ reset_context = execution_context._set_current_context_ids(
237
+ io_context.input_ids, io_context.function_call_ids, io_context.attempt_tokens
238
+ )
235
239
  with container_io_manager.handle_input_exception(io_context, started_at):
236
240
  res = io_context.call_finalized_function()
237
241
 
@@ -241,10 +245,11 @@ def call_function(
241
245
  raise InvalidError(f"Generator function returned value of type {type(res)}")
242
246
 
243
247
  # Send up to this many outputs at a time.
248
+ current_function_call_id = execution_context.current_function_call_id()
249
+ assert current_function_call_id is not None # Set above.
244
250
  generator_queue: asyncio.Queue[Any] = container_io_manager._queue_create(1024)
245
-
246
251
  with container_io_manager.generator_output_sender(
247
- function_call_ids[0],
252
+ current_function_call_id,
248
253
  io_context.finalized_function.data_format,
249
254
  generator_queue,
250
255
  ):
@@ -217,7 +217,11 @@ class _Invocation:
217
217
  return _Invocation(stub, function_call_id, client, retry_context)
218
218
 
219
219
  async def pop_function_call_outputs(
220
- self, timeout: Optional[float], clear_on_success: bool, input_jwts: Optional[list[str]] = None
220
+ self,
221
+ index: int = 0,
222
+ timeout: Optional[float] = None,
223
+ clear_on_success: bool = False,
224
+ input_jwts: Optional[list[str]] = None,
221
225
  ) -> api_pb2.FunctionGetOutputsResponse:
222
226
  t0 = time.time()
223
227
  if timeout is None:
@@ -235,6 +239,8 @@ class _Invocation:
235
239
  clear_on_success=clear_on_success,
236
240
  requested_at=time.time(),
237
241
  input_jwts=input_jwts,
242
+ start_idx=index,
243
+ end_idx=index,
238
244
  )
239
245
  response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
240
246
  self.stub.FunctionGetOutputs,
@@ -268,6 +274,7 @@ class _Invocation:
268
274
  # waits indefinitely for a single result for the function, and clear the outputs buffer after
269
275
  item: api_pb2.FunctionGetOutputsItem = (
270
276
  await self.pop_function_call_outputs(
277
+ index=0,
271
278
  timeout=None,
272
279
  clear_on_success=True,
273
280
  input_jwts=[expected_jwt] if expected_jwt else None,
@@ -311,14 +318,16 @@ class _Invocation:
311
318
 
312
319
  await self._retry_input()
313
320
 
314
- async def poll_function(self, timeout: Optional[float] = None):
321
+ async def poll_function(self, timeout: Optional[float] = None, *, index: int = 0):
315
322
  """Waits up to timeout for a result from a function.
316
323
 
317
324
  If timeout is `None`, waits indefinitely. This function is not
318
325
  cancellation-safe.
319
326
  """
320
327
  response: api_pb2.FunctionGetOutputsResponse = await self.pop_function_call_outputs(
321
- timeout=timeout, clear_on_success=False
328
+ index=index,
329
+ timeout=timeout,
330
+ clear_on_success=False,
322
331
  )
323
332
  if len(response.outputs) == 0 and response.num_unfinished_inputs == 0:
324
333
  # if no unfinished inputs and no outputs, then function expired
@@ -1828,8 +1837,10 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1828
1837
  def _invocation(self):
1829
1838
  return _Invocation(self.client.stub, self.object_id, self.client)
1830
1839
 
1831
- async def get(self, timeout: Optional[float] = None) -> ReturnType:
1832
- """Get the result of the function call.
1840
+ async def get(self, timeout: Optional[float] = None, *, index: int = 0) -> ReturnType:
1841
+ """Get the result of the index-th input of the function call.
1842
+ `.spawn()` calls have a single output, so only specifying `index=0` is valid.
1843
+ A non-zero index is useful when your function has multiple outputs, like via `.spawn_map()`.
1833
1844
 
1834
1845
  This function waits indefinitely by default. It takes an optional
1835
1846
  `timeout` argument that specifies the maximum number of seconds to wait,
@@ -1837,7 +1848,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1837
1848
 
1838
1849
  The returned coroutine is not cancellation-safe.
1839
1850
  """
1840
- return await self._invocation().poll_function(timeout=timeout)
1851
+ return await self._invocation().poll_function(timeout=timeout, index=index)
1841
1852
 
1842
1853
  async def get_call_graph(self) -> list[InputInfo]:
1843
1854
  """Returns a structure representing the call graph from a given root
@@ -65,6 +65,7 @@ class IOContext:
65
65
  input_ids: list[str]
66
66
  retry_counts: list[int]
67
67
  function_call_ids: list[str]
68
+ attempt_tokens: list[str]
68
69
  function_inputs: list[api_pb2.FunctionInput]
69
70
  finalized_function: "modal._runtime.user_code_imports.FinalizedFunction"
70
71
 
@@ -76,6 +77,7 @@ class IOContext:
76
77
  input_ids: list[str],
77
78
  retry_counts: list[int],
78
79
  function_call_ids: list[str],
80
+ attempt_tokens: list[str],
79
81
  finalized_function: "modal._runtime.user_code_imports.FinalizedFunction",
80
82
  function_inputs: list[api_pb2.FunctionInput],
81
83
  is_batched: bool,
@@ -84,6 +86,7 @@ class IOContext:
84
86
  self.input_ids = input_ids
85
87
  self.retry_counts = retry_counts
86
88
  self.function_call_ids = function_call_ids
89
+ self.attempt_tokens = attempt_tokens
87
90
  self.finalized_function = finalized_function
88
91
  self.function_inputs = function_inputs
89
92
  self._is_batched = is_batched
@@ -94,11 +97,11 @@ class IOContext:
94
97
  cls,
95
98
  client: _Client,
96
99
  finalized_functions: dict[str, "modal._runtime.user_code_imports.FinalizedFunction"],
97
- inputs: list[tuple[str, int, str, api_pb2.FunctionInput]],
100
+ inputs: list[tuple[str, int, str, str, api_pb2.FunctionInput]],
98
101
  is_batched: bool,
99
102
  ) -> "IOContext":
100
103
  assert len(inputs) >= 1 if is_batched else len(inputs) == 1
101
- input_ids, retry_counts, function_call_ids, function_inputs = zip(*inputs)
104
+ input_ids, retry_counts, function_call_ids, attempt_tokens, function_inputs = zip(*inputs)
102
105
 
103
106
  async def _populate_input_blobs(client: _Client, input: api_pb2.FunctionInput) -> api_pb2.FunctionInput:
104
107
  # If we got a pointer to a blob, download it from S3.
@@ -120,6 +123,7 @@ class IOContext:
120
123
  cast(list[str], input_ids),
121
124
  cast(list[int], retry_counts),
122
125
  cast(list[str], function_call_ids),
126
+ cast(list[str], attempt_tokens),
123
127
  finalized_function,
124
128
  cast(list[api_pb2.FunctionInput], function_inputs),
125
129
  is_batched,
@@ -585,7 +589,7 @@ class _ContainerIOManager:
585
589
  self,
586
590
  batch_max_size: int,
587
591
  batch_wait_ms: int,
588
- ) -> AsyncIterator[list[tuple[str, int, str, api_pb2.FunctionInput]]]:
592
+ ) -> AsyncIterator[list[tuple[str, int, str, str, api_pb2.FunctionInput]]]:
589
593
  request = api_pb2.FunctionGetInputsRequest(function_id=self.function_id)
590
594
  iteration = 0
591
595
  while self._fetching_inputs:
@@ -620,7 +624,9 @@ class _ContainerIOManager:
620
624
  if item.kill_switch:
621
625
  logger.debug(f"Task {self.task_id} input kill signal input.")
622
626
  return
623
- inputs.append((item.input_id, item.retry_count, item.function_call_id, item.input))
627
+ inputs.append(
628
+ (item.input_id, item.retry_count, item.function_call_id, item.attempt_token, item.input)
629
+ )
624
630
  if item.input.final_input:
625
631
  if request.batch_max_size > 0:
626
632
  logger.debug(f"Task {self.task_id} Final input not expected in batch input stream")
@@ -27,6 +27,7 @@ class IOContext:
27
27
  input_ids: list[str]
28
28
  retry_counts: list[int]
29
29
  function_call_ids: list[str]
30
+ attempt_tokens: list[str]
30
31
  function_inputs: list[modal_proto.api_pb2.FunctionInput]
31
32
  finalized_function: modal._runtime.user_code_imports.FinalizedFunction
32
33
  _cancel_issued: bool
@@ -37,6 +38,7 @@ class IOContext:
37
38
  input_ids: list[str],
38
39
  retry_counts: list[int],
39
40
  function_call_ids: list[str],
41
+ attempt_tokens: list[str],
40
42
  finalized_function: modal._runtime.user_code_imports.FinalizedFunction,
41
43
  function_inputs: list[modal_proto.api_pb2.FunctionInput],
42
44
  is_batched: bool,
@@ -50,7 +52,7 @@ class IOContext:
50
52
  cls,
51
53
  client: modal.client._Client,
52
54
  finalized_functions: dict[str, modal._runtime.user_code_imports.FinalizedFunction],
53
- inputs: list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]],
55
+ inputs: list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]],
54
56
  is_batched: bool,
55
57
  ) -> IOContext: ...
56
58
  def set_cancel_callback(self, cb: collections.abc.Callable[[], None]): ...
@@ -166,7 +168,7 @@ class _ContainerIOManager:
166
168
  def get_max_inputs_to_fetch(self): ...
167
169
  def _generate_inputs(
168
170
  self, batch_max_size: int, batch_wait_ms: int
169
- ) -> collections.abc.AsyncIterator[list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]]]: ...
171
+ ) -> collections.abc.AsyncIterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
170
172
  def run_inputs_outputs(
171
173
  self,
172
174
  finalized_functions: dict[str, modal._runtime.user_code_imports.FinalizedFunction],
@@ -410,10 +412,10 @@ class ContainerIOManager:
410
412
  class ___generate_inputs_spec(typing_extensions.Protocol[SUPERSELF]):
411
413
  def __call__(
412
414
  self, /, batch_max_size: int, batch_wait_ms: int
413
- ) -> typing.Iterator[list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]]]: ...
415
+ ) -> typing.Iterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
414
416
  def aio(
415
417
  self, /, batch_max_size: int, batch_wait_ms: int
416
- ) -> collections.abc.AsyncIterator[list[tuple[str, int, str, modal_proto.api_pb2.FunctionInput]]]: ...
418
+ ) -> collections.abc.AsyncIterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
417
419
 
418
420
  _generate_inputs: ___generate_inputs_spec[typing_extensions.Self]
419
421
 
@@ -72,22 +72,38 @@ def current_function_call_id() -> Optional[str]:
72
72
  return None
73
73
 
74
74
 
75
- def _set_current_context_ids(input_ids: list[str], function_call_ids: list[str]) -> Callable[[], None]:
76
- assert len(input_ids) == len(function_call_ids) and len(input_ids) > 0
75
+ def current_attempt_token() -> Optional[str]:
76
+ # This ContextVar isn't useful to expose to users.
77
+ try:
78
+ return _current_attempt_token.get()
79
+ except LookupError:
80
+ return None
81
+
82
+
83
+ def _set_current_context_ids(
84
+ input_ids: list[str], function_call_ids: list[str], attempt_tokens: list[str]
85
+ ) -> Callable[[], None]:
86
+ assert len(input_ids) == len(function_call_ids) == len(attempt_tokens) and input_ids
87
+
77
88
  input_id = input_ids[0]
78
89
  function_call_id = function_call_ids[0]
90
+ attempt_token = attempt_tokens[0]
91
+
79
92
  input_token = _current_input_id.set(input_id)
80
93
  function_call_token = _current_function_call_id.set(function_call_id)
94
+ attempt_token_token = _current_attempt_token.set(attempt_token)
81
95
 
82
96
  def _reset_current_context_ids():
83
97
  _current_input_id.reset(input_token)
84
98
  _current_function_call_id.reset(function_call_token)
99
+ _current_attempt_token.reset(attempt_token_token)
85
100
 
86
101
  return _reset_current_context_ids
87
102
 
88
103
 
89
104
  _current_input_id: ContextVar = ContextVar("_current_input_id")
90
105
  _current_function_call_id: ContextVar = ContextVar("_current_function_call_id")
106
+ _current_attempt_token: ContextVar = ContextVar("_current_attempt_token")
91
107
 
92
108
  _is_currently_importing = False # we set this to True while a container is importing user code
93
109
 
@@ -68,11 +68,14 @@ def current_function_call_id() -> typing.Optional[str]:
68
68
  """
69
69
  ...
70
70
 
71
+ def current_attempt_token() -> typing.Optional[str]: ...
71
72
  def _set_current_context_ids(
72
- input_ids: list[str], function_call_ids: list[str]
73
+ input_ids: list[str], function_call_ids: list[str], attempt_tokens: list[str]
73
74
  ) -> collections.abc.Callable[[], None]: ...
74
75
  def _import_context(): ...
75
76
 
76
77
  _current_input_id: contextvars.ContextVar
77
78
 
78
79
  _current_function_call_id: contextvars.ContextVar
80
+
81
+ _current_attempt_token: contextvars.ContextVar
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.2.dev32",
36
+ version: str = "1.1.2.dev34",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.2.dev32",
167
+ version: str = "1.1.2.dev34",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -433,7 +433,7 @@ class Function(
433
433
 
434
434
  _call_generator: ___call_generator_spec[typing_extensions.Self]
435
435
 
436
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
436
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
437
437
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
438
438
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
439
439
  ...
@@ -442,7 +442,7 @@ class Function(
442
442
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
443
443
  ...
444
444
 
445
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
445
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
446
446
 
447
447
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
448
448
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -469,7 +469,7 @@ class Function(
469
469
  """
470
470
  ...
471
471
 
472
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
472
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
473
473
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
474
474
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
475
475
 
@@ -493,7 +493,7 @@ class Function(
493
493
  ...
494
494
 
495
495
  _experimental_spawn: ___experimental_spawn_spec[
496
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
496
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
497
497
  ]
498
498
 
499
499
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -502,7 +502,7 @@ class Function(
502
502
 
503
503
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
504
504
 
505
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
505
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
506
506
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
507
507
  """Calls the function with the given arguments, without waiting for the results.
508
508
 
@@ -523,7 +523,7 @@ class Function(
523
523
  """
524
524
  ...
525
525
 
526
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
526
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
527
527
 
528
528
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
529
529
  """Return the inner Python object wrapped by this Modal Function."""
@@ -746,8 +746,10 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
746
746
  def _invocation(self): ...
747
747
 
748
748
  class __get_spec(typing_extensions.Protocol[ReturnType_INNER, SUPERSELF]):
749
- def __call__(self, /, timeout: typing.Optional[float] = None) -> ReturnType_INNER:
750
- """Get the result of the function call.
749
+ def __call__(self, /, timeout: typing.Optional[float] = None, *, index: int = 0) -> ReturnType_INNER:
750
+ """Get the result of the index-th input of the function call.
751
+ `.spawn()` calls have a single output, so only specifying `index=0` is valid.
752
+ A non-zero index is useful when your function has multiple outputs, like via `.spawn_map()`.
751
753
 
752
754
  This function waits indefinitely by default. It takes an optional
753
755
  `timeout` argument that specifies the maximum number of seconds to wait,
@@ -757,8 +759,10 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
757
759
  """
758
760
  ...
759
761
 
760
- async def aio(self, /, timeout: typing.Optional[float] = None) -> ReturnType_INNER:
761
- """Get the result of the function call.
762
+ async def aio(self, /, timeout: typing.Optional[float] = None, *, index: int = 0) -> ReturnType_INNER:
763
+ """Get the result of the index-th input of the function call.
764
+ `.spawn()` calls have a single output, so only specifying `index=0` is valid.
765
+ A non-zero index is useful when your function has multiple outputs, like via `.spawn_map()`.
762
766
 
763
767
  This function waits indefinitely by default. It takes an optional
764
768
  `timeout` argument that specifies the maximum number of seconds to wait,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.2.dev32
3
+ Version: 1.1.2.dev34
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.1.2.dev32"
4
+ __version__ = "1.1.2.dev34"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes