modal 0.62.16__py3-none-any.whl → 0.72.11__py3-none-any.whl

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 (220) hide show
  1. modal/__init__.py +17 -13
  2. modal/__main__.py +41 -3
  3. modal/_clustered_functions.py +80 -0
  4. modal/_clustered_functions.pyi +22 -0
  5. modal/_container_entrypoint.py +420 -937
  6. modal/_ipython.py +3 -13
  7. modal/_location.py +17 -10
  8. modal/_output.py +243 -99
  9. modal/_pty.py +2 -2
  10. modal/_resolver.py +55 -59
  11. modal/_resources.py +51 -0
  12. modal/_runtime/__init__.py +1 -0
  13. modal/_runtime/asgi.py +519 -0
  14. modal/_runtime/container_io_manager.py +1036 -0
  15. modal/_runtime/execution_context.py +89 -0
  16. modal/_runtime/telemetry.py +169 -0
  17. modal/_runtime/user_code_imports.py +356 -0
  18. modal/_serialization.py +134 -9
  19. modal/_traceback.py +47 -187
  20. modal/_tunnel.py +52 -16
  21. modal/_tunnel.pyi +19 -36
  22. modal/_utils/app_utils.py +3 -17
  23. modal/_utils/async_utils.py +479 -100
  24. modal/_utils/blob_utils.py +157 -186
  25. modal/_utils/bytes_io_segment_payload.py +97 -0
  26. modal/_utils/deprecation.py +89 -0
  27. modal/_utils/docker_utils.py +98 -0
  28. modal/_utils/function_utils.py +460 -171
  29. modal/_utils/grpc_testing.py +47 -31
  30. modal/_utils/grpc_utils.py +62 -109
  31. modal/_utils/hash_utils.py +61 -19
  32. modal/_utils/http_utils.py +39 -9
  33. modal/_utils/logger.py +2 -1
  34. modal/_utils/mount_utils.py +34 -16
  35. modal/_utils/name_utils.py +58 -0
  36. modal/_utils/package_utils.py +14 -1
  37. modal/_utils/pattern_utils.py +205 -0
  38. modal/_utils/rand_pb_testing.py +5 -7
  39. modal/_utils/shell_utils.py +15 -49
  40. modal/_vendor/a2wsgi_wsgi.py +62 -72
  41. modal/_vendor/cloudpickle.py +1 -1
  42. modal/_watcher.py +14 -12
  43. modal/app.py +1003 -314
  44. modal/app.pyi +540 -264
  45. modal/call_graph.py +7 -6
  46. modal/cli/_download.py +63 -53
  47. modal/cli/_traceback.py +200 -0
  48. modal/cli/app.py +205 -45
  49. modal/cli/config.py +12 -5
  50. modal/cli/container.py +62 -14
  51. modal/cli/dict.py +128 -0
  52. modal/cli/entry_point.py +26 -13
  53. modal/cli/environment.py +40 -9
  54. modal/cli/import_refs.py +64 -58
  55. modal/cli/launch.py +32 -18
  56. modal/cli/network_file_system.py +64 -83
  57. modal/cli/profile.py +1 -1
  58. modal/cli/programs/run_jupyter.py +35 -10
  59. modal/cli/programs/vscode.py +60 -10
  60. modal/cli/queues.py +131 -0
  61. modal/cli/run.py +234 -131
  62. modal/cli/secret.py +8 -7
  63. modal/cli/token.py +7 -2
  64. modal/cli/utils.py +79 -10
  65. modal/cli/volume.py +110 -109
  66. modal/client.py +250 -144
  67. modal/client.pyi +157 -118
  68. modal/cloud_bucket_mount.py +108 -34
  69. modal/cloud_bucket_mount.pyi +32 -38
  70. modal/cls.py +535 -148
  71. modal/cls.pyi +190 -146
  72. modal/config.py +41 -19
  73. modal/container_process.py +177 -0
  74. modal/container_process.pyi +82 -0
  75. modal/dict.py +111 -65
  76. modal/dict.pyi +136 -131
  77. modal/environments.py +106 -5
  78. modal/environments.pyi +77 -25
  79. modal/exception.py +34 -43
  80. modal/experimental.py +61 -2
  81. modal/extensions/ipython.py +5 -5
  82. modal/file_io.py +537 -0
  83. modal/file_io.pyi +235 -0
  84. modal/file_pattern_matcher.py +197 -0
  85. modal/functions.py +906 -911
  86. modal/functions.pyi +466 -430
  87. modal/gpu.py +57 -44
  88. modal/image.py +1089 -479
  89. modal/image.pyi +584 -228
  90. modal/io_streams.py +434 -0
  91. modal/io_streams.pyi +122 -0
  92. modal/mount.py +314 -101
  93. modal/mount.pyi +241 -235
  94. modal/network_file_system.py +92 -92
  95. modal/network_file_system.pyi +152 -110
  96. modal/object.py +67 -36
  97. modal/object.pyi +166 -143
  98. modal/output.py +63 -0
  99. modal/parallel_map.py +434 -0
  100. modal/parallel_map.pyi +75 -0
  101. modal/partial_function.py +282 -117
  102. modal/partial_function.pyi +222 -129
  103. modal/proxy.py +15 -12
  104. modal/proxy.pyi +3 -8
  105. modal/queue.py +182 -65
  106. modal/queue.pyi +218 -118
  107. modal/requirements/2024.04.txt +29 -0
  108. modal/requirements/2024.10.txt +16 -0
  109. modal/requirements/README.md +21 -0
  110. modal/requirements/base-images.json +22 -0
  111. modal/retries.py +48 -7
  112. modal/runner.py +459 -156
  113. modal/runner.pyi +135 -71
  114. modal/running_app.py +38 -0
  115. modal/sandbox.py +514 -236
  116. modal/sandbox.pyi +397 -169
  117. modal/schedule.py +4 -4
  118. modal/scheduler_placement.py +20 -3
  119. modal/secret.py +56 -31
  120. modal/secret.pyi +62 -42
  121. modal/serving.py +51 -56
  122. modal/serving.pyi +44 -36
  123. modal/stream_type.py +15 -0
  124. modal/token_flow.py +5 -3
  125. modal/token_flow.pyi +37 -32
  126. modal/volume.py +285 -157
  127. modal/volume.pyi +249 -184
  128. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
  129. modal-0.72.11.dist-info/RECORD +174 -0
  130. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
  131. modal_docs/gen_reference_docs.py +3 -1
  132. modal_docs/mdmd/mdmd.py +0 -1
  133. modal_docs/mdmd/signatures.py +5 -2
  134. modal_global_objects/images/base_images.py +28 -0
  135. modal_global_objects/mounts/python_standalone.py +2 -2
  136. modal_proto/__init__.py +1 -1
  137. modal_proto/api.proto +1288 -533
  138. modal_proto/api_grpc.py +856 -456
  139. modal_proto/api_pb2.py +2165 -1157
  140. modal_proto/api_pb2.pyi +8859 -0
  141. modal_proto/api_pb2_grpc.py +1674 -855
  142. modal_proto/api_pb2_grpc.pyi +1416 -0
  143. modal_proto/modal_api_grpc.py +149 -0
  144. modal_proto/modal_options_grpc.py +3 -0
  145. modal_proto/options_pb2.pyi +20 -0
  146. modal_proto/options_pb2_grpc.pyi +7 -0
  147. modal_proto/py.typed +0 -0
  148. modal_version/__init__.py +1 -1
  149. modal_version/_version_generated.py +2 -2
  150. modal/_asgi.py +0 -370
  151. modal/_container_entrypoint.pyi +0 -378
  152. modal/_container_exec.py +0 -128
  153. modal/_sandbox_shell.py +0 -49
  154. modal/shared_volume.py +0 -23
  155. modal/shared_volume.pyi +0 -24
  156. modal/stub.py +0 -783
  157. modal/stub.pyi +0 -332
  158. modal-0.62.16.dist-info/RECORD +0 -198
  159. modal_global_objects/images/conda.py +0 -15
  160. modal_global_objects/images/debian_slim.py +0 -15
  161. modal_global_objects/images/micromamba.py +0 -15
  162. test/__init__.py +0 -1
  163. test/aio_test.py +0 -12
  164. test/async_utils_test.py +0 -262
  165. test/blob_test.py +0 -67
  166. test/cli_imports_test.py +0 -149
  167. test/cli_test.py +0 -659
  168. test/client_test.py +0 -194
  169. test/cls_test.py +0 -630
  170. test/config_test.py +0 -137
  171. test/conftest.py +0 -1420
  172. test/container_app_test.py +0 -32
  173. test/container_test.py +0 -1389
  174. test/cpu_test.py +0 -23
  175. test/decorator_test.py +0 -85
  176. test/deprecation_test.py +0 -34
  177. test/dict_test.py +0 -33
  178. test/e2e_test.py +0 -68
  179. test/error_test.py +0 -7
  180. test/function_serialization_test.py +0 -32
  181. test/function_test.py +0 -653
  182. test/function_utils_test.py +0 -101
  183. test/gpu_test.py +0 -159
  184. test/grpc_utils_test.py +0 -141
  185. test/helpers.py +0 -42
  186. test/image_test.py +0 -669
  187. test/live_reload_test.py +0 -80
  188. test/lookup_test.py +0 -70
  189. test/mdmd_test.py +0 -329
  190. test/mount_test.py +0 -162
  191. test/mounted_files_test.py +0 -329
  192. test/network_file_system_test.py +0 -181
  193. test/notebook_test.py +0 -66
  194. test/object_test.py +0 -41
  195. test/package_utils_test.py +0 -25
  196. test/queue_test.py +0 -97
  197. test/resolver_test.py +0 -58
  198. test/retries_test.py +0 -67
  199. test/runner_test.py +0 -85
  200. test/sandbox_test.py +0 -191
  201. test/schedule_test.py +0 -15
  202. test/scheduler_placement_test.py +0 -29
  203. test/secret_test.py +0 -78
  204. test/serialization_test.py +0 -42
  205. test/stub_composition_test.py +0 -10
  206. test/stub_test.py +0 -360
  207. test/test_asgi_wrapper.py +0 -234
  208. test/token_flow_test.py +0 -18
  209. test/traceback_test.py +0 -135
  210. test/tunnel_test.py +0 -29
  211. test/utils_test.py +0 -88
  212. test/version_test.py +0 -14
  213. test/volume_test.py +0 -341
  214. test/watcher_test.py +0 -30
  215. test/webhook_test.py +0 -146
  216. /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
  217. /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
  218. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
  219. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
  220. {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
modal/queue.pyi CHANGED
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import modal.client
2
3
  import modal.object
3
4
  import synchronicity.combined_types
@@ -5,171 +6,270 @@ import typing
5
6
  import typing_extensions
6
7
 
7
8
  class _Queue(modal.object._Object):
9
+ def __init__(self): ...
8
10
  @staticmethod
9
- def new():
10
- ...
11
-
12
- def __init__(self):
13
- ...
14
-
15
- @staticmethod
16
- def validate_partition_key(partition: typing.Union[str, None]) -> bytes:
17
- ...
18
-
11
+ def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
19
12
  @classmethod
20
- def ephemeral(cls: typing.Type[_Queue], client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None, _heartbeat_sleep: float = 300) -> typing.AsyncContextManager[_Queue]:
21
- ...
22
-
13
+ def ephemeral(
14
+ cls: type[_Queue],
15
+ client: typing.Optional[modal.client._Client] = None,
16
+ environment_name: typing.Optional[str] = None,
17
+ _heartbeat_sleep: float = 300,
18
+ ) -> typing.AsyncContextManager[_Queue]: ...
23
19
  @staticmethod
24
- def from_name(label: str, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _Queue:
25
- ...
26
-
20
+ def from_name(
21
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
22
+ ) -> _Queue: ...
27
23
  @staticmethod
28
- def persisted(label: str, namespace=1, environment_name: typing.Union[str, None] = None) -> _Queue:
29
- ...
30
-
24
+ async def lookup(
25
+ name: str,
26
+ namespace=1,
27
+ client: typing.Optional[modal.client._Client] = None,
28
+ environment_name: typing.Optional[str] = None,
29
+ create_if_missing: bool = False,
30
+ ) -> _Queue: ...
31
31
  @staticmethod
32
- async def lookup(label: str, namespace=1, client: typing.Union[modal.client._Client, None] = None, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _Queue:
33
- ...
34
-
35
- async def _get_nonblocking(self, partition: typing.Union[str, None], n_values: int) -> typing.List[typing.Any]:
36
- ...
37
-
38
- async def _get_blocking(self, partition: typing.Union[str, None], timeout: typing.Union[float, None], n_values: int) -> typing.List[typing.Any]:
39
- ...
40
-
41
- async def get(self, block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> typing.Union[typing.Any, None]:
42
- ...
43
-
44
- async def get_many(self, n_values: int, block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> typing.List[typing.Any]:
45
- ...
46
-
47
- async def put(self, v: typing.Any, block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> None:
48
- ...
49
-
50
- async def put_many(self, vs: typing.List[typing.Any], block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> None:
51
- ...
52
-
53
- async def _put_many_blocking(self, partition: typing.Union[str, None], vs: typing.List[typing.Any], timeout: typing.Union[float, None] = None):
54
- ...
55
-
56
- async def _put_many_nonblocking(self, partition: typing.Union[str, None], vs: typing.List[typing.Any]):
57
- ...
58
-
59
- async def len(self, *, partition: typing.Union[str, None] = None) -> int:
60
- ...
61
-
32
+ async def delete(
33
+ name: str,
34
+ *,
35
+ client: typing.Optional[modal.client._Client] = None,
36
+ environment_name: typing.Optional[str] = None,
37
+ ): ...
38
+ async def _get_nonblocking(self, partition: typing.Optional[str], n_values: int) -> list[typing.Any]: ...
39
+ async def _get_blocking(
40
+ self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
41
+ ) -> list[typing.Any]: ...
42
+ async def clear(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None: ...
43
+ async def get(
44
+ self, block: bool = True, timeout: typing.Optional[float] = None, *, partition: typing.Optional[str] = None
45
+ ) -> typing.Optional[typing.Any]: ...
46
+ async def get_many(
47
+ self,
48
+ n_values: int,
49
+ block: bool = True,
50
+ timeout: typing.Optional[float] = None,
51
+ *,
52
+ partition: typing.Optional[str] = None,
53
+ ) -> list[typing.Any]: ...
54
+ async def put(
55
+ self,
56
+ v: typing.Any,
57
+ block: bool = True,
58
+ timeout: typing.Optional[float] = None,
59
+ *,
60
+ partition: typing.Optional[str] = None,
61
+ partition_ttl: int = 86400,
62
+ ) -> None: ...
63
+ async def put_many(
64
+ self,
65
+ vs: list[typing.Any],
66
+ block: bool = True,
67
+ timeout: typing.Optional[float] = None,
68
+ *,
69
+ partition: typing.Optional[str] = None,
70
+ partition_ttl: int = 86400,
71
+ ) -> None: ...
72
+ async def _put_many_blocking(
73
+ self,
74
+ partition: typing.Optional[str],
75
+ partition_ttl: int,
76
+ vs: list[typing.Any],
77
+ timeout: typing.Optional[float] = None,
78
+ ): ...
79
+ async def _put_many_nonblocking(
80
+ self, partition: typing.Optional[str], partition_ttl: int, vs: list[typing.Any]
81
+ ): ...
82
+ async def len(self, *, partition: typing.Optional[str] = None, total: bool = False) -> int: ...
83
+ def iterate(
84
+ self, *, partition: typing.Optional[str] = None, item_poll_timeout: float = 0.0
85
+ ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
62
86
 
63
87
  class Queue(modal.object.Object):
64
- def __init__(self):
65
- ...
66
-
67
- @staticmethod
68
- def new():
69
- ...
70
-
88
+ def __init__(self): ...
71
89
  @staticmethod
72
- def validate_partition_key(partition: typing.Union[str, None]) -> bytes:
73
- ...
74
-
90
+ def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
75
91
  @classmethod
76
- def ephemeral(cls: typing.Type[Queue], client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None, _heartbeat_sleep: float = 300) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Queue]:
77
- ...
78
-
92
+ def ephemeral(
93
+ cls: type[Queue],
94
+ client: typing.Optional[modal.client.Client] = None,
95
+ environment_name: typing.Optional[str] = None,
96
+ _heartbeat_sleep: float = 300,
97
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Queue]: ...
79
98
  @staticmethod
80
- def from_name(label: str, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> Queue:
81
- ...
82
-
83
- @staticmethod
84
- def persisted(label: str, namespace=1, environment_name: typing.Union[str, None] = None) -> Queue:
85
- ...
99
+ def from_name(
100
+ name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
101
+ ) -> Queue: ...
86
102
 
87
103
  class __lookup_spec(typing_extensions.Protocol):
88
- def __call__(self, label: str, namespace=1, client: typing.Union[modal.client.Client, None] = None, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> Queue:
89
- ...
90
-
91
- async def aio(self, *args, **kwargs) -> Queue:
92
- ...
104
+ def __call__(
105
+ self,
106
+ name: str,
107
+ namespace=1,
108
+ client: typing.Optional[modal.client.Client] = None,
109
+ environment_name: typing.Optional[str] = None,
110
+ create_if_missing: bool = False,
111
+ ) -> Queue: ...
112
+ async def aio(
113
+ self,
114
+ name: str,
115
+ namespace=1,
116
+ client: typing.Optional[modal.client.Client] = None,
117
+ environment_name: typing.Optional[str] = None,
118
+ create_if_missing: bool = False,
119
+ ) -> Queue: ...
93
120
 
94
121
  lookup: __lookup_spec
95
122
 
96
- class ___get_nonblocking_spec(typing_extensions.Protocol):
97
- def __call__(self, partition: typing.Union[str, None], n_values: int) -> typing.List[typing.Any]:
98
- ...
123
+ class __delete_spec(typing_extensions.Protocol):
124
+ def __call__(
125
+ self,
126
+ name: str,
127
+ *,
128
+ client: typing.Optional[modal.client.Client] = None,
129
+ environment_name: typing.Optional[str] = None,
130
+ ): ...
131
+ async def aio(
132
+ self,
133
+ name: str,
134
+ *,
135
+ client: typing.Optional[modal.client.Client] = None,
136
+ environment_name: typing.Optional[str] = None,
137
+ ): ...
138
+
139
+ delete: __delete_spec
99
140
 
100
- async def aio(self, *args, **kwargs) -> typing.List[typing.Any]:
101
- ...
141
+ class ___get_nonblocking_spec(typing_extensions.Protocol):
142
+ def __call__(self, partition: typing.Optional[str], n_values: int) -> list[typing.Any]: ...
143
+ async def aio(self, partition: typing.Optional[str], n_values: int) -> list[typing.Any]: ...
102
144
 
103
145
  _get_nonblocking: ___get_nonblocking_spec
104
146
 
105
147
  class ___get_blocking_spec(typing_extensions.Protocol):
106
- def __call__(self, partition: typing.Union[str, None], timeout: typing.Union[float, None], n_values: int) -> typing.List[typing.Any]:
107
- ...
108
-
109
- async def aio(self, *args, **kwargs) -> typing.List[typing.Any]:
110
- ...
148
+ def __call__(
149
+ self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
150
+ ) -> list[typing.Any]: ...
151
+ async def aio(
152
+ self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
153
+ ) -> list[typing.Any]: ...
111
154
 
112
155
  _get_blocking: ___get_blocking_spec
113
156
 
114
- class __get_spec(typing_extensions.Protocol):
115
- def __call__(self, block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> typing.Union[typing.Any, None]:
116
- ...
157
+ class __clear_spec(typing_extensions.Protocol):
158
+ def __call__(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None: ...
159
+ async def aio(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None: ...
117
160
 
118
- async def aio(self, *args, **kwargs) -> typing.Union[typing.Any, None]:
119
- ...
161
+ clear: __clear_spec
162
+
163
+ class __get_spec(typing_extensions.Protocol):
164
+ def __call__(
165
+ self, block: bool = True, timeout: typing.Optional[float] = None, *, partition: typing.Optional[str] = None
166
+ ) -> typing.Optional[typing.Any]: ...
167
+ async def aio(
168
+ self, block: bool = True, timeout: typing.Optional[float] = None, *, partition: typing.Optional[str] = None
169
+ ) -> typing.Optional[typing.Any]: ...
120
170
 
121
171
  get: __get_spec
122
172
 
123
173
  class __get_many_spec(typing_extensions.Protocol):
124
- def __call__(self, n_values: int, block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> typing.List[typing.Any]:
125
- ...
126
-
127
- async def aio(self, *args, **kwargs) -> typing.List[typing.Any]:
128
- ...
174
+ def __call__(
175
+ self,
176
+ n_values: int,
177
+ block: bool = True,
178
+ timeout: typing.Optional[float] = None,
179
+ *,
180
+ partition: typing.Optional[str] = None,
181
+ ) -> list[typing.Any]: ...
182
+ async def aio(
183
+ self,
184
+ n_values: int,
185
+ block: bool = True,
186
+ timeout: typing.Optional[float] = None,
187
+ *,
188
+ partition: typing.Optional[str] = None,
189
+ ) -> list[typing.Any]: ...
129
190
 
130
191
  get_many: __get_many_spec
131
192
 
132
193
  class __put_spec(typing_extensions.Protocol):
133
- def __call__(self, v: typing.Any, block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> None:
134
- ...
135
-
136
- async def aio(self, *args, **kwargs) -> None:
137
- ...
194
+ def __call__(
195
+ self,
196
+ v: typing.Any,
197
+ block: bool = True,
198
+ timeout: typing.Optional[float] = None,
199
+ *,
200
+ partition: typing.Optional[str] = None,
201
+ partition_ttl: int = 86400,
202
+ ) -> None: ...
203
+ async def aio(
204
+ self,
205
+ v: typing.Any,
206
+ block: bool = True,
207
+ timeout: typing.Optional[float] = None,
208
+ *,
209
+ partition: typing.Optional[str] = None,
210
+ partition_ttl: int = 86400,
211
+ ) -> None: ...
138
212
 
139
213
  put: __put_spec
140
214
 
141
215
  class __put_many_spec(typing_extensions.Protocol):
142
- def __call__(self, vs: typing.List[typing.Any], block: bool = True, timeout: typing.Union[float, None] = None, *, partition: typing.Union[str, None] = None) -> None:
143
- ...
144
-
145
- async def aio(self, *args, **kwargs) -> None:
146
- ...
216
+ def __call__(
217
+ self,
218
+ vs: list[typing.Any],
219
+ block: bool = True,
220
+ timeout: typing.Optional[float] = None,
221
+ *,
222
+ partition: typing.Optional[str] = None,
223
+ partition_ttl: int = 86400,
224
+ ) -> None: ...
225
+ async def aio(
226
+ self,
227
+ vs: list[typing.Any],
228
+ block: bool = True,
229
+ timeout: typing.Optional[float] = None,
230
+ *,
231
+ partition: typing.Optional[str] = None,
232
+ partition_ttl: int = 86400,
233
+ ) -> None: ...
147
234
 
148
235
  put_many: __put_many_spec
149
236
 
150
237
  class ___put_many_blocking_spec(typing_extensions.Protocol):
151
- def __call__(self, partition: typing.Union[str, None], vs: typing.List[typing.Any], timeout: typing.Union[float, None] = None):
152
- ...
153
-
154
- async def aio(self, *args, **kwargs):
155
- ...
238
+ def __call__(
239
+ self,
240
+ partition: typing.Optional[str],
241
+ partition_ttl: int,
242
+ vs: list[typing.Any],
243
+ timeout: typing.Optional[float] = None,
244
+ ): ...
245
+ async def aio(
246
+ self,
247
+ partition: typing.Optional[str],
248
+ partition_ttl: int,
249
+ vs: list[typing.Any],
250
+ timeout: typing.Optional[float] = None,
251
+ ): ...
156
252
 
157
253
  _put_many_blocking: ___put_many_blocking_spec
158
254
 
159
255
  class ___put_many_nonblocking_spec(typing_extensions.Protocol):
160
- def __call__(self, partition: typing.Union[str, None], vs: typing.List[typing.Any]):
161
- ...
162
-
163
- async def aio(self, *args, **kwargs):
164
- ...
256
+ def __call__(self, partition: typing.Optional[str], partition_ttl: int, vs: list[typing.Any]): ...
257
+ async def aio(self, partition: typing.Optional[str], partition_ttl: int, vs: list[typing.Any]): ...
165
258
 
166
259
  _put_many_nonblocking: ___put_many_nonblocking_spec
167
260
 
168
261
  class __len_spec(typing_extensions.Protocol):
169
- def __call__(self, *, partition: typing.Union[str, None] = None) -> int:
170
- ...
171
-
172
- async def aio(self, *args, **kwargs) -> int:
173
- ...
262
+ def __call__(self, *, partition: typing.Optional[str] = None, total: bool = False) -> int: ...
263
+ async def aio(self, *, partition: typing.Optional[str] = None, total: bool = False) -> int: ...
174
264
 
175
265
  len: __len_spec
266
+
267
+ class __iterate_spec(typing_extensions.Protocol):
268
+ def __call__(
269
+ self, *, partition: typing.Optional[str] = None, item_poll_timeout: float = 0.0
270
+ ) -> typing.Generator[typing.Any, None, None]: ...
271
+ def aio(
272
+ self, *, partition: typing.Optional[str] = None, item_poll_timeout: float = 0.0
273
+ ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
274
+
275
+ iterate: __iterate_spec
@@ -0,0 +1,29 @@
1
+ aiohttp==3.9.3
2
+ aiosignal==1.3.1
3
+ aiostream==0.5.2
4
+ annotated-types==0.6.0
5
+ anyio==4.3.0
6
+ async-timeout==4.0.3 ; python_version < "3.11"
7
+ attrs==23.2.0
8
+ certifi==2024.2.2
9
+ exceptiongroup==1.2.0 ; python_version < "3.11"
10
+ fastapi==0.110.0
11
+ frozenlist==1.4.1
12
+ grpclib==0.4.7
13
+ h2==4.1.0
14
+ hpack==4.0.0
15
+ hyperframe==6.0.1
16
+ idna==3.6
17
+ markdown-it-py==3.0.0
18
+ mdurl==0.1.2
19
+ multidict==6.0.5
20
+ protobuf==4.25.3
21
+ pydantic==2.6.4
22
+ pydantic_core==2.16.3
23
+ Pygments==2.17.2
24
+ python-multipart==0.0.9
25
+ rich==13.7.1
26
+ sniffio==1.3.1
27
+ starlette==0.36.3
28
+ typing_extensions==4.10.0
29
+ yarl==1.9.4
@@ -0,0 +1,16 @@
1
+ aiohappyeyeballs==2.4.3
2
+ aiohttp==3.10.8
3
+ aiosignal==1.3.1
4
+ async-timeout==4.0.3 ; python_version < "3.11"
5
+ attrs==24.2.0
6
+ certifi==2024.8.30
7
+ frozenlist==1.4.1
8
+ grpclib==0.4.7
9
+ h2==4.1.0
10
+ hpack==4.0.0
11
+ hyperframe==6.0.1
12
+ idna==3.10
13
+ multidict==6.1.0
14
+ protobuf>=3.20,<6
15
+ typing_extensions==4.12.2
16
+ yarl==1.13.1
@@ -0,0 +1,21 @@
1
+ # Modal Image builder configuration
2
+
3
+ This directory contains `modal.Image` specifications that vary across
4
+ "image builder" versions.
5
+
6
+ The `base-images.json` file specifies the versions used for Modal's
7
+ various `Image` constructor methods.
8
+
9
+ The versioned requirements files enumerate the dependencies needed by
10
+ the Modal client library when it is running inside a Modal container.
11
+
12
+ The container requirements are a subset of the dependencies required by the
13
+ client for local operation (i.e., to run or deploy Modal apps). Additionally,
14
+ we aim to pin specific versions rather than allowing a range as we do for the
15
+ installation dependencies.
16
+
17
+ From version `2024.04`, the requirements specify the entire dependency tree,
18
+ and not just the first-order dependencies.
19
+
20
+ Note that for `2023.12`, there is a separate requirements file that is used for
21
+ Python 3.12.
@@ -0,0 +1,22 @@
1
+ {
2
+ "debian": {
3
+ "2024.10": "bookworm",
4
+ "2024.04": "bookworm",
5
+ "2023.12": "bullseye"
6
+ },
7
+ "python": {
8
+ "2024.10": ["3.9.20", "3.10.15", "3.11.10", "3.12.6", "3.13.0"],
9
+ "2024.04": ["3.9.19", "3.10.14", "3.11.8", "3.12.2"],
10
+ "2023.12": ["3.9.15", "3.10.8", "3.11.0", "3.12.1"]
11
+ },
12
+ "micromamba": {
13
+ "2024.10": "1.5.10",
14
+ "2024.04": "1.5.8",
15
+ "2023.12": "1.3.1"
16
+ },
17
+ "package_tools": {
18
+ "2024.10": "pip wheel uv",
19
+ "2024.04": "pip wheel uv",
20
+ "2023.12": "pip"
21
+ }
22
+ }
modal/retries.py CHANGED
@@ -1,10 +1,14 @@
1
1
  # Copyright Modal Labs 2022
2
+ import asyncio
2
3
  from datetime import timedelta
3
4
 
4
5
  from modal_proto import api_pb2
5
6
 
6
7
  from .exception import InvalidError
7
8
 
9
+ MIN_INPUT_RETRY_DELAY_MS = 1000
10
+ MAX_INPUT_RETRY_DELAY_MS = 24 * 60 * 60 * 1000
11
+
8
12
 
9
13
  class Retries:
10
14
  """Adds a retry policy to a Modal function.
@@ -13,17 +17,17 @@ class Retries:
13
17
 
14
18
  ```python
15
19
  import modal
16
- stub = modal.Stub()
20
+ app = modal.App()
17
21
 
18
22
  # Basic configuration.
19
23
  # This sets a policy of max 4 retries with 1-second delay between failures.
20
- @stub.function(retries=4)
24
+ @app.function(retries=4)
21
25
  def f():
22
26
  pass
23
27
 
24
28
 
25
29
  # Fixed-interval retries with 3-second delay between failures.
26
- @stub.function(
30
+ @app.function(
27
31
  retries=modal.Retries(
28
32
  max_retries=2,
29
33
  backoff_coefficient=1.0,
@@ -35,7 +39,7 @@ class Retries:
35
39
 
36
40
 
37
41
  # Exponential backoff, with retry delay doubling after each failure.
38
- @stub.function(
42
+ @app.function(
39
43
  retries=modal.Retries(
40
44
  max_retries=4,
41
45
  backoff_coefficient=2.0,
@@ -53,14 +57,16 @@ class Retries:
53
57
  # The maximum number of retries that can be made in the presence of failures.
54
58
  max_retries: int,
55
59
  # Coefficent controlling how much the retry delay increases each retry attempt.
56
- # A backoff coefficient of 1.0 creates fixed-delay retries where the delay period will always equal the initial delay.
60
+ # A backoff coefficient of 1.0 creates fixed-delay where the delay period always equals the initial delay.
57
61
  backoff_coefficient: float = 2.0,
58
62
  # Number of seconds that must elapse before the first retry occurs.
59
63
  initial_delay: float = 1.0,
60
64
  # Maximum length of retry delay in seconds, preventing the delay from growing infinitely.
61
65
  max_delay: float = 60.0,
62
66
  ):
63
- """Construct a new retries policy, supporting exponential and fixed-interval delays via a backoff coefficient."""
67
+ """
68
+ Construct a new retries policy, supporting exponential and fixed-interval delays via a backoff coefficient.
69
+ """
64
70
  if max_retries < 0:
65
71
  raise InvalidError(f"Invalid retries number: {max_retries}. Function retries must be non-negative.")
66
72
 
@@ -84,7 +90,8 @@ class Retries:
84
90
 
85
91
  if not 1.0 <= backoff_coefficient <= 10.0:
86
92
  raise InvalidError(
87
- f"Invalid backoff_coefficient: {backoff_coefficient}. Coefficient must be between 1.0 (fixed-interval backoff) and 10.0"
93
+ f"Invalid backoff_coefficient: {backoff_coefficient}. "
94
+ "Coefficient must be between 1.0 (fixed-interval backoff) and 10.0"
88
95
  )
89
96
 
90
97
  self.max_retries = max_retries
@@ -100,3 +107,37 @@ class Retries:
100
107
  initial_delay_ms=self.initial_delay // timedelta(milliseconds=1),
101
108
  max_delay_ms=self.max_delay // timedelta(milliseconds=1),
102
109
  )
110
+
111
+
112
+ class RetryManager:
113
+ """
114
+ Helper class to apply the specified retry policy.
115
+ """
116
+
117
+ def __init__(self, retry_policy: api_pb2.FunctionRetryPolicy):
118
+ self.retry_policy = retry_policy
119
+ self.attempt_count = 0
120
+
121
+ async def raise_or_sleep(self, exc: Exception):
122
+ """
123
+ Raises an exception if the maximum retry count has been reached, otherwise sleeps for calculated delay.
124
+ """
125
+ self.attempt_count += 1
126
+ if self.attempt_count > self.retry_policy.retries:
127
+ raise exc
128
+ delay_ms = self._retry_delay_ms(self.attempt_count, self.retry_policy)
129
+ await asyncio.sleep(delay_ms / 1000)
130
+
131
+ @staticmethod
132
+ def _retry_delay_ms(attempt_count: int, retry_policy: api_pb2.FunctionRetryPolicy) -> float:
133
+ """
134
+ Computes the amount of time to sleep before retrying based on the backend_coefficient and initial_delay_ms args.
135
+ """
136
+ if attempt_count < 1:
137
+ raise ValueError(f"Cannot compute retry delay. attempt_count must be at least 1, but was {attempt_count}")
138
+ delay_ms = retry_policy.initial_delay_ms * (retry_policy.backoff_coefficient ** (attempt_count - 1))
139
+ if delay_ms < MIN_INPUT_RETRY_DELAY_MS:
140
+ return MIN_INPUT_RETRY_DELAY_MS
141
+ if delay_ms > MAX_INPUT_RETRY_DELAY_MS:
142
+ return MAX_INPUT_RETRY_DELAY_MS
143
+ return delay_ms