modal 1.1.2.dev44__tar.gz → 1.1.3__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.dev44 → modal-1.1.3}/PKG-INFO +1 -1
  2. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_functions.py +116 -26
  3. {modal-1.1.2.dev44 → modal-1.1.3}/modal/app.py +2 -2
  4. {modal-1.1.2.dev44 → modal-1.1.3}/modal/client.pyi +2 -10
  5. {modal-1.1.2.dev44 → modal-1.1.3}/modal/dict.py +7 -0
  6. {modal-1.1.2.dev44 → modal-1.1.3}/modal/dict.pyi +18 -0
  7. {modal-1.1.2.dev44 → modal-1.1.3}/modal/functions.pyi +49 -0
  8. {modal-1.1.2.dev44 → modal-1.1.3}/modal/image.py +2 -1
  9. {modal-1.1.2.dev44 → modal-1.1.3}/modal/image.pyi +4 -2
  10. {modal-1.1.2.dev44 → modal-1.1.3}/modal/parallel_map.py +21 -2
  11. {modal-1.1.2.dev44 → modal-1.1.3}/modal/parallel_map.pyi +3 -0
  12. {modal-1.1.2.dev44 → modal-1.1.3}/modal/queue.py +7 -0
  13. {modal-1.1.2.dev44 → modal-1.1.3}/modal/queue.pyi +18 -0
  14. {modal-1.1.2.dev44 → modal-1.1.3}/modal/secret.py +7 -0
  15. {modal-1.1.2.dev44 → modal-1.1.3}/modal/secret.pyi +18 -0
  16. {modal-1.1.2.dev44 → modal-1.1.3}/modal/volume.py +10 -0
  17. {modal-1.1.2.dev44 → modal-1.1.3}/modal/volume.pyi +21 -0
  18. {modal-1.1.2.dev44 → modal-1.1.3}/modal.egg-info/PKG-INFO +1 -1
  19. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/api.proto +8 -3
  20. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/api_pb2.py +493 -493
  21. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/api_pb2.pyi +15 -6
  22. {modal-1.1.2.dev44 → modal-1.1.3}/modal_version/__init__.py +1 -1
  23. {modal-1.1.2.dev44 → modal-1.1.3}/LICENSE +0 -0
  24. {modal-1.1.2.dev44 → modal-1.1.3}/README.md +0 -0
  25. {modal-1.1.2.dev44 → modal-1.1.3}/modal/__init__.py +0 -0
  26. {modal-1.1.2.dev44 → modal-1.1.3}/modal/__main__.py +0 -0
  27. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_clustered_functions.py +0 -0
  28. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_clustered_functions.pyi +0 -0
  29. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_container_entrypoint.py +0 -0
  30. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_ipython.py +0 -0
  31. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_location.py +0 -0
  32. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_object.py +0 -0
  33. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_output.py +0 -0
  34. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_partial_function.py +0 -0
  35. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_pty.py +0 -0
  36. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_resolver.py +0 -0
  37. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_resources.py +0 -0
  38. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/__init__.py +0 -0
  39. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/asgi.py +0 -0
  40. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/container_io_manager.py +0 -0
  41. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/container_io_manager.pyi +0 -0
  42. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/execution_context.py +0 -0
  43. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/execution_context.pyi +0 -0
  44. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  45. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/telemetry.py +0 -0
  46. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_runtime/user_code_imports.py +0 -0
  47. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_serialization.py +0 -0
  48. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_traceback.py +0 -0
  49. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_tunnel.py +0 -0
  50. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_tunnel.pyi +0 -0
  51. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_type_manager.py +0 -0
  52. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/__init__.py +0 -0
  53. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/app_utils.py +0 -0
  54. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/async_utils.py +0 -0
  55. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/auth_token_manager.py +0 -0
  56. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/blob_utils.py +0 -0
  57. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/bytes_io_segment_payload.py +0 -0
  58. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/deprecation.py +0 -0
  59. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/docker_utils.py +0 -0
  60. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/function_utils.py +0 -0
  61. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/git_utils.py +0 -0
  62. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/grpc_testing.py +0 -0
  63. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/grpc_utils.py +0 -0
  64. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/hash_utils.py +0 -0
  65. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/http_utils.py +0 -0
  66. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/jwt_utils.py +0 -0
  67. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/logger.py +0 -0
  68. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/mount_utils.py +0 -0
  69. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/name_utils.py +0 -0
  70. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/package_utils.py +0 -0
  71. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/pattern_utils.py +0 -0
  72. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/rand_pb_testing.py +0 -0
  73. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/shell_utils.py +0 -0
  74. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_utils/time_utils.py +0 -0
  75. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_vendor/__init__.py +0 -0
  76. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  77. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_vendor/cloudpickle.py +0 -0
  78. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_vendor/tblib.py +0 -0
  79. {modal-1.1.2.dev44 → modal-1.1.3}/modal/_watcher.py +0 -0
  80. {modal-1.1.2.dev44 → modal-1.1.3}/modal/app.pyi +0 -0
  81. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/2023.12.312.txt +0 -0
  82. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/2023.12.txt +0 -0
  83. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/2024.04.txt +0 -0
  84. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/2024.10.txt +0 -0
  85. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/2025.06.txt +0 -0
  86. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/PREVIEW.txt +0 -0
  87. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/README.md +0 -0
  88. {modal-1.1.2.dev44 → modal-1.1.3}/modal/builder/base-images.json +0 -0
  89. {modal-1.1.2.dev44 → modal-1.1.3}/modal/call_graph.py +0 -0
  90. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/__init__.py +0 -0
  91. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/_download.py +0 -0
  92. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/_traceback.py +0 -0
  93. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/app.py +0 -0
  94. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/cluster.py +0 -0
  95. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/config.py +0 -0
  96. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/container.py +0 -0
  97. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/dict.py +0 -0
  98. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/entry_point.py +0 -0
  99. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/environment.py +0 -0
  100. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/import_refs.py +0 -0
  101. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/launch.py +0 -0
  102. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/network_file_system.py +0 -0
  103. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/profile.py +0 -0
  104. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/programs/__init__.py +0 -0
  105. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/programs/launch_instance_ssh.py +0 -0
  106. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/programs/run_jupyter.py +0 -0
  107. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/programs/run_marimo.py +0 -0
  108. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/programs/vscode.py +0 -0
  109. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/queues.py +0 -0
  110. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/run.py +0 -0
  111. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/secret.py +0 -0
  112. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/token.py +0 -0
  113. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/utils.py +0 -0
  114. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cli/volume.py +0 -0
  115. {modal-1.1.2.dev44 → modal-1.1.3}/modal/client.py +0 -0
  116. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cloud_bucket_mount.py +0 -0
  117. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cloud_bucket_mount.pyi +0 -0
  118. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cls.py +0 -0
  119. {modal-1.1.2.dev44 → modal-1.1.3}/modal/cls.pyi +0 -0
  120. {modal-1.1.2.dev44 → modal-1.1.3}/modal/config.py +0 -0
  121. {modal-1.1.2.dev44 → modal-1.1.3}/modal/container_process.py +0 -0
  122. {modal-1.1.2.dev44 → modal-1.1.3}/modal/container_process.pyi +0 -0
  123. {modal-1.1.2.dev44 → modal-1.1.3}/modal/environments.py +0 -0
  124. {modal-1.1.2.dev44 → modal-1.1.3}/modal/environments.pyi +0 -0
  125. {modal-1.1.2.dev44 → modal-1.1.3}/modal/exception.py +0 -0
  126. {modal-1.1.2.dev44 → modal-1.1.3}/modal/experimental/__init__.py +0 -0
  127. {modal-1.1.2.dev44 → modal-1.1.3}/modal/experimental/flash.py +0 -0
  128. {modal-1.1.2.dev44 → modal-1.1.3}/modal/experimental/flash.pyi +0 -0
  129. {modal-1.1.2.dev44 → modal-1.1.3}/modal/experimental/ipython.py +0 -0
  130. {modal-1.1.2.dev44 → modal-1.1.3}/modal/file_io.py +0 -0
  131. {modal-1.1.2.dev44 → modal-1.1.3}/modal/file_io.pyi +0 -0
  132. {modal-1.1.2.dev44 → modal-1.1.3}/modal/file_pattern_matcher.py +0 -0
  133. {modal-1.1.2.dev44 → modal-1.1.3}/modal/functions.py +0 -0
  134. {modal-1.1.2.dev44 → modal-1.1.3}/modal/gpu.py +0 -0
  135. {modal-1.1.2.dev44 → modal-1.1.3}/modal/io_streams.py +0 -0
  136. {modal-1.1.2.dev44 → modal-1.1.3}/modal/io_streams.pyi +0 -0
  137. {modal-1.1.2.dev44 → modal-1.1.3}/modal/mount.py +0 -0
  138. {modal-1.1.2.dev44 → modal-1.1.3}/modal/mount.pyi +0 -0
  139. {modal-1.1.2.dev44 → modal-1.1.3}/modal/network_file_system.py +0 -0
  140. {modal-1.1.2.dev44 → modal-1.1.3}/modal/network_file_system.pyi +0 -0
  141. {modal-1.1.2.dev44 → modal-1.1.3}/modal/object.py +0 -0
  142. {modal-1.1.2.dev44 → modal-1.1.3}/modal/object.pyi +0 -0
  143. {modal-1.1.2.dev44 → modal-1.1.3}/modal/output.py +0 -0
  144. {modal-1.1.2.dev44 → modal-1.1.3}/modal/partial_function.py +0 -0
  145. {modal-1.1.2.dev44 → modal-1.1.3}/modal/partial_function.pyi +0 -0
  146. {modal-1.1.2.dev44 → modal-1.1.3}/modal/proxy.py +0 -0
  147. {modal-1.1.2.dev44 → modal-1.1.3}/modal/proxy.pyi +0 -0
  148. {modal-1.1.2.dev44 → modal-1.1.3}/modal/py.typed +0 -0
  149. {modal-1.1.2.dev44 → modal-1.1.3}/modal/retries.py +0 -0
  150. {modal-1.1.2.dev44 → modal-1.1.3}/modal/runner.py +0 -0
  151. {modal-1.1.2.dev44 → modal-1.1.3}/modal/runner.pyi +0 -0
  152. {modal-1.1.2.dev44 → modal-1.1.3}/modal/running_app.py +0 -0
  153. {modal-1.1.2.dev44 → modal-1.1.3}/modal/sandbox.py +0 -0
  154. {modal-1.1.2.dev44 → modal-1.1.3}/modal/sandbox.pyi +0 -0
  155. {modal-1.1.2.dev44 → modal-1.1.3}/modal/schedule.py +0 -0
  156. {modal-1.1.2.dev44 → modal-1.1.3}/modal/scheduler_placement.py +0 -0
  157. {modal-1.1.2.dev44 → modal-1.1.3}/modal/serving.py +0 -0
  158. {modal-1.1.2.dev44 → modal-1.1.3}/modal/serving.pyi +0 -0
  159. {modal-1.1.2.dev44 → modal-1.1.3}/modal/snapshot.py +0 -0
  160. {modal-1.1.2.dev44 → modal-1.1.3}/modal/snapshot.pyi +0 -0
  161. {modal-1.1.2.dev44 → modal-1.1.3}/modal/stream_type.py +0 -0
  162. {modal-1.1.2.dev44 → modal-1.1.3}/modal/token_flow.py +0 -0
  163. {modal-1.1.2.dev44 → modal-1.1.3}/modal/token_flow.pyi +0 -0
  164. {modal-1.1.2.dev44 → modal-1.1.3}/modal.egg-info/SOURCES.txt +0 -0
  165. {modal-1.1.2.dev44 → modal-1.1.3}/modal.egg-info/dependency_links.txt +0 -0
  166. {modal-1.1.2.dev44 → modal-1.1.3}/modal.egg-info/entry_points.txt +0 -0
  167. {modal-1.1.2.dev44 → modal-1.1.3}/modal.egg-info/requires.txt +0 -0
  168. {modal-1.1.2.dev44 → modal-1.1.3}/modal.egg-info/top_level.txt +0 -0
  169. {modal-1.1.2.dev44 → modal-1.1.3}/modal_docs/__init__.py +0 -0
  170. {modal-1.1.2.dev44 → modal-1.1.3}/modal_docs/gen_cli_docs.py +0 -0
  171. {modal-1.1.2.dev44 → modal-1.1.3}/modal_docs/gen_reference_docs.py +0 -0
  172. {modal-1.1.2.dev44 → modal-1.1.3}/modal_docs/mdmd/__init__.py +0 -0
  173. {modal-1.1.2.dev44 → modal-1.1.3}/modal_docs/mdmd/mdmd.py +0 -0
  174. {modal-1.1.2.dev44 → modal-1.1.3}/modal_docs/mdmd/signatures.py +0 -0
  175. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/__init__.py +0 -0
  176. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/api_grpc.py +0 -0
  177. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/api_pb2_grpc.py +0 -0
  178. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/api_pb2_grpc.pyi +0 -0
  179. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/modal_api_grpc.py +0 -0
  180. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/modal_options_grpc.py +0 -0
  181. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/options.proto +0 -0
  182. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/options_grpc.py +0 -0
  183. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/options_pb2.py +0 -0
  184. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/options_pb2.pyi +0 -0
  185. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/options_pb2_grpc.py +0 -0
  186. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/options_pb2_grpc.pyi +0 -0
  187. {modal-1.1.2.dev44 → modal-1.1.3}/modal_proto/py.typed +0 -0
  188. {modal-1.1.2.dev44 → modal-1.1.3}/modal_version/__main__.py +0 -0
  189. {modal-1.1.2.dev44 → modal-1.1.3}/pyproject.toml +0 -0
  190. {modal-1.1.2.dev44 → modal-1.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.2.dev44
3
+ Version: 1.1.3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -9,7 +9,7 @@ import warnings
9
9
  from collections.abc import AsyncGenerator, Sequence, Sized
10
10
  from dataclasses import dataclass
11
11
  from pathlib import PurePosixPath
12
- from typing import TYPE_CHECKING, Any, Callable, Optional, Union
12
+ from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Optional, Union
13
13
 
14
14
  import typing_extensions
15
15
  from google.protobuf.message import Message
@@ -100,6 +100,10 @@ if TYPE_CHECKING:
100
100
  import modal.partial_function
101
101
 
102
102
  MAX_INTERNAL_FAILURE_COUNT = 8
103
+ TERMINAL_STATUSES = (
104
+ api_pb2.GenericResult.GENERIC_STATUS_SUCCESS,
105
+ api_pb2.GenericResult.GENERIC_STATUS_TERMINATED,
106
+ )
103
107
 
104
108
 
105
109
  @dataclasses.dataclass
@@ -300,11 +304,7 @@ class _Invocation:
300
304
 
301
305
  while True:
302
306
  item = await self._get_single_output(ctx.input_jwt)
303
- if item.result.status in (
304
- api_pb2.GenericResult.GENERIC_STATUS_SUCCESS,
305
- api_pb2.GenericResult.GENERIC_STATUS_TERMINATED,
306
- ):
307
- # success or cancellations are "final" results
307
+ if item.result.status in TERMINAL_STATUSES:
308
308
  return await _process_result(item.result, item.data_format, self.stub, self.client)
309
309
 
310
310
  if item.result.status != api_pb2.GenericResult.GENERIC_STATUS_INTERNAL_FAILURE:
@@ -360,6 +360,43 @@ class _Invocation:
360
360
  if items_total is not None and items_received >= items_total:
361
361
  break
362
362
 
363
+ async def enumerate(self, start_index: int, end_index: int):
364
+ """Iterate over the results of the function call in the range [start_index, end_index)."""
365
+ limit = 49
366
+ current_index = start_index
367
+ while current_index < end_index:
368
+ # batch_end_indx is inclusive, so we subtract 1 to get the last index in the batch.
369
+ batch_end_index = min(current_index + limit, end_index) - 1
370
+ request = api_pb2.FunctionGetOutputsRequest(
371
+ function_call_id=self.function_call_id,
372
+ timeout=0,
373
+ last_entry_id="0-0",
374
+ clear_on_success=False,
375
+ requested_at=time.time(),
376
+ start_idx=current_index,
377
+ end_idx=batch_end_index,
378
+ )
379
+ response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
380
+ self.stub.FunctionGetOutputs,
381
+ request,
382
+ attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD,
383
+ )
384
+
385
+ outputs = list(response.outputs)
386
+ outputs.sort(key=lambda x: x.idx)
387
+ for output in outputs:
388
+ if output.idx != current_index:
389
+ break
390
+ result = await _process_result(output.result, output.data_format, self.stub, self.client)
391
+ yield output.idx, result
392
+ current_index += 1
393
+
394
+ # We're missing current_index, so we need to poll the function for the next result
395
+ if len(outputs) < (batch_end_index - current_index + 1):
396
+ result = await self.poll_function(index=current_index)
397
+ yield current_index, result
398
+ current_index += 1
399
+
363
400
 
364
401
  class _InputPlaneInvocation:
365
402
  """Internal client representation of a single-input call to a Modal Function using the input
@@ -374,6 +411,7 @@ class _InputPlaneInvocation:
374
411
  client: _Client,
375
412
  input_item: api_pb2.FunctionPutInputsItem,
376
413
  function_id: str,
414
+ retry_policy: api_pb2.FunctionRetryPolicy,
377
415
  input_plane_region: str,
378
416
  ):
379
417
  self.stub = stub
@@ -381,6 +419,7 @@ class _InputPlaneInvocation:
381
419
  self.attempt_token = attempt_token
382
420
  self.input_item = input_item
383
421
  self.function_id = function_id
422
+ self.retry_policy = retry_policy
384
423
  self.input_plane_region = input_plane_region
385
424
 
386
425
  @staticmethod
@@ -416,11 +455,15 @@ class _InputPlaneInvocation:
416
455
  response = await retry_transient_errors(stub.AttemptStart, request, metadata=metadata)
417
456
  attempt_token = response.attempt_token
418
457
 
419
- return _InputPlaneInvocation(stub, attempt_token, client, input_item, function_id, input_plane_region)
458
+ return _InputPlaneInvocation(
459
+ stub, attempt_token, client, input_item, function_id, response.retry_policy, input_plane_region
460
+ )
420
461
 
421
462
  async def run_function(self) -> Any:
463
+ # User errors including timeouts are managed by the user-specified retry policy.
464
+ user_retry_manager = RetryManager(self.retry_policy)
465
+
422
466
  # This will retry when the server returns GENERIC_STATUS_INTERNAL_FAILURE, i.e. lost inputs or worker preemption
423
- # TODO(ryan): add logic to retry for user defined retry policy
424
467
  internal_failure_count = 0
425
468
  while True:
426
469
  await_request = api_pb2.AttemptAwaitRequest(
@@ -437,32 +480,48 @@ class _InputPlaneInvocation:
437
480
  )
438
481
 
439
482
  if await_response.HasField("output"):
483
+ if await_response.output.result.status in TERMINAL_STATUSES:
484
+ return await _process_result(
485
+ await_response.output.result, await_response.output.data_format, self.client.stub, self.client
486
+ )
487
+
440
488
  if await_response.output.result.status == api_pb2.GenericResult.GENERIC_STATUS_INTERNAL_FAILURE:
441
489
  internal_failure_count += 1
442
490
  # Limit the number of times we retry
443
491
  if internal_failure_count < MAX_INTERNAL_FAILURE_COUNT:
444
492
  # For system failures on the server, we retry immediately,
445
493
  # and the failure does not count towards the retry policy.
446
- retry_request = api_pb2.AttemptRetryRequest(
447
- function_id=self.function_id,
448
- parent_input_id=current_input_id() or "",
449
- input=self.input_item,
450
- attempt_token=self.attempt_token,
451
- )
452
- # TODO(ryan): Add exponential backoff?
453
- retry_response = await retry_transient_errors(
454
- self.stub.AttemptRetry,
455
- retry_request,
456
- metadata=metadata,
457
- )
458
- self.attempt_token = retry_response.attempt_token
494
+ self.attempt_token = await self._retry_input(metadata)
459
495
  continue
460
496
 
461
- control_plane_stub = self.client.stub
462
- # Note: Blob download is done on the control plane stub, not the input plane stub!
463
- return await _process_result(
464
- await_response.output.result, await_response.output.data_format, control_plane_stub, self.client
465
- )
497
+ # We add delays between retries for non-internal failures.
498
+ delay_ms = user_retry_manager.get_delay_ms()
499
+ if delay_ms is None:
500
+ # No more retries either because we reached the retry limit or user didn't set a retry policy
501
+ # and the limit defaulted to 0.
502
+ # An unsuccessful status should raise an error when it's converted to an exception.
503
+ # Note: Blob download is done on the control plane stub not the input plane stub!
504
+ return await _process_result(
505
+ await_response.output.result, await_response.output.data_format, self.client.stub, self.client
506
+ )
507
+ await asyncio.sleep(delay_ms / 1000)
508
+
509
+ await self._retry_input(metadata)
510
+
511
+ async def _retry_input(self, metadata: list[tuple[str, str]]) -> str:
512
+ retry_request = api_pb2.AttemptRetryRequest(
513
+ function_id=self.function_id,
514
+ parent_input_id=current_input_id() or "",
515
+ input=self.input_item,
516
+ attempt_token=self.attempt_token,
517
+ )
518
+ # TODO(ryan): Add exponential backoff?
519
+ retry_response = await retry_transient_errors(
520
+ self.stub.AttemptRetry,
521
+ retry_request,
522
+ metadata=metadata,
523
+ )
524
+ return retry_response.attempt_token
466
525
 
467
526
  async def run_generator(self):
468
527
  items_received = 0
@@ -1865,6 +1924,36 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1865
1924
  """
1866
1925
  return await self._invocation().poll_function(timeout=timeout, index=index)
1867
1926
 
1927
+ @live_method_gen
1928
+ async def iter(self, *, start: int = 0, end: Optional[int] = None) -> AsyncIterator[ReturnType]:
1929
+ """Iterate in-order over the results of the function call.
1930
+
1931
+ Optionally, specify a range [start, end) to iterate over.
1932
+
1933
+ Example:
1934
+ ```python
1935
+ @app.function()
1936
+ def my_func(a):
1937
+ return a ** 2
1938
+
1939
+
1940
+ @app.local_entrypoint()
1941
+ def main():
1942
+ fc = my_func.spawn_map([1, 2, 3, 4])
1943
+ assert list(fc.iter()) == [1, 4, 9, 16]
1944
+ assert list(fc.iter(start=1, end=3)) == [4, 9]
1945
+ ```
1946
+
1947
+ If `end` is not provided, it will iterate over all results.
1948
+ """
1949
+ num_inputs = await self.num_inputs()
1950
+ if end is None:
1951
+ end = num_inputs
1952
+ if start < 0 or end > num_inputs:
1953
+ raise ValueError(f"Invalid index range: {start} to {end} for {num_inputs} inputs")
1954
+ async for _, item in self._invocation().enumerate(start_index=start, end_index=end):
1955
+ yield item
1956
+
1868
1957
  async def get_call_graph(self) -> list[InputInfo]:
1869
1958
  """Returns a structure representing the call graph from a given root
1870
1959
  call ID, along with the status of execution for each node.
@@ -1926,6 +2015,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1926
2015
  fc: _FunctionCall[Any] = _FunctionCall._from_loader(_load, rep, hydrate_lazily=True)
1927
2016
  # We already know the object ID, so we can set it directly
1928
2017
  fc._object_id = function_call_id
2018
+ fc._client = client
1929
2019
  return fc
1930
2020
 
1931
2021
  @staticmethod
@@ -641,7 +641,7 @@ class _App:
641
641
  scaledown_window: Optional[int] = None, # Max time (in seconds) a container can remain idle while scaling down.
642
642
  proxy: Optional[_Proxy] = None, # Reference to a Modal Proxy to use in front of this function.
643
643
  retries: Optional[Union[int, Retries]] = None, # Number of times to retry each input in case of failure.
644
- timeout: int = 300, # Maximum execution time of the function in seconds.
644
+ timeout: int = 300, # Maximum execution time in seconds.
645
645
  name: Optional[str] = None, # Sets the Modal name of the function within the app
646
646
  is_generator: Optional[
647
647
  bool
@@ -869,7 +869,7 @@ class _App:
869
869
  scaledown_window: Optional[int] = None, # Max time (in seconds) a container can remain idle while scaling down.
870
870
  proxy: Optional[_Proxy] = None, # Reference to a Modal Proxy to use in front of this function.
871
871
  retries: Optional[Union[int, Retries]] = None, # Number of times to retry each input in case of failure.
872
- timeout: int = 300, # Maximum execution time of the function in seconds.
872
+ timeout: int = 300, # Maximum execution time in seconds; applies independently to startup and each input.
873
873
  cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
874
874
  region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
875
875
  enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
@@ -29,11 +29,7 @@ class _Client:
29
29
  _snapshotted: bool
30
30
 
31
31
  def __init__(
32
- self,
33
- server_url: str,
34
- client_type: int,
35
- credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.2.dev44",
32
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.1.3"
37
33
  ):
38
34
  """mdmd:hidden
39
35
  The Modal client object is not intended to be instantiated directly by users.
@@ -160,11 +156,7 @@ class Client:
160
156
  _snapshotted: bool
161
157
 
162
158
  def __init__(
163
- self,
164
- server_url: str,
165
- client_type: int,
166
- credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.2.dev44",
159
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.1.3"
168
160
  ):
169
161
  """mdmd:hidden
170
162
  The Modal client object is not intended to be instantiated directly by users.
@@ -81,6 +81,8 @@ class _DictManager:
81
81
  Note that this method does not return a local instance of the Dict. You can use
82
82
  `modal.Dict.from_name` to perform a lookup after creation.
83
83
 
84
+ Added in v1.1.2.
85
+
84
86
  """
85
87
  check_object_name(name, "Dict")
86
88
  client = await _Client.from_env() if client is None else client
@@ -132,6 +134,8 @@ class _DictManager:
132
134
  dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
133
135
  ```
134
136
 
137
+ Added in v1.1.2.
138
+
135
139
  """
136
140
  client = await _Client.from_env() if client is None else client
137
141
  if max_objects is not None and max_objects < 0:
@@ -192,6 +196,9 @@ class _DictManager:
192
196
  ```python notest
193
197
  await modal.Dict.objects.delete("my-dict", environment_name="dev")
194
198
  ```
199
+
200
+ Added in v1.1.2.
201
+
195
202
  """
196
203
  try:
197
204
  obj = await _Dict.from_name(name, environment_name=environment_name).hydrate(client)
@@ -66,6 +66,8 @@ class _DictManager:
66
66
 
67
67
  Note that this method does not return a local instance of the Dict. You can use
68
68
  `modal.Dict.from_name` to perform a lookup after creation.
69
+
70
+ Added in v1.1.2.
69
71
  """
70
72
  ...
71
73
 
@@ -98,6 +100,8 @@ class _DictManager:
98
100
  ```python
99
101
  dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
100
102
  ```
103
+
104
+ Added in v1.1.2.
101
105
  """
102
106
  ...
103
107
 
@@ -125,6 +129,8 @@ class _DictManager:
125
129
  ```python notest
126
130
  await modal.Dict.objects.delete("my-dict", environment_name="dev")
127
131
  ```
132
+
133
+ Added in v1.1.2.
128
134
  """
129
135
  ...
130
136
 
@@ -167,6 +173,8 @@ class DictManager:
167
173
 
168
174
  Note that this method does not return a local instance of the Dict. You can use
169
175
  `modal.Dict.from_name` to perform a lookup after creation.
176
+
177
+ Added in v1.1.2.
170
178
  """
171
179
  ...
172
180
 
@@ -202,6 +210,8 @@ class DictManager:
202
210
 
203
211
  Note that this method does not return a local instance of the Dict. You can use
204
212
  `modal.Dict.from_name` to perform a lookup after creation.
213
+
214
+ Added in v1.1.2.
205
215
  """
206
216
  ...
207
217
 
@@ -238,6 +248,8 @@ class DictManager:
238
248
  ```python
239
249
  dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
240
250
  ```
251
+
252
+ Added in v1.1.2.
241
253
  """
242
254
  ...
243
255
 
@@ -271,6 +283,8 @@ class DictManager:
271
283
  ```python
272
284
  dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
273
285
  ```
286
+
287
+ Added in v1.1.2.
274
288
  """
275
289
  ...
276
290
 
@@ -302,6 +316,8 @@ class DictManager:
302
316
  ```python notest
303
317
  await modal.Dict.objects.delete("my-dict", environment_name="dev")
304
318
  ```
319
+
320
+ Added in v1.1.2.
305
321
  """
306
322
  ...
307
323
 
@@ -330,6 +346,8 @@ class DictManager:
330
346
  ```python notest
331
347
  await modal.Dict.objects.delete("my-dict", environment_name="dev")
332
348
  ```
349
+
350
+ Added in v1.1.2.
333
351
  """
334
352
  ...
335
353
 
@@ -788,6 +788,55 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
788
788
 
789
789
  get: __get_spec[modal._functions.ReturnType, typing_extensions.Self]
790
790
 
791
+ class __iter_spec(typing_extensions.Protocol[ReturnType_INNER, SUPERSELF]):
792
+ def __call__(self, /, *, start: int = 0, end: typing.Optional[int] = None) -> typing.Iterator[ReturnType_INNER]:
793
+ """Iterate in-order over the results of the function call.
794
+
795
+ Optionally, specify a range [start, end) to iterate over.
796
+
797
+ Example:
798
+ ```python
799
+ @app.function()
800
+ def my_func(a):
801
+ return a ** 2
802
+
803
+
804
+ @app.local_entrypoint()
805
+ def main():
806
+ fc = my_func.spawn_map([1, 2, 3, 4])
807
+ assert list(fc.iter()) == [1, 4, 9, 16]
808
+ assert list(fc.iter(start=1, end=3)) == [4, 9]
809
+ ```
810
+
811
+ If `end` is not provided, it will iterate over all results.
812
+ """
813
+ ...
814
+
815
+ def aio(self, /, *, start: int = 0, end: typing.Optional[int] = None) -> typing.AsyncIterator[ReturnType_INNER]:
816
+ """Iterate in-order over the results of the function call.
817
+
818
+ Optionally, specify a range [start, end) to iterate over.
819
+
820
+ Example:
821
+ ```python
822
+ @app.function()
823
+ def my_func(a):
824
+ return a ** 2
825
+
826
+
827
+ @app.local_entrypoint()
828
+ def main():
829
+ fc = my_func.spawn_map([1, 2, 3, 4])
830
+ assert list(fc.iter()) == [1, 4, 9, 16]
831
+ assert list(fc.iter(start=1, end=3)) == [4, 9]
832
+ ```
833
+
834
+ If `end` is not provided, it will iterate over all results.
835
+ """
836
+ ...
837
+
838
+ iter: __iter_spec[modal._functions.ReturnType, typing_extensions.Self]
839
+
791
840
  class __get_call_graph_spec(typing_extensions.Protocol[SUPERSELF]):
792
841
  def __call__(self, /) -> list[modal.call_graph.InputInfo]:
793
842
  """Returns a structure representing the call graph from a given root
@@ -1356,7 +1356,8 @@ class _Image(_Object, type_prefix="im"):
1356
1356
  image = modal.Image.debian_slim().uv_sync()
1357
1357
  ```
1358
1358
 
1359
- The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context.
1359
+ The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context. The
1360
+ `uv_project_dir` is relative to the current working directory of where `modal` is called.
1360
1361
 
1361
1362
  Added in v1.1.0.
1362
1363
  """
@@ -524,7 +524,8 @@ class _Image(modal._object._Object):
524
524
  image = modal.Image.debian_slim().uv_sync()
525
525
  ```
526
526
 
527
- The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context.
527
+ The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context. The
528
+ `uv_project_dir` is relative to the current working directory of where `modal` is called.
528
529
 
529
530
  Added in v1.1.0.
530
531
  """
@@ -1368,7 +1369,8 @@ class Image(modal.object.Object):
1368
1369
  image = modal.Image.debian_slim().uv_sync()
1369
1370
  ```
1370
1371
 
1371
- The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context.
1372
+ The `pyproject.toml` and `uv.lock` in `uv_project_dir` are automatically added to the build context. The
1373
+ `uv_project_dir` is relative to the current working directory of where `modal` is called.
1372
1374
 
1373
1375
  Added in v1.1.0.
1374
1376
  """
@@ -673,9 +673,11 @@ async def _map_invocation_inputplane(
673
673
  # any reason).
674
674
  max_inputs_outstanding = MAX_INPUTS_OUTSTANDING_DEFAULT
675
675
 
676
- # Input plane does not yet return a retry policy. So we currently disable retries.
676
+ # Set a default retry policy to construct an instance of _MapItemsManager.
677
+ # We'll update the retry policy with the actual user-specified retry policy
678
+ # from the server in the first MapStartOrContinue response.
677
679
  retry_policy = api_pb2.FunctionRetryPolicy(
678
- retries=0, # Input plane does not yet return a retry policy. So only retry server failures for now.
680
+ retries=0,
679
681
  initial_delay_ms=1000,
680
682
  max_delay_ms=1000,
681
683
  backoff_coefficient=1.0,
@@ -771,10 +773,17 @@ async def _map_invocation_inputplane(
771
773
 
772
774
  map_items_manager.handle_put_continue_response(response_items_idx_tuple)
773
775
 
776
+ # Set the function call id and actual retry policy with the data from the first response.
777
+ # This conditional is skipped for subsequent iterations of this for-loop.
774
778
  if function_call_id is None:
775
779
  function_call_id = response.function_call_id
776
780
  function_call_id_received.set()
777
781
  max_inputs_outstanding = response.max_inputs_outstanding or MAX_INPUTS_OUTSTANDING_DEFAULT
782
+ map_items_manager.set_retry_policy(response.retry_policy)
783
+ # Update the retry policy for the first batch of inputs.
784
+ # Subsequent batches will have the correct user-specified retry policy
785
+ # set by the updated _MapItemsManager.
786
+ map_items_manager.update_items_retry_policy(response.retry_policy)
778
787
  yield
779
788
 
780
789
  async def check_lost_inputs():
@@ -1475,6 +1484,9 @@ class _MapItemContext:
1475
1484
  retry_count=self.retry_manager.retry_count,
1476
1485
  )
1477
1486
 
1487
+ def set_retry_policy(self, retry_policy: api_pb2.FunctionRetryPolicy):
1488
+ self.retry_manager = RetryManager(retry_policy)
1489
+
1478
1490
  def handle_retry_response(self, input_jwt: str):
1479
1491
  self.input_jwt.set_result(input_jwt)
1480
1492
  self.state = _MapItemState.WAITING_FOR_OUTPUT
@@ -1511,6 +1523,9 @@ class _MapItemsManager:
1511
1523
  self._sync_client_retries_enabled = sync_client_retries_enabled
1512
1524
  self._is_input_plane_instance = is_input_plane_instance
1513
1525
 
1526
+ def set_retry_policy(self, retry_policy: api_pb2.FunctionRetryPolicy):
1527
+ self._retry_policy = retry_policy
1528
+
1514
1529
  async def add_items(self, items: list[api_pb2.FunctionPutInputsItem]):
1515
1530
  for item in items:
1516
1531
  # acquire semaphore to limit the number of inputs in progress
@@ -1540,6 +1555,10 @@ class _MapItemsManager:
1540
1555
  async def prepare_items_for_retry(self, retriable_idxs: list[int]) -> list[api_pb2.FunctionRetryInputsItem]:
1541
1556
  return [await self._item_context[idx].prepare_item_for_retry() for idx in retriable_idxs]
1542
1557
 
1558
+ def update_items_retry_policy(self, retry_policy: api_pb2.FunctionRetryPolicy):
1559
+ for ctx in self._item_context.values():
1560
+ ctx.set_retry_policy(retry_policy)
1561
+
1543
1562
  def get_input_jwts_waiting_for_output(self) -> list[str]:
1544
1563
  """
1545
1564
  Returns a list of input_jwts for inputs that are waiting for output.
@@ -406,6 +406,7 @@ class _MapItemContext:
406
406
  ...
407
407
 
408
408
  async def prepare_item_for_retry(self) -> modal_proto.api_pb2.FunctionRetryInputsItem: ...
409
+ def set_retry_policy(self, retry_policy: modal_proto.api_pb2.FunctionRetryPolicy): ...
409
410
  def handle_retry_response(self, input_jwt: str): ...
410
411
  async def create_map_start_or_continue_item(self, idx: int) -> modal_proto.api_pb2.MapStartOrContinueItem: ...
411
412
 
@@ -422,11 +423,13 @@ class _MapItemsManager:
422
423
  """Initialize self. See help(type(self)) for accurate signature."""
423
424
  ...
424
425
 
426
+ def set_retry_policy(self, retry_policy: modal_proto.api_pb2.FunctionRetryPolicy): ...
425
427
  async def add_items(self, items: list[modal_proto.api_pb2.FunctionPutInputsItem]): ...
426
428
  async def add_items_inputplane(self, items: list[modal_proto.api_pb2.MapStartOrContinueItem]): ...
427
429
  async def prepare_items_for_retry(
428
430
  self, retriable_idxs: list[int]
429
431
  ) -> list[modal_proto.api_pb2.FunctionRetryInputsItem]: ...
432
+ def update_items_retry_policy(self, retry_policy: modal_proto.api_pb2.FunctionRetryPolicy): ...
430
433
  def get_input_jwts_waiting_for_output(self) -> list[str]:
431
434
  """Returns a list of input_jwts for inputs that are waiting for output."""
432
435
  ...
@@ -79,6 +79,8 @@ class _QueueManager:
79
79
  Note that this method does not return a local instance of the Queue. You can use
80
80
  `modal.Queue.from_name` to perform a lookup after creation.
81
81
 
82
+ Added in v1.1.2.
83
+
82
84
  """
83
85
  check_object_name(name, "Queue")
84
86
  client = await _Client.from_env() if client is None else client
@@ -130,6 +132,8 @@ class _QueueManager:
130
132
  queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
131
133
  ```
132
134
 
135
+ Added in v1.1.2.
136
+
133
137
  """
134
138
  client = await _Client.from_env() if client is None else client
135
139
  if max_objects is not None and max_objects < 0:
@@ -190,6 +194,9 @@ class _QueueManager:
190
194
  ```python notest
191
195
  await modal.Queue.objects.delete("my-queue", environment_name="dev")
192
196
  ```
197
+
198
+ Added in v1.1.2.
199
+
193
200
  """
194
201
  try:
195
202
  obj = await _Queue.from_name(name, environment_name=environment_name).hydrate(client)
@@ -64,6 +64,8 @@ class _QueueManager:
64
64
 
65
65
  Note that this method does not return a local instance of the Queue. You can use
66
66
  `modal.Queue.from_name` to perform a lookup after creation.
67
+
68
+ Added in v1.1.2.
67
69
  """
68
70
  ...
69
71
 
@@ -96,6 +98,8 @@ class _QueueManager:
96
98
  ```python
97
99
  queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
98
100
  ```
101
+
102
+ Added in v1.1.2.
99
103
  """
100
104
  ...
101
105
 
@@ -123,6 +127,8 @@ class _QueueManager:
123
127
  ```python notest
124
128
  await modal.Queue.objects.delete("my-queue", environment_name="dev")
125
129
  ```
130
+
131
+ Added in v1.1.2.
126
132
  """
127
133
  ...
128
134
 
@@ -165,6 +171,8 @@ class QueueManager:
165
171
 
166
172
  Note that this method does not return a local instance of the Queue. You can use
167
173
  `modal.Queue.from_name` to perform a lookup after creation.
174
+
175
+ Added in v1.1.2.
168
176
  """
169
177
  ...
170
178
 
@@ -200,6 +208,8 @@ class QueueManager:
200
208
 
201
209
  Note that this method does not return a local instance of the Queue. You can use
202
210
  `modal.Queue.from_name` to perform a lookup after creation.
211
+
212
+ Added in v1.1.2.
203
213
  """
204
214
  ...
205
215
 
@@ -236,6 +246,8 @@ class QueueManager:
236
246
  ```python
237
247
  queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
238
248
  ```
249
+
250
+ Added in v1.1.2.
239
251
  """
240
252
  ...
241
253
 
@@ -269,6 +281,8 @@ class QueueManager:
269
281
  ```python
270
282
  queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
271
283
  ```
284
+
285
+ Added in v1.1.2.
272
286
  """
273
287
  ...
274
288
 
@@ -300,6 +314,8 @@ class QueueManager:
300
314
  ```python notest
301
315
  await modal.Queue.objects.delete("my-queue", environment_name="dev")
302
316
  ```
317
+
318
+ Added in v1.1.2.
303
319
  """
304
320
  ...
305
321
 
@@ -328,6 +344,8 @@ class QueueManager:
328
344
  ```python notest
329
345
  await modal.Queue.objects.delete("my-queue", environment_name="dev")
330
346
  ```
347
+
348
+ Added in v1.1.2.
331
349
  """
332
350
  ...
333
351