modal 0.73.9__py3-none-any.whl → 0.73.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/_functions.py +31 -8
- modal/_utils/function_utils.py +14 -12
- modal/app.py +2 -2
- modal/cli/run.py +83 -29
- modal/client.pyi +2 -2
- modal/functions.pyi +7 -7
- modal/image.py +13 -3
- modal/image.pyi +2 -0
- modal/mount.py +3 -3
- modal/mount.pyi +1 -1
- {modal-0.73.9.dist-info → modal-0.73.11.dist-info}/METADATA +1 -1
- {modal-0.73.9.dist-info → modal-0.73.11.dist-info}/RECORD +17 -17
- modal_version/_version_generated.py +1 -1
- {modal-0.73.9.dist-info → modal-0.73.11.dist-info}/LICENSE +0 -0
- {modal-0.73.9.dist-info → modal-0.73.11.dist-info}/WHEEL +0 -0
- {modal-0.73.9.dist-info → modal-0.73.11.dist-info}/entry_points.txt +0 -0
- {modal-0.73.9.dist-info → modal-0.73.11.dist-info}/top_level.txt +0 -0
modal/_functions.py
CHANGED
@@ -65,7 +65,7 @@ from .exception import (
|
|
65
65
|
)
|
66
66
|
from .gpu import GPU_T, parse_gpu_config
|
67
67
|
from .image import _Image
|
68
|
-
from .mount import _get_client_mount, _Mount,
|
68
|
+
from .mount import _get_client_mount, _Mount, get_sys_modules_mounts
|
69
69
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
70
70
|
from .output import _get_output_manager
|
71
71
|
from .parallel_map import (
|
@@ -436,7 +436,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
436
436
|
return fun
|
437
437
|
|
438
438
|
@staticmethod
|
439
|
-
def
|
439
|
+
def from_local(
|
440
440
|
info: FunctionInfo,
|
441
441
|
app,
|
442
442
|
image: _Image,
|
@@ -504,18 +504,41 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
504
504
|
if include_source_mode != IncludeSourceMode.INCLUDE_NOTHING:
|
505
505
|
entrypoint_mounts = info.get_entrypoint_mount()
|
506
506
|
else:
|
507
|
-
entrypoint_mounts =
|
507
|
+
entrypoint_mounts = {}
|
508
508
|
|
509
509
|
all_mounts = [
|
510
510
|
_get_client_mount(),
|
511
511
|
*explicit_mounts,
|
512
|
-
*entrypoint_mounts,
|
512
|
+
*entrypoint_mounts.values(),
|
513
513
|
]
|
514
514
|
|
515
515
|
if include_source_mode is IncludeSourceMode.INCLUDE_FIRST_PARTY:
|
516
|
-
|
517
|
-
#
|
518
|
-
|
516
|
+
auto_mounts = get_sys_modules_mounts()
|
517
|
+
# don't need to add entrypoint modules to automounts:
|
518
|
+
for entrypoint_module in entrypoint_mounts:
|
519
|
+
auto_mounts.pop(entrypoint_module, None)
|
520
|
+
|
521
|
+
warn_missing_modules = set(auto_mounts.keys()) - image._added_python_source_set
|
522
|
+
|
523
|
+
if warn_missing_modules:
|
524
|
+
python_stringified_modules = ", ".join(f'"{mod}"' for mod in sorted(warn_missing_modules))
|
525
|
+
deprecation_warning(
|
526
|
+
(2025, 2, 3),
|
527
|
+
(
|
528
|
+
'Modal will stop implicitly adding local Python modules to the Image ("automounting") in a '
|
529
|
+
"future update. The following modules need to be explicitly added for future "
|
530
|
+
"compatibility:\n"
|
531
|
+
)
|
532
|
+
+ "\n".join(sorted([f"* {m}" for m in warn_missing_modules]))
|
533
|
+
+ "\n\n"
|
534
|
+
+ (
|
535
|
+
"e.g.:\n"
|
536
|
+
f"image_with_source = my_image.add_local_python_source({python_stringified_modules})\n\n"
|
537
|
+
)
|
538
|
+
+ "For more information, see https://modal.com/docs/guide/modal-1-0-migration",
|
539
|
+
pending=True,
|
540
|
+
)
|
541
|
+
all_mounts += auto_mounts.values()
|
519
542
|
else:
|
520
543
|
# skip any mount introspection/logic inside containers, since the function
|
521
544
|
# should already be hydrated
|
@@ -564,7 +587,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
564
587
|
for k, pf in build_functions:
|
565
588
|
build_function = pf.raw_f
|
566
589
|
snapshot_info = FunctionInfo(build_function, user_cls=info.user_cls)
|
567
|
-
snapshot_function = _Function.
|
590
|
+
snapshot_function = _Function.from_local(
|
568
591
|
snapshot_info,
|
569
592
|
app=None,
|
570
593
|
image=image,
|
modal/_utils/function_utils.py
CHANGED
@@ -308,7 +308,7 @@ class FunctionInfo:
|
|
308
308
|
format=api_pb2.ClassParameterInfo.PARAM_SERIALIZATION_FORMAT_PROTO, schema=modal_parameters
|
309
309
|
)
|
310
310
|
|
311
|
-
def get_entrypoint_mount(self) ->
|
311
|
+
def get_entrypoint_mount(self) -> dict[str, _Mount]:
|
312
312
|
"""
|
313
313
|
Includes:
|
314
314
|
* Implicit mount of the function itself (the module or package that the function is part of)
|
@@ -322,7 +322,7 @@ class FunctionInfo:
|
|
322
322
|
"""
|
323
323
|
if self.is_serialized():
|
324
324
|
# Don't auto-mount anything for serialized functions (including notebooks)
|
325
|
-
return
|
325
|
+
return {}
|
326
326
|
|
327
327
|
# make sure the function's own entrypoint is included:
|
328
328
|
if self._type == FunctionInfoType.PACKAGE:
|
@@ -331,21 +331,23 @@ class FunctionInfo:
|
|
331
331
|
# includes non-.py files, since we'll want to migrate to .py-only
|
332
332
|
# soon to get it consistent with the `add_local_python_source()`
|
333
333
|
# defaults.
|
334
|
-
return
|
334
|
+
return {top_level_package: _Mount._from_local_python_packages(top_level_package)}
|
335
335
|
elif self._type == FunctionInfoType.FILE:
|
336
|
-
|
336
|
+
# TODO: inspect if this file is already included as part of
|
337
|
+
# a package mount, and skip it + reference that package
|
338
|
+
# instead if that's the case. This avoids possible module
|
339
|
+
# duplication bugs
|
340
|
+
module_file = Path(self._file)
|
341
|
+
container_module_name = module_file.stem
|
342
|
+
remote_path = ROOT_DIR / module_file.name
|
337
343
|
if not _is_modal_path(remote_path):
|
338
|
-
|
339
|
-
|
340
|
-
# instead if that's the case. This avoids possible module
|
341
|
-
# duplication bugs
|
342
|
-
return [
|
343
|
-
_Mount._from_local_file(
|
344
|
+
return {
|
345
|
+
container_module_name: _Mount._from_local_file(
|
344
346
|
self._file,
|
345
347
|
remote_path=remote_path,
|
346
348
|
)
|
347
|
-
|
348
|
-
return
|
349
|
+
}
|
350
|
+
return {} # this should never be reached...
|
349
351
|
|
350
352
|
def get_tag(self):
|
351
353
|
return self.function_name
|
modal/app.py
CHANGED
@@ -725,7 +725,7 @@ class _App:
|
|
725
725
|
raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
|
726
726
|
scheduler_placement = SchedulerPlacement(region=region)
|
727
727
|
|
728
|
-
function = _Function.
|
728
|
+
function = _Function.from_local(
|
729
729
|
info,
|
730
730
|
app=self,
|
731
731
|
image=image,
|
@@ -862,7 +862,7 @@ class _App:
|
|
862
862
|
|
863
863
|
info = FunctionInfo(None, serialized=serialized, user_cls=user_cls)
|
864
864
|
|
865
|
-
cls_func = _Function.
|
865
|
+
cls_func = _Function.from_local(
|
866
866
|
info,
|
867
867
|
app=self,
|
868
868
|
image=image or self._get_default_image(),
|
modal/cli/run.py
CHANGED
@@ -7,6 +7,7 @@ import re
|
|
7
7
|
import shlex
|
8
8
|
import sys
|
9
9
|
import time
|
10
|
+
from dataclasses import dataclass
|
10
11
|
from functools import partial
|
11
12
|
from typing import Any, Callable, Optional, get_type_hints
|
12
13
|
|
@@ -35,6 +36,7 @@ class ParameterMetadata(TypedDict):
|
|
35
36
|
default: Any
|
36
37
|
annotation: Any
|
37
38
|
type_hint: Any
|
39
|
+
kind: Any
|
38
40
|
|
39
41
|
|
40
42
|
class AnyParamType(click.ParamType):
|
@@ -58,7 +60,13 @@ class NoParserAvailable(InvalidError):
|
|
58
60
|
pass
|
59
61
|
|
60
62
|
|
61
|
-
|
63
|
+
@dataclass
|
64
|
+
class FnSignature:
|
65
|
+
parameters: dict[str, ParameterMetadata]
|
66
|
+
has_variadic_args: bool
|
67
|
+
|
68
|
+
|
69
|
+
def _get_signature(f: Callable[..., Any], is_method: bool = False) -> FnSignature:
|
62
70
|
try:
|
63
71
|
type_hints = get_type_hints(f)
|
64
72
|
except Exception as exc:
|
@@ -66,18 +74,29 @@ def _get_signature(f: Callable[..., Any], is_method: bool = False) -> dict[str,
|
|
66
74
|
msg = "Unable to generate command line interface for app entrypoint. See traceback above for details."
|
67
75
|
raise ExecutionError(msg) from exc
|
68
76
|
|
77
|
+
has_variadic_args = False
|
78
|
+
|
69
79
|
if is_method:
|
70
80
|
self = None # Dummy, doesn't matter
|
71
81
|
f = functools.partial(f, self)
|
82
|
+
|
72
83
|
signature: dict[str, ParameterMetadata] = {}
|
73
84
|
for param in inspect.signature(f).parameters.values():
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
85
|
+
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
86
|
+
has_variadic_args = True
|
87
|
+
else:
|
88
|
+
signature[param.name] = {
|
89
|
+
"name": param.name,
|
90
|
+
"default": param.default,
|
91
|
+
"annotation": param.annotation,
|
92
|
+
"type_hint": type_hints.get(param.name, "Any"),
|
93
|
+
"kind": param.kind,
|
94
|
+
}
|
95
|
+
|
96
|
+
if has_variadic_args and len(signature) > 0:
|
97
|
+
raise InvalidError("Functions with variable-length positional arguments (*args) cannot have other parameters.")
|
98
|
+
|
99
|
+
return FnSignature(signature, has_variadic_args)
|
81
100
|
|
82
101
|
|
83
102
|
def _get_param_type_as_str(annot: Any) -> str:
|
@@ -96,17 +115,18 @@ def _get_param_type_as_str(annot: Any) -> str:
|
|
96
115
|
return annot_str
|
97
116
|
|
98
117
|
|
99
|
-
def _add_click_options(func,
|
118
|
+
def _add_click_options(func, parameters: dict[str, ParameterMetadata]):
|
100
119
|
"""Adds @click.option based on function signature
|
101
120
|
|
102
121
|
Kind of like typer, but using options instead of positional arguments
|
103
122
|
"""
|
104
|
-
for param in
|
123
|
+
for param in parameters.values():
|
105
124
|
param_type_str = _get_param_type_as_str(param["type_hint"])
|
106
125
|
param_name = param["name"].replace("_", "-")
|
107
126
|
cli_name = "--" + param_name
|
108
127
|
if param_type_str == "bool":
|
109
128
|
cli_name += "/--no-" + param_name
|
129
|
+
|
110
130
|
parser = option_parsers.get(param_type_str)
|
111
131
|
if parser is None:
|
112
132
|
msg = f"Parameter `{param_name}` has unparseable annotation: {param['annotation']!r}"
|
@@ -145,9 +165,15 @@ def _write_local_result(result_path: str, res: Any):
|
|
145
165
|
fid.write(res)
|
146
166
|
|
147
167
|
|
148
|
-
def _make_click_function(app, inner: Callable[[dict[str, Any]], Any]):
|
168
|
+
def _make_click_function(app, signature: FnSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
|
149
169
|
@click.pass_context
|
150
170
|
def f(ctx, **kwargs):
|
171
|
+
if signature.has_variadic_args:
|
172
|
+
assert len(kwargs) == 0
|
173
|
+
args = ctx.args
|
174
|
+
else:
|
175
|
+
args = ()
|
176
|
+
|
151
177
|
show_progress: bool = ctx.obj["show_progress"]
|
152
178
|
with enable_output(show_progress):
|
153
179
|
with run_app(
|
@@ -156,7 +182,7 @@ def _make_click_function(app, inner: Callable[[dict[str, Any]], Any]):
|
|
156
182
|
environment_name=ctx.obj["env"],
|
157
183
|
interactive=ctx.obj["interactive"],
|
158
184
|
):
|
159
|
-
res = inner(kwargs)
|
185
|
+
res = inner(args, kwargs)
|
160
186
|
|
161
187
|
if result_path := ctx.obj["result_path"]:
|
162
188
|
_write_local_result(result_path, res)
|
@@ -168,23 +194,33 @@ def _get_click_command_for_function(app: App, function: Function):
|
|
168
194
|
if function.is_generator:
|
169
195
|
raise InvalidError("`modal run` is not supported for generator functions")
|
170
196
|
|
171
|
-
signature
|
197
|
+
signature = _get_signature(function.info.raw_f)
|
198
|
+
|
199
|
+
def _inner(args, click_kwargs):
|
200
|
+
return function.remote(*args, **click_kwargs)
|
172
201
|
|
173
|
-
|
174
|
-
return function.remote(**click_kwargs)
|
202
|
+
f = _make_click_function(app, signature, _inner)
|
175
203
|
|
176
|
-
|
204
|
+
with_click_options = _add_click_options(f, signature.parameters)
|
177
205
|
|
178
|
-
|
179
|
-
|
206
|
+
if signature.has_variadic_args:
|
207
|
+
return click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})(
|
208
|
+
with_click_options
|
209
|
+
)
|
210
|
+
else:
|
211
|
+
return click.command(with_click_options)
|
180
212
|
|
181
213
|
|
182
214
|
def _get_click_command_for_cls(app: App, method_ref: MethodReference):
|
183
|
-
|
215
|
+
parameters: dict[str, ParameterMetadata]
|
184
216
|
cls = method_ref.cls
|
185
217
|
method_name = method_ref.method_name
|
186
218
|
|
187
219
|
cls_signature = _get_signature(cls._get_user_cls())
|
220
|
+
|
221
|
+
if cls_signature.has_variadic_args:
|
222
|
+
raise InvalidError("Modal classes cannot have variable-length positional arguments (*args).")
|
223
|
+
|
188
224
|
partial_functions = cls._get_partial_functions()
|
189
225
|
|
190
226
|
if method_name in ("*", ""):
|
@@ -202,27 +238,35 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference):
|
|
202
238
|
fun_signature = _get_signature(partial_function._get_raw_f(), is_method=True)
|
203
239
|
|
204
240
|
# TODO(erikbern): assert there's no overlap?
|
205
|
-
|
241
|
+
parameters = dict(**cls_signature.parameters, **fun_signature.parameters) # Pool all arguments
|
206
242
|
|
207
|
-
def _inner(click_kwargs):
|
243
|
+
def _inner(args, click_kwargs):
|
208
244
|
# unpool class and method arguments
|
209
245
|
# TODO(erikbern): this code is a bit hacky
|
210
|
-
cls_kwargs = {k: click_kwargs[k] for k in cls_signature}
|
211
|
-
fun_kwargs = {k: click_kwargs[k] for k in fun_signature}
|
246
|
+
cls_kwargs = {k: click_kwargs[k] for k in cls_signature.parameters}
|
247
|
+
fun_kwargs = {k: click_kwargs[k] for k in fun_signature.parameters}
|
212
248
|
|
213
249
|
instance = cls(**cls_kwargs)
|
214
250
|
method: Function = getattr(instance, method_name)
|
215
|
-
return method.remote(**fun_kwargs)
|
251
|
+
return method.remote(*args, **fun_kwargs)
|
252
|
+
|
253
|
+
f = _make_click_function(app, fun_signature, _inner)
|
254
|
+
with_click_options = _add_click_options(f, parameters)
|
216
255
|
|
217
|
-
|
218
|
-
|
219
|
-
|
256
|
+
if fun_signature.has_variadic_args:
|
257
|
+
return click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})(
|
258
|
+
with_click_options
|
259
|
+
)
|
260
|
+
else:
|
261
|
+
return click.command(with_click_options)
|
220
262
|
|
221
263
|
|
222
264
|
def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoint):
|
223
265
|
func = entrypoint.info.raw_f
|
224
266
|
isasync = inspect.iscoroutinefunction(func)
|
225
267
|
|
268
|
+
signature = _get_signature(func)
|
269
|
+
|
226
270
|
@click.pass_context
|
227
271
|
def f(ctx, *args, **kwargs):
|
228
272
|
if ctx.obj["detach"]:
|
@@ -231,6 +275,10 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
231
275
|
"triggered Modal function alive after the parent process has been killed or disconnected."
|
232
276
|
)
|
233
277
|
|
278
|
+
if signature.has_variadic_args:
|
279
|
+
assert len(args) == 0 and len(kwargs) == 0
|
280
|
+
args = ctx.args
|
281
|
+
|
234
282
|
show_progress: bool = ctx.obj["show_progress"]
|
235
283
|
with enable_output(show_progress):
|
236
284
|
with run_app(
|
@@ -250,8 +298,14 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
250
298
|
if result_path := ctx.obj["result_path"]:
|
251
299
|
_write_local_result(result_path, res)
|
252
300
|
|
253
|
-
with_click_options = _add_click_options(f,
|
254
|
-
|
301
|
+
with_click_options = _add_click_options(f, signature.parameters)
|
302
|
+
|
303
|
+
if signature.has_variadic_args:
|
304
|
+
return click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})(
|
305
|
+
with_click_options
|
306
|
+
)
|
307
|
+
else:
|
308
|
+
return click.command(with_click_options)
|
255
309
|
|
256
310
|
|
257
311
|
def _get_runnable_list(all_usable_commands: list[CLICommand]) -> str:
|
modal/client.pyi
CHANGED
@@ -27,7 +27,7 @@ class _Client:
|
|
27
27
|
_snapshotted: bool
|
28
28
|
|
29
29
|
def __init__(
|
30
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.
|
30
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.11"
|
31
31
|
): ...
|
32
32
|
def is_closed(self) -> bool: ...
|
33
33
|
@property
|
@@ -85,7 +85,7 @@ class Client:
|
|
85
85
|
_snapshotted: bool
|
86
86
|
|
87
87
|
def __init__(
|
88
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.
|
88
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.11"
|
89
89
|
): ...
|
90
90
|
def is_closed(self) -> bool: ...
|
91
91
|
@property
|
modal/functions.pyi
CHANGED
@@ -57,7 +57,7 @@ class Function(
|
|
57
57
|
def __init__(self, *args, **kwargs): ...
|
58
58
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction): ...
|
59
59
|
@staticmethod
|
60
|
-
def
|
60
|
+
def from_local(
|
61
61
|
info: modal._utils.function_utils.FunctionInfo,
|
62
62
|
app,
|
63
63
|
image: modal.image.Image,
|
@@ -200,11 +200,11 @@ class Function(
|
|
200
200
|
|
201
201
|
_call_generator_nowait: ___call_generator_nowait_spec[typing_extensions.Self]
|
202
202
|
|
203
|
-
class __remote_spec(typing_extensions.Protocol[
|
203
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
204
204
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
205
205
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
206
206
|
|
207
|
-
remote: __remote_spec[modal._functions.
|
207
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
208
208
|
|
209
209
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
210
210
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -219,19 +219,19 @@ class Function(
|
|
219
219
|
self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
|
220
220
|
) -> modal._functions.OriginalReturnType: ...
|
221
221
|
|
222
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
222
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
223
223
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
224
224
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
225
225
|
|
226
226
|
_experimental_spawn: ___experimental_spawn_spec[
|
227
|
-
modal._functions.
|
227
|
+
modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
|
228
228
|
]
|
229
229
|
|
230
|
-
class __spawn_spec(typing_extensions.Protocol[
|
230
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
231
231
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
232
232
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
233
233
|
|
234
|
-
spawn: __spawn_spec[modal._functions.
|
234
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
235
235
|
|
236
236
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
|
237
237
|
|
modal/image.py
CHANGED
@@ -402,12 +402,14 @@ class _Image(_Object, type_prefix="im"):
|
|
402
402
|
_deferred_mounts: Sequence[
|
403
403
|
_Mount
|
404
404
|
] # added as mounts on any container referencing the Image, see `def _mount_layers`
|
405
|
+
_added_python_source_set: frozenset[str] # used to warn about missing mounts during auto-mount deprecation
|
405
406
|
_metadata: Optional[api_pb2.ImageMetadata] = None # set on hydration, private for now
|
406
407
|
|
407
408
|
def _initialize_from_empty(self):
|
408
409
|
self.inside_exceptions = []
|
409
410
|
self._serve_mounts = frozenset()
|
410
411
|
self._deferred_mounts = ()
|
412
|
+
self._added_python_source_set = frozenset()
|
411
413
|
self.force_build = False
|
412
414
|
|
413
415
|
def _initialize_from_other(self, other: "_Image"):
|
@@ -416,6 +418,7 @@ class _Image(_Object, type_prefix="im"):
|
|
416
418
|
self.force_build = other.force_build
|
417
419
|
self._serve_mounts = other._serve_mounts
|
418
420
|
self._deferred_mounts = other._deferred_mounts
|
421
|
+
self._added_python_source_set = other._added_python_source_set
|
419
422
|
|
420
423
|
def _hydrate_metadata(self, metadata: Optional[Message]):
|
421
424
|
env_image_id = config.get("image_id") # set as an env var in containers
|
@@ -440,7 +443,9 @@ class _Image(_Object, type_prefix="im"):
|
|
440
443
|
self2._deferred_mounts = tuple(base_image._deferred_mounts) + (mount,)
|
441
444
|
self2._serve_mounts = base_image._serve_mounts | ({mount} if mount.is_local() else set())
|
442
445
|
|
443
|
-
|
446
|
+
img = _Image._from_loader(_load, "Image(local files)", deps=lambda: [base_image, mount])
|
447
|
+
img._added_python_source_set = base_image._added_python_source_set
|
448
|
+
return img
|
444
449
|
|
445
450
|
@property
|
446
451
|
def _mount_layers(self) -> typing.Sequence[_Mount]:
|
@@ -659,6 +664,9 @@ class _Image(_Object, type_prefix="im"):
|
|
659
664
|
rep = f"Image({dockerfile_function})"
|
660
665
|
obj = _Image._from_loader(_load, rep, deps=_deps)
|
661
666
|
obj.force_build = force_build
|
667
|
+
obj._added_python_source_set = frozenset.union(
|
668
|
+
frozenset(), *(base._added_python_source_set for base in base_images.values())
|
669
|
+
)
|
662
670
|
return obj
|
663
671
|
|
664
672
|
def copy_mount(self, mount: _Mount, remote_path: Union[str, Path] = ".") -> "_Image":
|
@@ -843,7 +851,9 @@ class _Image(_Object, type_prefix="im"):
|
|
843
851
|
```
|
844
852
|
"""
|
845
853
|
mount = _Mount._from_local_python_packages(*modules, ignore=ignore)
|
846
|
-
|
854
|
+
img = self._add_mount_layer_or_copy(mount, copy=copy)
|
855
|
+
img._added_python_source_set |= set(modules)
|
856
|
+
return img
|
847
857
|
|
848
858
|
def copy_local_dir(
|
849
859
|
self,
|
@@ -1954,7 +1964,7 @@ class _Image(_Object, type_prefix="im"):
|
|
1954
1964
|
|
1955
1965
|
info = FunctionInfo(raw_f)
|
1956
1966
|
|
1957
|
-
function = _Function.
|
1967
|
+
function = _Function.from_local(
|
1958
1968
|
info,
|
1959
1969
|
app=None,
|
1960
1970
|
image=self, # type: ignore[reportArgumentType] # TODO: probably conflict with type stub?
|
modal/image.pyi
CHANGED
@@ -85,6 +85,7 @@ class _Image(modal._object._Object):
|
|
85
85
|
inside_exceptions: list[Exception]
|
86
86
|
_serve_mounts: frozenset[modal.mount._Mount]
|
87
87
|
_deferred_mounts: collections.abc.Sequence[modal.mount._Mount]
|
88
|
+
_added_python_source_set: frozenset[str]
|
88
89
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
89
90
|
|
90
91
|
def _initialize_from_empty(self): ...
|
@@ -355,6 +356,7 @@ class Image(modal.object.Object):
|
|
355
356
|
inside_exceptions: list[Exception]
|
356
357
|
_serve_mounts: frozenset[modal.mount.Mount]
|
357
358
|
_deferred_mounts: collections.abc.Sequence[modal.mount.Mount]
|
359
|
+
_added_python_source_set: frozenset[str]
|
358
360
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
359
361
|
|
360
362
|
def __init__(self, *args, **kwargs): ...
|
modal/mount.py
CHANGED
@@ -821,14 +821,14 @@ def _is_modal_path(remote_path: PurePosixPath):
|
|
821
821
|
return False
|
822
822
|
|
823
823
|
|
824
|
-
def
|
824
|
+
def get_sys_modules_mounts() -> dict[str, _Mount]:
|
825
825
|
"""mdmd:hidden
|
826
826
|
|
827
827
|
Auto-mount local modules that have been imported in global scope.
|
828
828
|
This may or may not include the "entrypoint" of the function as well, depending on how modal is invoked
|
829
829
|
Note: sys.modules may change during the iteration
|
830
830
|
"""
|
831
|
-
auto_mounts =
|
831
|
+
auto_mounts = {}
|
832
832
|
top_level_modules = []
|
833
833
|
skip_prefixes = set()
|
834
834
|
for name, module in sorted(sys.modules.items(), key=lambda kv: len(kv[0])):
|
@@ -858,6 +858,6 @@ def get_auto_mounts() -> list[_Mount]:
|
|
858
858
|
# skip any module that has paths in SYS_PREFIXES, or would overwrite the modal Package in the container
|
859
859
|
break
|
860
860
|
else:
|
861
|
-
auto_mounts
|
861
|
+
auto_mounts[module_name] = potential_mount
|
862
862
|
|
863
863
|
return auto_mounts
|
modal/mount.pyi
CHANGED
@@ -306,7 +306,7 @@ def _create_client_mount(): ...
|
|
306
306
|
def create_client_mount(): ...
|
307
307
|
def _get_client_mount(): ...
|
308
308
|
def _is_modal_path(remote_path: pathlib.PurePosixPath): ...
|
309
|
-
def
|
309
|
+
def get_sys_modules_mounts() -> dict[str, _Mount]: ...
|
310
310
|
|
311
311
|
ROOT_DIR: pathlib.PurePosixPath
|
312
312
|
|
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=scYhGFqh8OJcVDo-VOxIT6CCwxOgzgflYWMnIZiMRqE,2871
|
|
3
3
|
modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
|
4
4
|
modal/_clustered_functions.pyi,sha256=vllkegc99A0jrUOWa8mdlSbdp6uz36TsHhGxysAOpaQ,771
|
5
5
|
modal/_container_entrypoint.py,sha256=qahIuJvaMmWG85N5vNS1yuAQ9XZoo1ftzfatkos_q7I,29553
|
6
|
-
modal/_functions.py,sha256=
|
6
|
+
modal/_functions.py,sha256=Yv8hutin0R_0ZRvUZm8kcjVMf4CNAckgMSDpbLSUl0E,71593
|
7
7
|
modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
|
8
8
|
modal/_location.py,sha256=S3lSxIU3h9HkWpkJ3Pwo0pqjIOSB1fjeSgUsY3x7eec,1202
|
9
9
|
modal/_object.py,sha256=ItQcsMNkz9Y3kdTsvfNarbW-paJ2qabDyQ7njaqY0XI,11359
|
@@ -17,11 +17,11 @@ modal/_traceback.py,sha256=IZQzB3fVlUfMHOSyKUgw0H6qv4yHnpyq-XVCNZKfUdA,5023
|
|
17
17
|
modal/_tunnel.py,sha256=zTBxBiuH1O22tS1OliAJdIsSmaZS8PlnifS_6S5z-mk,6320
|
18
18
|
modal/_tunnel.pyi,sha256=JmmDYAy9F1FpgJ_hWx0xkom2nTOFQjn4mTPYlU3PFo4,1245
|
19
19
|
modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
|
20
|
-
modal/app.py,sha256=
|
20
|
+
modal/app.py,sha256=MaWCYgNx8y2GQhmaXQBMKKAAfCYfdxrdYs6zCBoJzwI,44628
|
21
21
|
modal/app.pyi,sha256=lxiuWzE_OLb3WHg-H7Pek9DGBuCUzZ55P594VhJL5LA,26113
|
22
22
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
23
23
|
modal/client.py,sha256=8SQawr7P1PNUCq1UmJMUQXG2jIo4Nmdcs311XqrNLRE,15276
|
24
|
-
modal/client.pyi,sha256=
|
24
|
+
modal/client.pyi,sha256=ALJFDxGokLAB9KoQ5frmUZt3pg2VcXN2CAp_v7ukNuw,7593
|
25
25
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
26
26
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
27
27
|
modal/cls.py,sha256=kNnZrBYVXOhgEXU0rDWk2Hr-bQRrsZkMKDgC-TD_6Bs,31063
|
@@ -40,14 +40,14 @@ modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
|
|
40
40
|
modal/file_io.pyi,sha256=NTRft1tbPSWf9TlWVeZmTlgB5AZ_Zhu2srWIrWr7brk,9445
|
41
41
|
modal/file_pattern_matcher.py,sha256=1cZ4V2wSLiaXqAqStETSwp3bzDD6QZOt6pmmjk3Okz4,6505
|
42
42
|
modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
|
43
|
-
modal/functions.pyi,sha256=
|
43
|
+
modal/functions.pyi,sha256=YflJx4BhzmJLJzpVWbuAMv0Qv63Mgb3r9qZqrgBEr1w,14289
|
44
44
|
modal/gpu.py,sha256=2qZMNnoMrjU-5Bu7fx68pANUAKTtZq0EWEEeBA9OUVQ,7426
|
45
|
-
modal/image.py,sha256=
|
46
|
-
modal/image.pyi,sha256=
|
45
|
+
modal/image.py,sha256=ekE2693foy30Xi1LM3swKZPW6HuaACj-OBvfspVSyIE,91509
|
46
|
+
modal/image.pyi,sha256=kdJzy1eaxNPZeCpE0TMYYLhJ6UWmkfRDeb_vzngJUoQ,26462
|
47
47
|
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
48
48
|
modal/io_streams.pyi,sha256=bJ7ZLmSmJ0nKoa6r4FJpbqvzdUVa0lEe0Fa-MMpMezU,5071
|
49
|
-
modal/mount.py,sha256=
|
50
|
-
modal/mount.pyi,sha256=
|
49
|
+
modal/mount.py,sha256=aUa_KTsUzpbRso5XfVw2UuKl9j1KdWEIpNCl9pF1_YI,32156
|
50
|
+
modal/mount.pyi,sha256=CmHa7zKSxHA_7-vMQLnGfw_ZXvAvHlafvUEVJcQ1LQA,12535
|
51
51
|
modal/network_file_system.py,sha256=WXdyL7du_fvjvuG6hSAREyJ83sSEP2xSLAIAhBsisdI,14869
|
52
52
|
modal/network_file_system.pyi,sha256=4N3eqMbTSlqmS8VV_aJK-uvrgJC8xnf_YtW5FHfRfc8,8156
|
53
53
|
modal/object.py,sha256=bTeskuY8JFrESjU4_UL_nTwYlBQdOLmVaOX3X6EMxsg,164
|
@@ -95,7 +95,7 @@ modal/_utils/blob_utils.py,sha256=N66LtZI8PpCkZ7maA7GLW5CAmYUoNJdG-GjaAUR4_NQ,14
|
|
95
95
|
modal/_utils/bytes_io_segment_payload.py,sha256=uunxVJS4PE1LojF_UpURMzVK9GuvmYWRqQo_bxEj5TU,3385
|
96
96
|
modal/_utils/deprecation.py,sha256=dycySRBxyZf3ITzEqPNM6MxXTk9-0VVLA8oCPQ5j_Os,3426
|
97
97
|
modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
|
98
|
-
modal/_utils/function_utils.py,sha256=
|
98
|
+
modal/_utils/function_utils.py,sha256=fc5RKK59SxV7Vg4JyURVB2CCLUNKQVcBG60_dUxviRM,27307
|
99
99
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
100
100
|
modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
|
101
101
|
modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
|
@@ -125,7 +125,7 @@ modal/cli/launch.py,sha256=pzQt2QlcrbIUU0MVzWWPAvMQ6MCyqsHZ0X9JcV-sY04,3242
|
|
125
125
|
modal/cli/network_file_system.py,sha256=eq3JnwjbfFNsJodIyANHL06ByYc3BSavzdmu8C96cHA,7948
|
126
126
|
modal/cli/profile.py,sha256=rLXfjJObfPNjaZvNfHGIKqs7y9bGYyGe-K7V0w-Ni0M,3110
|
127
127
|
modal/cli/queues.py,sha256=6gTu76dzBtPN5eQVsLrvQpuru5jI9ZCWK5Eh8J8XhaM,4498
|
128
|
-
modal/cli/run.py,sha256=
|
128
|
+
modal/cli/run.py,sha256=hXCvqQM6w7dXz0254q5Sr0UNR5JC6cEA6fBdrcshCcg,21784
|
129
129
|
modal/cli/secret.py,sha256=uQpwYrMY98iMCWeZOQTcktOYhPTZ8IHnyealDc2CZqo,4206
|
130
130
|
modal/cli/token.py,sha256=mxSgOWakXG6N71hQb1ko61XAR9ZGkTMZD-Txn7gmTac,1924
|
131
131
|
modal/cli/utils.py,sha256=hZmjyzcPjDnQSkLvycZD2LhGdcsfdZshs_rOU78EpvI,3717
|
@@ -171,10 +171,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
171
171
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
172
172
|
modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
|
173
173
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
174
|
-
modal_version/_version_generated.py,sha256=
|
175
|
-
modal-0.73.
|
176
|
-
modal-0.73.
|
177
|
-
modal-0.73.
|
178
|
-
modal-0.73.
|
179
|
-
modal-0.73.
|
180
|
-
modal-0.73.
|
174
|
+
modal_version/_version_generated.py,sha256=c8Anvfl2gD2odGTQ0RxHnfa44Y9okAWKU0eMINv_kPQ,149
|
175
|
+
modal-0.73.11.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
176
|
+
modal-0.73.11.dist-info/METADATA,sha256=zjTqoWXVVpSM6DjsVRIsiwjIK07gCrXAVodMWTUuXUQ,2330
|
177
|
+
modal-0.73.11.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
178
|
+
modal-0.73.11.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
179
|
+
modal-0.73.11.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
180
|
+
modal-0.73.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|