modal 0.67.6__py3-none-any.whl → 0.67.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/_clustered_functions.py +2 -2
- modal/_clustered_functions.pyi +2 -2
- modal/_container_entrypoint.py +5 -4
- modal/_output.py +29 -28
- modal/_pty.py +2 -2
- modal/_resolver.py +6 -5
- modal/_resources.py +3 -3
- modal/_runtime/asgi.py +7 -6
- modal/_runtime/container_io_manager.py +22 -26
- modal/_runtime/execution_context.py +2 -2
- modal/_runtime/telemetry.py +1 -2
- modal/_runtime/user_code_imports.py +12 -14
- modal/_serialization.py +3 -7
- modal/_traceback.py +5 -5
- modal/_tunnel.py +4 -3
- modal/_tunnel.pyi +2 -2
- modal/_utils/async_utils.py +8 -15
- modal/_utils/blob_utils.py +4 -3
- modal/_utils/function_utils.py +14 -10
- modal/_utils/grpc_testing.py +7 -6
- modal/_utils/grpc_utils.py +2 -3
- modal/_utils/hash_utils.py +2 -2
- modal/_utils/mount_utils.py +5 -4
- modal/_utils/package_utils.py +2 -3
- modal/_utils/pattern_matcher.py +6 -6
- modal/_utils/rand_pb_testing.py +3 -3
- modal/_utils/shell_utils.py +2 -1
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +8 -7
- modal/app.py +29 -34
- modal/app.pyi +102 -97
- modal/call_graph.py +6 -6
- modal/cli/_download.py +3 -2
- modal/cli/_traceback.py +4 -4
- modal/cli/app.py +4 -4
- modal/cli/container.py +4 -4
- modal/cli/dict.py +1 -1
- modal/cli/environment.py +2 -3
- modal/cli/launch.py +2 -2
- modal/cli/network_file_system.py +1 -1
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +2 -2
- modal/cli/programs/vscode.py +3 -3
- modal/cli/queues.py +1 -1
- modal/cli/run.py +6 -6
- modal/cli/secret.py +3 -3
- modal/cli/utils.py +2 -1
- modal/cli/volume.py +3 -3
- modal/client.py +6 -11
- modal/client.pyi +18 -27
- modal/cloud_bucket_mount.py +3 -3
- modal/cloud_bucket_mount.pyi +2 -2
- modal/cls.py +30 -30
- modal/cls.pyi +35 -34
- modal/config.py +3 -2
- modal/dict.py +4 -3
- modal/dict.pyi +10 -9
- modal/environments.py +3 -3
- modal/environments.pyi +3 -3
- modal/exception.py +2 -3
- modal/functions.py +105 -35
- modal/functions.pyi +71 -48
- modal/image.py +45 -48
- modal/image.pyi +102 -101
- modal/io_streams.py +4 -7
- modal/io_streams.pyi +14 -13
- modal/mount.py +23 -22
- modal/mount.pyi +28 -29
- modal/network_file_system.py +7 -6
- modal/network_file_system.pyi +12 -11
- modal/object.py +9 -8
- modal/object.pyi +47 -34
- modal/output.py +2 -1
- modal/parallel_map.py +4 -4
- modal/partial_function.py +9 -13
- modal/partial_function.pyi +17 -18
- modal/queue.py +9 -8
- modal/queue.pyi +23 -22
- modal/retries.py +38 -0
- modal/runner.py +8 -7
- modal/runner.pyi +8 -14
- modal/running_app.py +3 -3
- modal/sandbox.py +14 -13
- modal/sandbox.pyi +67 -72
- modal/scheduler_placement.py +2 -1
- modal/secret.py +7 -7
- modal/secret.pyi +12 -12
- modal/serving.py +4 -3
- modal/serving.pyi +5 -4
- modal/token_flow.py +3 -2
- modal/token_flow.pyi +3 -3
- modal/volume.py +7 -12
- modal/volume.pyi +17 -16
- {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/METADATA +1 -1
- modal-0.67.11.dist-info/RECORD +168 -0
- modal_docs/mdmd/signatures.py +1 -2
- modal_version/_version_generated.py +1 -1
- modal-0.67.6.dist-info/RECORD +0 -168
- {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/LICENSE +0 -0
- {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/WHEEL +0 -0
- {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/entry_points.txt +0 -0
- {modal-0.67.6.dist-info → modal-0.67.11.dist-info}/top_level.txt +0 -0
modal/functions.pyi
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import collections.abc
|
1
2
|
import google.protobuf.message
|
2
3
|
import modal._utils.async_utils
|
3
4
|
import modal._utils.function_utils
|
@@ -25,11 +26,35 @@ import pathlib
|
|
25
26
|
import typing
|
26
27
|
import typing_extensions
|
27
28
|
|
29
|
+
class _RetryContext:
|
30
|
+
function_call_invocation_type: int
|
31
|
+
retry_policy: modal_proto.api_pb2.FunctionRetryPolicy
|
32
|
+
function_call_jwt: str
|
33
|
+
input_jwt: str
|
34
|
+
input_id: str
|
35
|
+
item: modal_proto.api_pb2.FunctionPutInputsItem
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
function_call_invocation_type: int,
|
40
|
+
retry_policy: modal_proto.api_pb2.FunctionRetryPolicy,
|
41
|
+
function_call_jwt: str,
|
42
|
+
input_jwt: str,
|
43
|
+
input_id: str,
|
44
|
+
item: modal_proto.api_pb2.FunctionPutInputsItem,
|
45
|
+
) -> None: ...
|
46
|
+
def __repr__(self): ...
|
47
|
+
def __eq__(self, other): ...
|
48
|
+
|
28
49
|
class _Invocation:
|
29
50
|
stub: modal_proto.modal_api_grpc.ModalClientModal
|
30
51
|
|
31
52
|
def __init__(
|
32
|
-
self,
|
53
|
+
self,
|
54
|
+
stub: modal_proto.modal_api_grpc.ModalClientModal,
|
55
|
+
function_call_id: str,
|
56
|
+
client: modal.client._Client,
|
57
|
+
retry_context: typing.Optional[_RetryContext] = None,
|
33
58
|
): ...
|
34
59
|
@staticmethod
|
35
60
|
async def create(
|
@@ -38,6 +63,8 @@ class _Invocation:
|
|
38
63
|
async def pop_function_call_outputs(
|
39
64
|
self, timeout: typing.Optional[float], clear_on_success: bool
|
40
65
|
) -> modal_proto.api_pb2.FunctionGetOutputsResponse: ...
|
66
|
+
async def _retry_input(self) -> None: ...
|
67
|
+
async def _get_single_output(self) -> typing.Any: ...
|
41
68
|
async def run_function(self) -> typing.Any: ...
|
42
69
|
async def poll_function(self, timeout: typing.Optional[float] = None): ...
|
43
70
|
def run_generator(self): ...
|
@@ -60,42 +87,38 @@ def _parse_retries(
|
|
60
87
|
|
61
88
|
class _FunctionSpec:
|
62
89
|
image: typing.Optional[modal.image._Image]
|
63
|
-
mounts:
|
64
|
-
secrets:
|
65
|
-
network_file_systems: typing.
|
66
|
-
|
67
|
-
]
|
68
|
-
volumes: typing.Dict[
|
90
|
+
mounts: collections.abc.Sequence[modal.mount._Mount]
|
91
|
+
secrets: collections.abc.Sequence[modal.secret._Secret]
|
92
|
+
network_file_systems: dict[typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem]
|
93
|
+
volumes: dict[
|
69
94
|
typing.Union[str, pathlib.PurePosixPath],
|
70
95
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
71
96
|
]
|
72
|
-
gpus: typing.Union[
|
73
|
-
None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
74
|
-
]
|
97
|
+
gpus: typing.Union[None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]]
|
75
98
|
cloud: typing.Optional[str]
|
76
99
|
cpu: typing.Optional[float]
|
77
|
-
memory: typing.Union[int,
|
100
|
+
memory: typing.Union[int, tuple[int, int], None]
|
78
101
|
ephemeral_disk: typing.Optional[int]
|
79
102
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement]
|
80
103
|
|
81
104
|
def __init__(
|
82
105
|
self,
|
83
106
|
image: typing.Optional[modal.image._Image],
|
84
|
-
mounts:
|
85
|
-
secrets:
|
86
|
-
network_file_systems:
|
107
|
+
mounts: collections.abc.Sequence[modal.mount._Mount],
|
108
|
+
secrets: collections.abc.Sequence[modal.secret._Secret],
|
109
|
+
network_file_systems: dict[
|
87
110
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
|
88
111
|
],
|
89
|
-
volumes:
|
112
|
+
volumes: dict[
|
90
113
|
typing.Union[str, pathlib.PurePosixPath],
|
91
114
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
92
115
|
],
|
93
116
|
gpus: typing.Union[
|
94
|
-
None, bool, str, modal.gpu._GPUConfig,
|
117
|
+
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
95
118
|
],
|
96
119
|
cloud: typing.Optional[str],
|
97
120
|
cpu: typing.Optional[float],
|
98
|
-
memory: typing.Union[int,
|
121
|
+
memory: typing.Union[int, tuple[int, int], None],
|
99
122
|
ephemeral_disk: typing.Optional[int],
|
100
123
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement],
|
101
124
|
) -> None: ...
|
@@ -110,7 +133,7 @@ OriginalReturnType = typing.TypeVar("OriginalReturnType", covariant=True)
|
|
110
133
|
|
111
134
|
class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object._Object):
|
112
135
|
_info: typing.Optional[modal._utils.function_utils.FunctionInfo]
|
113
|
-
_serve_mounts:
|
136
|
+
_serve_mounts: frozenset[modal.mount._Mount]
|
114
137
|
_app: typing.Optional[modal.app._App]
|
115
138
|
_obj: typing.Optional[modal.cls._Obj]
|
116
139
|
_web_url: typing.Optional[str]
|
@@ -124,7 +147,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
124
147
|
_cluster_size: typing.Optional[int]
|
125
148
|
_use_method_name: str
|
126
149
|
_class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
|
127
|
-
_method_handle_metadata: typing.Optional[
|
150
|
+
_method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
|
128
151
|
|
129
152
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function._PartialFunction): ...
|
130
153
|
def _bind_instance_method(self, class_bound_method: _Function): ...
|
@@ -133,23 +156,23 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
133
156
|
info: modal._utils.function_utils.FunctionInfo,
|
134
157
|
app,
|
135
158
|
image: modal.image._Image,
|
136
|
-
secrets:
|
159
|
+
secrets: collections.abc.Sequence[modal.secret._Secret] = (),
|
137
160
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
138
161
|
is_generator=False,
|
139
162
|
gpu: typing.Union[
|
140
|
-
None, bool, str, modal.gpu._GPUConfig,
|
163
|
+
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
141
164
|
] = None,
|
142
|
-
mounts:
|
143
|
-
network_file_systems:
|
165
|
+
mounts: collections.abc.Collection[modal.mount._Mount] = (),
|
166
|
+
network_file_systems: dict[
|
144
167
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
|
145
168
|
] = {},
|
146
169
|
allow_cross_region_volumes: bool = False,
|
147
|
-
volumes:
|
170
|
+
volumes: dict[
|
148
171
|
typing.Union[str, pathlib.PurePosixPath],
|
149
172
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
150
173
|
] = {},
|
151
174
|
webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
|
152
|
-
memory: typing.Union[int,
|
175
|
+
memory: typing.Union[int, tuple[int, int], None] = None,
|
153
176
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
154
177
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
155
178
|
timeout: typing.Optional[int] = None,
|
@@ -179,13 +202,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
179
202
|
obj: modal.cls._Obj,
|
180
203
|
from_other_workspace: bool,
|
181
204
|
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
182
|
-
args:
|
183
|
-
kwargs:
|
205
|
+
args: collections.abc.Sized,
|
206
|
+
kwargs: dict[str, typing.Any],
|
184
207
|
) -> _Function: ...
|
185
208
|
async def keep_warm(self, warm_pool_size: int) -> None: ...
|
186
209
|
@classmethod
|
187
210
|
def from_name(
|
188
|
-
cls:
|
211
|
+
cls: type[_Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
|
189
212
|
) -> _Function: ...
|
190
213
|
@staticmethod
|
191
214
|
async def lookup(
|
@@ -218,13 +241,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
218
241
|
def cluster_size(self) -> int: ...
|
219
242
|
def _map(
|
220
243
|
self, input_queue: modal.parallel_map._SynchronizedQueue, order_outputs: bool, return_exceptions: bool
|
221
|
-
) ->
|
244
|
+
) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
222
245
|
async def _call_function(self, args, kwargs) -> ReturnType: ...
|
223
246
|
async def _call_function_nowait(self, args, kwargs, function_call_invocation_type: int) -> _Invocation: ...
|
224
247
|
def _call_generator(self, args, kwargs): ...
|
225
248
|
async def _call_generator_nowait(self, args, kwargs): ...
|
226
249
|
async def remote(self, *args: P.args, **kwargs: P.kwargs) -> ReturnType: ...
|
227
|
-
def remote_gen(self, *args, **kwargs) ->
|
250
|
+
def remote_gen(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
228
251
|
def _get_info(self) -> modal._utils.function_utils.FunctionInfo: ...
|
229
252
|
def _get_obj(self) -> typing.Optional[modal.cls._Obj]: ...
|
230
253
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
@@ -279,7 +302,7 @@ P_INNER = typing_extensions.ParamSpec("P_INNER")
|
|
279
302
|
|
280
303
|
class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.Object):
|
281
304
|
_info: typing.Optional[modal._utils.function_utils.FunctionInfo]
|
282
|
-
_serve_mounts:
|
305
|
+
_serve_mounts: frozenset[modal.mount.Mount]
|
283
306
|
_app: typing.Optional[modal.app.App]
|
284
307
|
_obj: typing.Optional[modal.cls.Obj]
|
285
308
|
_web_url: typing.Optional[str]
|
@@ -293,7 +316,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
293
316
|
_cluster_size: typing.Optional[int]
|
294
317
|
_use_method_name: str
|
295
318
|
_class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
|
296
|
-
_method_handle_metadata: typing.Optional[
|
319
|
+
_method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
|
297
320
|
|
298
321
|
def __init__(self, *args, **kwargs): ...
|
299
322
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction): ...
|
@@ -303,23 +326,23 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
303
326
|
info: modal._utils.function_utils.FunctionInfo,
|
304
327
|
app,
|
305
328
|
image: modal.image.Image,
|
306
|
-
secrets:
|
329
|
+
secrets: collections.abc.Sequence[modal.secret.Secret] = (),
|
307
330
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
308
331
|
is_generator=False,
|
309
332
|
gpu: typing.Union[
|
310
|
-
None, bool, str, modal.gpu._GPUConfig,
|
333
|
+
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
311
334
|
] = None,
|
312
|
-
mounts:
|
313
|
-
network_file_systems:
|
335
|
+
mounts: collections.abc.Collection[modal.mount.Mount] = (),
|
336
|
+
network_file_systems: dict[
|
314
337
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
|
315
338
|
] = {},
|
316
339
|
allow_cross_region_volumes: bool = False,
|
317
|
-
volumes:
|
340
|
+
volumes: dict[
|
318
341
|
typing.Union[str, pathlib.PurePosixPath],
|
319
342
|
typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
|
320
343
|
] = {},
|
321
344
|
webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
|
322
|
-
memory: typing.Union[int,
|
345
|
+
memory: typing.Union[int, tuple[int, int], None] = None,
|
323
346
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
324
347
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
325
348
|
timeout: typing.Optional[int] = None,
|
@@ -349,8 +372,8 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
349
372
|
obj: modal.cls.Obj,
|
350
373
|
from_other_workspace: bool,
|
351
374
|
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
352
|
-
args:
|
353
|
-
kwargs:
|
375
|
+
args: collections.abc.Sized,
|
376
|
+
kwargs: dict[str, typing.Any],
|
354
377
|
) -> Function: ...
|
355
378
|
|
356
379
|
class __keep_warm_spec(typing_extensions.Protocol):
|
@@ -361,7 +384,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
361
384
|
|
362
385
|
@classmethod
|
363
386
|
def from_name(
|
364
|
-
cls:
|
387
|
+
cls: type[Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
|
365
388
|
) -> Function: ...
|
366
389
|
|
367
390
|
class __lookup_spec(typing_extensions.Protocol):
|
@@ -412,7 +435,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
412
435
|
) -> typing.Generator[typing.Any, None, None]: ...
|
413
436
|
def aio(
|
414
437
|
self, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
|
415
|
-
) ->
|
438
|
+
) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
416
439
|
|
417
440
|
_map: ___map_spec
|
418
441
|
|
@@ -444,7 +467,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
444
467
|
|
445
468
|
class __remote_gen_spec(typing_extensions.Protocol):
|
446
469
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
447
|
-
def aio(self, *args, **kwargs) ->
|
470
|
+
def aio(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
448
471
|
|
449
472
|
remote_gen: __remote_gen_spec
|
450
473
|
|
@@ -517,8 +540,8 @@ class _FunctionCall(typing.Generic[ReturnType], modal.object._Object):
|
|
517
540
|
|
518
541
|
def _invocation(self): ...
|
519
542
|
async def get(self, timeout: typing.Optional[float] = None) -> ReturnType: ...
|
520
|
-
def get_gen(self) ->
|
521
|
-
async def get_call_graph(self) ->
|
543
|
+
def get_gen(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
544
|
+
async def get_call_graph(self) -> list[modal.call_graph.InputInfo]: ...
|
522
545
|
async def cancel(self, terminate_containers: bool = False): ...
|
523
546
|
@staticmethod
|
524
547
|
async def from_id(
|
@@ -539,13 +562,13 @@ class FunctionCall(typing.Generic[ReturnType], modal.object.Object):
|
|
539
562
|
|
540
563
|
class __get_gen_spec(typing_extensions.Protocol):
|
541
564
|
def __call__(self) -> typing.Generator[typing.Any, None, None]: ...
|
542
|
-
def aio(self) ->
|
565
|
+
def aio(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
543
566
|
|
544
567
|
get_gen: __get_gen_spec
|
545
568
|
|
546
569
|
class __get_call_graph_spec(typing_extensions.Protocol):
|
547
|
-
def __call__(self) ->
|
548
|
-
async def aio(self) ->
|
570
|
+
def __call__(self) -> list[modal.call_graph.InputInfo]: ...
|
571
|
+
async def aio(self) -> list[modal.call_graph.InputInfo]: ...
|
549
572
|
|
550
573
|
get_call_graph: __get_call_graph_spec
|
551
574
|
|
modal/image.py
CHANGED
@@ -7,18 +7,15 @@ import shlex
|
|
7
7
|
import sys
|
8
8
|
import typing
|
9
9
|
import warnings
|
10
|
+
from collections.abc import Sequence
|
10
11
|
from dataclasses import dataclass
|
11
12
|
from inspect import isfunction
|
12
13
|
from pathlib import Path, PurePosixPath
|
13
14
|
from typing import (
|
14
15
|
Any,
|
15
16
|
Callable,
|
16
|
-
Dict,
|
17
|
-
List,
|
18
17
|
Literal,
|
19
18
|
Optional,
|
20
|
-
Sequence,
|
21
|
-
Set,
|
22
19
|
Union,
|
23
20
|
cast,
|
24
21
|
get_args,
|
@@ -59,7 +56,7 @@ ImageBuilderVersion = Literal["2023.12", "2024.04", "2024.10"]
|
|
59
56
|
# so that we fail fast / clearly in unsupported containers. Additionally, we enumerate the supported
|
60
57
|
# Python versions in mount.py where we specify the "standalone Python versions" we create mounts for.
|
61
58
|
# Consider consolidating these multiple sources of truth?
|
62
|
-
SUPPORTED_PYTHON_SERIES:
|
59
|
+
SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
|
63
60
|
"2024.10": ["3.9", "3.10", "3.11", "3.12", "3.13"],
|
64
61
|
"2024.04": ["3.9", "3.10", "3.11", "3.12"],
|
65
62
|
"2023.12": ["3.9", "3.10", "3.11", "3.12"],
|
@@ -74,7 +71,7 @@ def _validate_python_version(
|
|
74
71
|
) -> str:
|
75
72
|
if python_version is None:
|
76
73
|
# If Python version is unspecified, match the local version, up to the minor component
|
77
|
-
python_version = series_version = "{
|
74
|
+
python_version = series_version = "{}.{}".format(*sys.version_info)
|
78
75
|
elif not isinstance(python_version, str):
|
79
76
|
raise InvalidError(f"Python version must be specified as a string, not {type(python_version).__name__}")
|
80
77
|
elif not re.match(r"^3(?:\.\d{1,2}){1,2}(rc\d*)?$", python_version):
|
@@ -86,7 +83,7 @@ def _validate_python_version(
|
|
86
83
|
"Python version must be specified as 'major.minor' for this interface;"
|
87
84
|
f" micro-level specification ({python_version!r}) is not valid."
|
88
85
|
)
|
89
|
-
series_version = "{
|
86
|
+
series_version = "{}.{}".format(*components)
|
90
87
|
|
91
88
|
supported_series = SUPPORTED_PYTHON_SERIES[builder_version]
|
92
89
|
if series_version not in supported_series:
|
@@ -111,13 +108,13 @@ def _dockerhub_python_version(builder_version: ImageBuilderVersion, python_versi
|
|
111
108
|
# This allows us to publish one pre-built debian-slim image per Python series.
|
112
109
|
python_versions = _base_image_config("python", builder_version)
|
113
110
|
series_to_micro_version = dict(tuple(v.rsplit(".", 1)) for v in python_versions)
|
114
|
-
python_series_requested = "{
|
111
|
+
python_series_requested = "{}.{}".format(*version_components)
|
115
112
|
micro_version = series_to_micro_version[python_series_requested]
|
116
113
|
return f"{python_series_requested}.{micro_version}"
|
117
114
|
|
118
115
|
|
119
116
|
def _base_image_config(group: str, builder_version: ImageBuilderVersion) -> Any:
|
120
|
-
with open(LOCAL_REQUIREMENTS_DIR / "base-images.json"
|
117
|
+
with open(LOCAL_REQUIREMENTS_DIR / "base-images.json") as f:
|
121
118
|
data = json.load(f)
|
122
119
|
return data[group][builder_version]
|
123
120
|
|
@@ -146,7 +143,7 @@ def _get_modal_requirements_command(version: ImageBuilderVersion) -> str:
|
|
146
143
|
return f"{prefix} -r {CONTAINER_REQUIREMENTS_PATH}"
|
147
144
|
|
148
145
|
|
149
|
-
def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[str,
|
146
|
+
def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[str, list[str]]]) -> list[str]:
|
150
147
|
"""Takes a sequence of strings, or string lists, and flattens it.
|
151
148
|
|
152
149
|
Raises an error if any of the elements are not strings or string lists.
|
@@ -155,7 +152,7 @@ def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[st
|
|
155
152
|
def is_str_list(x):
|
156
153
|
return isinstance(x, list) and all(isinstance(y, str) for y in x)
|
157
154
|
|
158
|
-
ret:
|
155
|
+
ret: list[str] = []
|
159
156
|
for x in args:
|
160
157
|
if isinstance(x, str):
|
161
158
|
ret.append(x)
|
@@ -166,7 +163,7 @@ def _flatten_str_args(function_name: str, arg_name: str, args: Sequence[Union[st
|
|
166
163
|
return ret
|
167
164
|
|
168
165
|
|
169
|
-
def _validate_packages(packages:
|
166
|
+
def _validate_packages(packages: list[str]) -> bool:
|
170
167
|
"""Validates that a list of packages does not contain any command-line options."""
|
171
168
|
return not any(pkg.startswith("-") for pkg in packages)
|
172
169
|
|
@@ -219,7 +216,7 @@ def _get_image_builder_version(server_version: ImageBuilderVersion) -> ImageBuil
|
|
219
216
|
version_source = ""
|
220
217
|
version = server_version
|
221
218
|
|
222
|
-
supported_versions:
|
219
|
+
supported_versions: set[ImageBuilderVersion] = set(get_args(ImageBuilderVersion))
|
223
220
|
if version not in supported_versions:
|
224
221
|
if local_config_version is not None:
|
225
222
|
update_suggestion = "or remove your local configuration"
|
@@ -259,8 +256,8 @@ class _ImageRegistryConfig:
|
|
259
256
|
@dataclass
|
260
257
|
class DockerfileSpec:
|
261
258
|
# Ideally we would use field() with default_factory=, but doesn't work with synchronicity type-stub gen
|
262
|
-
commands:
|
263
|
-
context_files:
|
259
|
+
commands: list[str]
|
260
|
+
context_files: dict[str, str]
|
264
261
|
|
265
262
|
|
266
263
|
async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.ImageJoinStreamingResponse:
|
@@ -310,8 +307,8 @@ class _Image(_Object, type_prefix="im"):
|
|
310
307
|
"""
|
311
308
|
|
312
309
|
force_build: bool
|
313
|
-
inside_exceptions:
|
314
|
-
_serve_mounts:
|
310
|
+
inside_exceptions: list[Exception]
|
311
|
+
_serve_mounts: frozenset[_Mount] # used for mounts watching in `modal serve`
|
315
312
|
_deferred_mounts: Sequence[
|
316
313
|
_Mount
|
317
314
|
] # added as mounts on any container referencing the Image, see `def _mount_layers`
|
@@ -390,7 +387,7 @@ class _Image(_Object, type_prefix="im"):
|
|
390
387
|
@staticmethod
|
391
388
|
def _from_args(
|
392
389
|
*,
|
393
|
-
base_images: Optional[
|
390
|
+
base_images: Optional[dict[str, "_Image"]] = None,
|
394
391
|
dockerfile_function: Optional[Callable[[ImageBuilderVersion], DockerfileSpec]] = None,
|
395
392
|
secrets: Optional[Sequence[_Secret]] = None,
|
396
393
|
gpu_config: Optional[api_pb2.GPUConfig] = None,
|
@@ -720,7 +717,7 @@ class _Image(_Object, type_prefix="im"):
|
|
720
717
|
|
721
718
|
def pip_install(
|
722
719
|
self,
|
723
|
-
*packages: Union[str,
|
720
|
+
*packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
|
724
721
|
find_links: Optional[str] = None, # Passes -f (--find-links) pip install
|
725
722
|
index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
|
726
723
|
extra_index_url: Optional[str] = None, # Passes --extra-index-url to pip install
|
@@ -762,7 +759,7 @@ class _Image(_Object, type_prefix="im"):
|
|
762
759
|
return self
|
763
760
|
|
764
761
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
765
|
-
package_args =
|
762
|
+
package_args = shlex.join(sorted(pkgs))
|
766
763
|
extra_args = _make_pip_install_args(find_links, index_url, extra_index_url, pre, extra_options)
|
767
764
|
commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
|
768
765
|
if not _validate_packages(pkgs):
|
@@ -924,7 +921,7 @@ class _Image(_Object, type_prefix="im"):
|
|
924
921
|
def pip_install_from_pyproject(
|
925
922
|
self,
|
926
923
|
pyproject_toml: str,
|
927
|
-
optional_dependencies:
|
924
|
+
optional_dependencies: list[str] = [],
|
928
925
|
*,
|
929
926
|
find_links: Optional[str] = None, # Passes -f (--find-links) pip install
|
930
927
|
index_url: Optional[str] = None, # Passes -i (--index-url) to pip install
|
@@ -968,7 +965,7 @@ class _Image(_Object, type_prefix="im"):
|
|
968
965
|
dependencies.extend(optionals[dep_group_name])
|
969
966
|
|
970
967
|
extra_args = _make_pip_install_args(find_links, index_url, extra_index_url, pre, extra_options)
|
971
|
-
package_args =
|
968
|
+
package_args = shlex.join(sorted(dependencies))
|
972
969
|
commands = ["FROM base", f"RUN python -m pip install {package_args} {extra_args}"]
|
973
970
|
if version > "2023.12": # Back-compat for legacy trailing space
|
974
971
|
commands = [cmd.strip() for cmd in commands]
|
@@ -994,11 +991,11 @@ class _Image(_Object, type_prefix="im"):
|
|
994
991
|
old_installer: bool = False,
|
995
992
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
996
993
|
# Selected optional dependency groups to install (See https://python-poetry.org/docs/cli/#install)
|
997
|
-
with_:
|
994
|
+
with_: list[str] = [],
|
998
995
|
# Selected optional dependency groups to exclude (See https://python-poetry.org/docs/cli/#install)
|
999
|
-
without:
|
996
|
+
without: list[str] = [],
|
1000
997
|
# Only install dependency groups specifed in this list.
|
1001
|
-
only:
|
998
|
+
only: list[str] = [],
|
1002
999
|
*,
|
1003
1000
|
secrets: Sequence[_Secret] = [],
|
1004
1001
|
gpu: GPU_T = None,
|
@@ -1066,8 +1063,8 @@ class _Image(_Object, type_prefix="im"):
|
|
1066
1063
|
|
1067
1064
|
def dockerfile_commands(
|
1068
1065
|
self,
|
1069
|
-
*dockerfile_commands: Union[str,
|
1070
|
-
context_files:
|
1066
|
+
*dockerfile_commands: Union[str, list[str]],
|
1067
|
+
context_files: dict[str, str] = {},
|
1071
1068
|
secrets: Sequence[_Secret] = [],
|
1072
1069
|
gpu: GPU_T = None,
|
1073
1070
|
# modal.Mount with local files to supply as build context for COPY commands
|
@@ -1093,7 +1090,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1093
1090
|
|
1094
1091
|
def entrypoint(
|
1095
1092
|
self,
|
1096
|
-
entrypoint_commands:
|
1093
|
+
entrypoint_commands: list[str],
|
1097
1094
|
) -> "_Image":
|
1098
1095
|
"""Set the entrypoint for the image."""
|
1099
1096
|
args_str = _flatten_str_args("entrypoint", "entrypoint_files", entrypoint_commands)
|
@@ -1104,7 +1101,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1104
1101
|
|
1105
1102
|
def shell(
|
1106
1103
|
self,
|
1107
|
-
shell_commands:
|
1104
|
+
shell_commands: list[str],
|
1108
1105
|
) -> "_Image":
|
1109
1106
|
"""Overwrite default shell for the image."""
|
1110
1107
|
args_str = _flatten_str_args("shell", "shell_commands", shell_commands)
|
@@ -1115,7 +1112,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1115
1112
|
|
1116
1113
|
def run_commands(
|
1117
1114
|
self,
|
1118
|
-
*commands: Union[str,
|
1115
|
+
*commands: Union[str, list[str]],
|
1119
1116
|
secrets: Sequence[_Secret] = [],
|
1120
1117
|
gpu: GPU_T = None,
|
1121
1118
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
@@ -1147,8 +1144,8 @@ class _Image(_Object, type_prefix="im"):
|
|
1147
1144
|
|
1148
1145
|
def conda_install(
|
1149
1146
|
self,
|
1150
|
-
*packages: Union[str,
|
1151
|
-
channels:
|
1147
|
+
*packages: Union[str, list[str]], # A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
|
1148
|
+
channels: list[str] = [], # A list of Conda channels, eg. ["conda-forge", "nvidia"]
|
1152
1149
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1153
1150
|
secrets: Sequence[_Secret] = [],
|
1154
1151
|
gpu: GPU_T = None,
|
@@ -1208,11 +1205,11 @@ class _Image(_Object, type_prefix="im"):
|
|
1208
1205
|
def micromamba_install(
|
1209
1206
|
self,
|
1210
1207
|
# A list of Python packages, eg. ["numpy", "matplotlib>=3.5.0"]
|
1211
|
-
*packages: Union[str,
|
1208
|
+
*packages: Union[str, list[str]],
|
1212
1209
|
# A local path to a file containing package specifications
|
1213
1210
|
spec_file: Optional[str] = None,
|
1214
1211
|
# A list of Conda channels, eg. ["conda-forge", "nvidia"].
|
1215
|
-
channels:
|
1212
|
+
channels: list[str] = [],
|
1216
1213
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1217
1214
|
secrets: Sequence[_Secret] = [],
|
1218
1215
|
gpu: GPU_T = None,
|
@@ -1223,7 +1220,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1223
1220
|
return self
|
1224
1221
|
|
1225
1222
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
1226
|
-
package_args =
|
1223
|
+
package_args = shlex.join(pkgs)
|
1227
1224
|
channel_args = "".join(f" -c {channel}" for channel in channels)
|
1228
1225
|
|
1229
1226
|
space = " " if package_args else ""
|
@@ -1251,10 +1248,10 @@ class _Image(_Object, type_prefix="im"):
|
|
1251
1248
|
def _registry_setup_commands(
|
1252
1249
|
tag: str,
|
1253
1250
|
builder_version: ImageBuilderVersion,
|
1254
|
-
setup_commands:
|
1251
|
+
setup_commands: list[str],
|
1255
1252
|
add_python: Optional[str] = None,
|
1256
|
-
) ->
|
1257
|
-
add_python_commands:
|
1253
|
+
) -> list[str]:
|
1254
|
+
add_python_commands: list[str] = []
|
1258
1255
|
if add_python:
|
1259
1256
|
_validate_python_version(add_python, builder_version, allow_micro_granularity=False)
|
1260
1257
|
add_python_commands = [
|
@@ -1285,7 +1282,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1285
1282
|
tag: str,
|
1286
1283
|
*,
|
1287
1284
|
secret: Optional[_Secret] = None,
|
1288
|
-
setup_dockerfile_commands:
|
1285
|
+
setup_dockerfile_commands: list[str] = [],
|
1289
1286
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1290
1287
|
add_python: Optional[str] = None,
|
1291
1288
|
**kwargs,
|
@@ -1344,7 +1341,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1344
1341
|
tag: str,
|
1345
1342
|
secret: Optional[_Secret] = None,
|
1346
1343
|
*,
|
1347
|
-
setup_dockerfile_commands:
|
1344
|
+
setup_dockerfile_commands: list[str] = [],
|
1348
1345
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1349
1346
|
add_python: Optional[str] = None,
|
1350
1347
|
**kwargs,
|
@@ -1395,7 +1392,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1395
1392
|
tag: str,
|
1396
1393
|
secret: Optional[_Secret] = None,
|
1397
1394
|
*,
|
1398
|
-
setup_dockerfile_commands:
|
1395
|
+
setup_dockerfile_commands: list[str] = [],
|
1399
1396
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1400
1397
|
add_python: Optional[str] = None,
|
1401
1398
|
**kwargs,
|
@@ -1550,7 +1547,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1550
1547
|
|
1551
1548
|
def apt_install(
|
1552
1549
|
self,
|
1553
|
-
*packages: Union[str,
|
1550
|
+
*packages: Union[str, list[str]], # A list of packages, e.g. ["ssh", "libpq-dev"]
|
1554
1551
|
force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
|
1555
1552
|
secrets: Sequence[_Secret] = [],
|
1556
1553
|
gpu: GPU_T = None,
|
@@ -1567,7 +1564,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1567
1564
|
if not pkgs:
|
1568
1565
|
return self
|
1569
1566
|
|
1570
|
-
package_args =
|
1567
|
+
package_args = shlex.join(pkgs)
|
1571
1568
|
|
1572
1569
|
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
1573
1570
|
commands = [
|
@@ -1590,11 +1587,11 @@ class _Image(_Object, type_prefix="im"):
|
|
1590
1587
|
raw_f: Callable[..., Any],
|
1591
1588
|
secrets: Sequence[_Secret] = (), # Optional Modal Secret objects with environment variables for the container
|
1592
1589
|
gpu: Union[
|
1593
|
-
GPU_T,
|
1590
|
+
GPU_T, list[GPU_T]
|
1594
1591
|
] = None, # GPU request as string ("any", "T4", ...), object (`modal.GPU.A100()`, ...), or a list of either
|
1595
1592
|
mounts: Sequence[_Mount] = (), # Mounts attached to the function
|
1596
|
-
volumes:
|
1597
|
-
network_file_systems:
|
1593
|
+
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {}, # Volume mount paths
|
1594
|
+
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {}, # NFS mount paths
|
1598
1595
|
cpu: Optional[float] = None, # How many CPU cores to request. This is a soft limit.
|
1599
1596
|
memory: Optional[int] = None, # How much memory to request, in MiB. This is a soft limit.
|
1600
1597
|
timeout: Optional[int] = 60 * 60, # Maximum execution time of the function in seconds.
|
@@ -1602,7 +1599,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1602
1599
|
cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
|
1603
1600
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
|
1604
1601
|
args: Sequence[Any] = (), # Positional arguments to the function.
|
1605
|
-
kwargs:
|
1602
|
+
kwargs: dict[str, Any] = {}, # Keyword arguments to the function.
|
1606
1603
|
) -> "_Image":
|
1607
1604
|
"""Run user-defined function `raw_f` as an image build step. The function runs just like an ordinary Modal
|
1608
1605
|
function, and any kwargs accepted by `@app.function` (such as `Mount`s, `NetworkFileSystem`s,
|
@@ -1676,7 +1673,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1676
1673
|
force_build=self.force_build or force_build,
|
1677
1674
|
)
|
1678
1675
|
|
1679
|
-
def env(self, vars:
|
1676
|
+
def env(self, vars: dict[str, str]) -> "_Image":
|
1680
1677
|
"""Sets the environment variables in an Image.
|
1681
1678
|
|
1682
1679
|
**Example**
|