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.
- modal/__init__.py +17 -13
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +420 -937
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -59
- modal/_resources.py +51 -0
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1036 -0
- modal/_runtime/execution_context.py +89 -0
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +134 -9
- modal/_traceback.py +47 -187
- modal/_tunnel.py +52 -16
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +479 -100
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +460 -171
- modal/_utils/grpc_testing.py +47 -31
- modal/_utils/grpc_utils.py +62 -109
- modal/_utils/hash_utils.py +61 -19
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +5 -7
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +14 -12
- modal/app.py +1003 -314
- modal/app.pyi +540 -264
- modal/call_graph.py +7 -6
- modal/cli/_download.py +63 -53
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +205 -45
- modal/cli/config.py +12 -5
- modal/cli/container.py +62 -14
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +64 -58
- modal/cli/launch.py +32 -18
- modal/cli/network_file_system.py +64 -83
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +35 -10
- modal/cli/programs/vscode.py +60 -10
- modal/cli/queues.py +131 -0
- modal/cli/run.py +234 -131
- modal/cli/secret.py +8 -7
- modal/cli/token.py +7 -2
- modal/cli/utils.py +79 -10
- modal/cli/volume.py +110 -109
- modal/client.py +250 -144
- modal/client.pyi +157 -118
- modal/cloud_bucket_mount.py +108 -34
- modal/cloud_bucket_mount.pyi +32 -38
- modal/cls.py +535 -148
- modal/cls.pyi +190 -146
- modal/config.py +41 -19
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +111 -65
- modal/dict.pyi +136 -131
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +34 -43
- modal/experimental.py +61 -2
- modal/extensions/ipython.py +5 -5
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +197 -0
- modal/functions.py +906 -911
- modal/functions.pyi +466 -430
- modal/gpu.py +57 -44
- modal/image.py +1089 -479
- modal/image.pyi +584 -228
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +314 -101
- modal/mount.pyi +241 -235
- modal/network_file_system.py +92 -92
- modal/network_file_system.pyi +152 -110
- modal/object.py +67 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +434 -0
- modal/parallel_map.pyi +75 -0
- modal/partial_function.py +282 -117
- modal/partial_function.pyi +222 -129
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +182 -65
- modal/queue.pyi +218 -118
- modal/requirements/2024.04.txt +29 -0
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +48 -7
- modal/runner.py +459 -156
- modal/runner.pyi +135 -71
- modal/running_app.py +38 -0
- modal/sandbox.py +514 -236
- modal/sandbox.pyi +397 -169
- modal/schedule.py +4 -4
- modal/scheduler_placement.py +20 -3
- modal/secret.py +56 -31
- modal/secret.pyi +62 -42
- modal/serving.py +51 -56
- modal/serving.pyi +44 -36
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +285 -157
- modal/volume.pyi +249 -184
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
- modal-0.72.11.dist-info/RECORD +174 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +5 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1288 -533
- modal_proto/api_grpc.py +856 -456
- modal_proto/api_pb2.py +2165 -1157
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1674 -855
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_entrypoint.pyi +0 -378
- modal/_container_exec.py +0 -128
- modal/_sandbox_shell.py +0 -49
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal/stub.py +0 -783
- modal/stub.pyi +0 -332
- modal-0.62.16.dist-info/RECORD +0 -198
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -262
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -659
- test/client_test.py +0 -194
- test/cls_test.py +0 -630
- test/config_test.py +0 -137
- test/conftest.py +0 -1420
- test/container_app_test.py +0 -32
- test/container_test.py +0 -1389
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -33
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -653
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -141
- test/helpers.py +0 -42
- test/image_test.py +0 -669
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -329
- test/network_file_system_test.py +0 -181
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -97
- test/resolver_test.py +0 -58
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -29
- test/secret_test.py +0 -78
- test/serialization_test.py +0 -42
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -360
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -341
- test/watcher_test.py +0 -30
- test/webhook_test.py +0 -146
- /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
- /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
- {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
|
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(
|
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(
|
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
|
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
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
async def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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.
|
73
|
-
...
|
74
|
-
|
90
|
+
def validate_partition_key(partition: typing.Optional[str]) -> bytes: ...
|
75
91
|
@classmethod
|
76
|
-
def ephemeral(
|
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(
|
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__(
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
97
|
-
def __call__(
|
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
|
-
|
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__(
|
107
|
-
|
108
|
-
|
109
|
-
async def aio(
|
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
|
115
|
-
def __call__(self,
|
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
|
-
|
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__(
|
125
|
-
|
126
|
-
|
127
|
-
|
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__(
|
134
|
-
|
135
|
-
|
136
|
-
|
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__(
|
143
|
-
|
144
|
-
|
145
|
-
|
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__(
|
152
|
-
|
153
|
-
|
154
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
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
|
-
"""
|
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}.
|
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
|