modal 0.66.17__py3-none-any.whl → 0.66.39__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/_container_entrypoint.py +4 -341
- modal/_runtime/container_io_manager.py +6 -14
- modal/_runtime/user_code_imports.py +361 -0
- modal/_utils/function_utils.py +28 -8
- modal/app.py +13 -46
- modal/cli/import_refs.py +4 -38
- modal/client.pyi +2 -2
- modal/dict.py +0 -6
- modal/dict.pyi +0 -4
- modal/experimental.py +0 -3
- modal/functions.py +10 -9
- modal/functions.pyi +8 -8
- modal/gpu.py +8 -6
- modal/image.py +93 -6
- modal/image.pyi +20 -2
- modal/mount.py +2 -1
- modal/network_file_system.py +0 -28
- modal/network_file_system.pyi +0 -14
- modal/partial_function.py +11 -1
- modal/queue.py +0 -6
- modal/queue.pyi +0 -4
- modal/sandbox.py +1 -1
- modal/volume.py +0 -22
- modal/volume.pyi +0 -9
- {modal-0.66.17.dist-info → modal-0.66.39.dist-info}/METADATA +1 -2
- {modal-0.66.17.dist-info → modal-0.66.39.dist-info}/RECORD +38 -37
- modal_proto/api.proto +1 -20
- modal_proto/api_grpc.py +0 -16
- modal_proto/api_pb2.py +389 -413
- modal_proto/api_pb2.pyi +5 -56
- modal_proto/api_pb2_grpc.py +0 -33
- modal_proto/api_pb2_grpc.pyi +0 -10
- modal_proto/modal_api_grpc.py +0 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.66.17.dist-info → modal-0.66.39.dist-info}/LICENSE +0 -0
- {modal-0.66.17.dist-info → modal-0.66.39.dist-info}/WHEEL +0 -0
- {modal-0.66.17.dist-info → modal-0.66.39.dist-info}/entry_points.txt +0 -0
- {modal-0.66.17.dist-info → modal-0.66.39.dist-info}/top_level.txt +0 -0
modal/functions.py
CHANGED
@@ -299,13 +299,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
299
299
|
"""Functions are the basic units of serverless execution on Modal.
|
300
300
|
|
301
301
|
Generally, you will not construct a `Function` directly. Instead, use the
|
302
|
-
|
303
|
-
for your application.
|
302
|
+
`App.function()` decorator to register your Python functions with your App.
|
304
303
|
"""
|
305
304
|
|
306
305
|
# TODO: more type annotations
|
307
306
|
_info: Optional[FunctionInfo]
|
308
|
-
|
307
|
+
_serve_mounts: typing.FrozenSet[_Mount] # set at load time, only by loader
|
309
308
|
_app: Optional["modal.app._App"] = None
|
310
309
|
_obj: Optional["modal.cls._Obj"] = None # only set for InstanceServiceFunctions and bound instance methods
|
311
310
|
_web_url: Optional[str]
|
@@ -580,6 +579,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
580
579
|
|
581
580
|
if is_local():
|
582
581
|
entrypoint_mounts = info.get_entrypoint_mount()
|
582
|
+
|
583
583
|
all_mounts = [
|
584
584
|
_get_client_mount(),
|
585
585
|
*explicit_mounts,
|
@@ -612,6 +612,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
612
612
|
if proxy:
|
613
613
|
# HACK: remove this once we stop using ssh tunnels for this.
|
614
614
|
if image:
|
615
|
+
# TODO(elias): this will cause an error if users use prior `.add_local_*` commands without copy=True
|
615
616
|
image = image.apt_install("autossh")
|
616
617
|
|
617
618
|
function_spec = _FunctionSpec(
|
@@ -828,7 +829,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
828
829
|
)
|
829
830
|
for path, volume in validated_volumes
|
830
831
|
]
|
831
|
-
loaded_mount_ids = {m.object_id for m in all_mounts}
|
832
|
+
loaded_mount_ids = {m.object_id for m in all_mounts} | {m.object_id for m in image._mount_layers}
|
832
833
|
|
833
834
|
# Get object dependencies
|
834
835
|
object_dependencies = []
|
@@ -970,9 +971,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
970
971
|
raise InvalidError(f"Function {info.function_name} is too large to deploy.")
|
971
972
|
raise
|
972
973
|
function_creation_status.set_response(response)
|
973
|
-
|
974
|
-
|
975
|
-
obj.
|
974
|
+
serve_mounts = set(m for m in all_mounts if m.is_local()) # needed for modal.serve file watching
|
975
|
+
serve_mounts |= image._serve_mounts
|
976
|
+
obj._serve_mounts = frozenset(serve_mounts)
|
976
977
|
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
977
978
|
|
978
979
|
rep = f"Function({tag})"
|
@@ -1028,7 +1029,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1028
1029
|
identity = "class service function for a parameterized class"
|
1029
1030
|
if not self._parent.is_hydrated:
|
1030
1031
|
if self._parent.app._running_app is None:
|
1031
|
-
reason = ", because the App it is defined on is not running
|
1032
|
+
reason = ", because the App it is defined on is not running"
|
1032
1033
|
else:
|
1033
1034
|
reason = ""
|
1034
1035
|
raise ExecutionError(
|
@@ -1223,7 +1224,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1223
1224
|
self._function_name = None
|
1224
1225
|
self._info = None
|
1225
1226
|
self._use_function_id = ""
|
1226
|
-
self.
|
1227
|
+
self._serve_mounts = frozenset()
|
1227
1228
|
|
1228
1229
|
def _hydrate_metadata(self, metadata: Optional[Message]):
|
1229
1230
|
# Overridden concrete implementation of base class method
|
modal/functions.pyi
CHANGED
@@ -110,7 +110,7 @@ OriginalReturnType = typing.TypeVar("OriginalReturnType", covariant=True)
|
|
110
110
|
|
111
111
|
class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object._Object):
|
112
112
|
_info: typing.Optional[modal._utils.function_utils.FunctionInfo]
|
113
|
-
|
113
|
+
_serve_mounts: typing.FrozenSet[modal.mount._Mount]
|
114
114
|
_app: typing.Optional[modal.app._App]
|
115
115
|
_obj: typing.Optional[modal.cls._Obj]
|
116
116
|
_web_url: typing.Optional[str]
|
@@ -286,7 +286,7 @@ P_INNER = typing_extensions.ParamSpec("P_INNER")
|
|
286
286
|
|
287
287
|
class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.Object):
|
288
288
|
_info: typing.Optional[modal._utils.function_utils.FunctionInfo]
|
289
|
-
|
289
|
+
_serve_mounts: typing.FrozenSet[modal.mount.Mount]
|
290
290
|
_app: typing.Optional[modal.app.App]
|
291
291
|
_obj: typing.Optional[modal.cls.Obj]
|
292
292
|
_web_url: typing.Optional[str]
|
@@ -450,11 +450,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
450
450
|
|
451
451
|
_call_generator_nowait: ___call_generator_nowait_spec
|
452
452
|
|
453
|
-
class __remote_spec(typing_extensions.Protocol[
|
453
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
454
454
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
455
455
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
456
456
|
|
457
|
-
remote: __remote_spec[
|
457
|
+
remote: __remote_spec[ReturnType, P]
|
458
458
|
|
459
459
|
class __remote_gen_spec(typing_extensions.Protocol):
|
460
460
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -466,17 +466,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
466
466
|
def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
|
467
467
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
468
468
|
|
469
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
469
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
470
470
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
471
471
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
472
472
|
|
473
|
-
_experimental_spawn: ___experimental_spawn_spec[
|
473
|
+
_experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
|
474
474
|
|
475
|
-
class __spawn_spec(typing_extensions.Protocol[
|
475
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
476
476
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
477
477
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
478
478
|
|
479
|
-
spawn: __spawn_spec[
|
479
|
+
spawn: __spawn_spec[ReturnType, P]
|
480
480
|
|
481
481
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
482
482
|
|
modal/gpu.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import Optional, Union
|
3
|
+
from typing import Callable, Optional, Union
|
4
4
|
|
5
5
|
from modal_proto import api_pb2
|
6
6
|
|
@@ -147,25 +147,27 @@ class Any(_GPUConfig):
|
|
147
147
|
return f"GPU(Any, count={self.count})"
|
148
148
|
|
149
149
|
|
150
|
-
STRING_TO_GPU_CONFIG = {
|
150
|
+
STRING_TO_GPU_CONFIG: dict[str, Callable] = {
|
151
151
|
"t4": T4,
|
152
152
|
"l4": L4,
|
153
153
|
"a100": A100,
|
154
|
+
"a100-80gb": lambda: A100(size="80GB"),
|
154
155
|
"h100": H100,
|
155
156
|
"a10g": A10G,
|
156
157
|
"any": Any,
|
157
158
|
}
|
158
|
-
display_string_to_config = "\n".join(
|
159
|
-
f'- "{key}" → `{cls()}`' for key, cls in STRING_TO_GPU_CONFIG.items() if key != "inf2"
|
160
|
-
)
|
159
|
+
display_string_to_config = "\n".join(f'- "{key}" → `{c()}`' for key, c in STRING_TO_GPU_CONFIG.items() if key != "inf2")
|
161
160
|
__doc__ = f"""
|
162
161
|
**GPU configuration shortcodes**
|
163
162
|
|
164
163
|
The following are the valid `str` values for the `gpu` parameter of
|
165
|
-
[`@app.function`](/docs/reference/modal.
|
164
|
+
[`@app.function`](/docs/reference/modal.App#function).
|
166
165
|
|
167
166
|
{display_string_to_config}
|
168
167
|
|
168
|
+
The shortcodes also support specifying count by suffixing `:N` to acquire `N` GPUs.
|
169
|
+
For example, `a10g:4` will provision 4 A10G GPUs.
|
170
|
+
|
169
171
|
Other configurations can be created using the constructors documented below.
|
170
172
|
"""
|
171
173
|
|
modal/image.py
CHANGED
@@ -273,12 +273,24 @@ class _Image(_Object, type_prefix="im"):
|
|
273
273
|
|
274
274
|
force_build: bool
|
275
275
|
inside_exceptions: List[Exception]
|
276
|
-
|
276
|
+
_serve_mounts: typing.FrozenSet[_Mount] # used for mounts watching in `modal serve`
|
277
|
+
_deferred_mounts: Sequence[
|
278
|
+
_Mount
|
279
|
+
] # added as mounts on any container referencing the Image, see `def _mount_layers`
|
277
280
|
_metadata: Optional[api_pb2.ImageMetadata] = None # set on hydration, private for now
|
278
281
|
|
279
282
|
def _initialize_from_empty(self):
|
280
283
|
self.inside_exceptions = []
|
281
|
-
self.
|
284
|
+
self._serve_mounts = frozenset()
|
285
|
+
self._deferred_mounts = ()
|
286
|
+
self.force_build = False
|
287
|
+
|
288
|
+
def _initialize_from_other(self, other: "_Image"):
|
289
|
+
# used by .clone()
|
290
|
+
self.inside_exceptions = other.inside_exceptions
|
291
|
+
self.force_build = other.force_build
|
292
|
+
self._serve_mounts = other._serve_mounts
|
293
|
+
self._deferred_mounts = other._deferred_mounts
|
282
294
|
|
283
295
|
def _hydrate_metadata(self, message: Optional[Message]):
|
284
296
|
env_image_id = config.get("image_id") # set as an env var in containers
|
@@ -292,6 +304,51 @@ class _Image(_Object, type_prefix="im"):
|
|
292
304
|
assert isinstance(message, api_pb2.ImageMetadata)
|
293
305
|
self._metadata = message
|
294
306
|
|
307
|
+
def _add_mount_layer_or_copy(self, mount: _Mount, copy: bool = False):
|
308
|
+
if copy:
|
309
|
+
return self.copy_mount(mount, remote_path="/")
|
310
|
+
|
311
|
+
base_image = self
|
312
|
+
|
313
|
+
async def _load(self2: "_Image", resolver: Resolver, existing_object_id: Optional[str]):
|
314
|
+
self2._hydrate_from_other(base_image) # same image id as base image as long as it's lazy
|
315
|
+
self2._deferred_mounts = tuple(base_image._deferred_mounts) + (mount,)
|
316
|
+
self2._serve_mounts = base_image._serve_mounts | ({mount} if mount.is_local() else set())
|
317
|
+
|
318
|
+
return _Image._from_loader(_load, "Image(local files)", deps=lambda: [base_image, mount])
|
319
|
+
|
320
|
+
@property
|
321
|
+
def _mount_layers(self) -> typing.Tuple[_Mount]:
|
322
|
+
"""Non-evaluated mount layers on the image
|
323
|
+
|
324
|
+
When the image is used by a Modal container, these mounts need to be attached as well to
|
325
|
+
represent the full image content, as they haven't yet been represented as a layer in the
|
326
|
+
image.
|
327
|
+
|
328
|
+
When the image is used as a base image for a new layer (that is not itself a mount layer)
|
329
|
+
these mounts need to first be inserted as a copy operation (.copy_mount) into the image.
|
330
|
+
"""
|
331
|
+
return self._deferred_mounts
|
332
|
+
|
333
|
+
def _assert_no_mount_layers(self):
|
334
|
+
if self._mount_layers:
|
335
|
+
raise InvalidError(
|
336
|
+
"An image tried to run a build step after using `image.add_local_*` to include local files.\n"
|
337
|
+
"\n"
|
338
|
+
"Run `image.add_local_*` commands last in your image build to avoid rebuilding images with every local "
|
339
|
+
"file change. Modal will then add these files to containers on startup instead, saving build time.\n"
|
340
|
+
"If you need to run other build steps after adding local files, set `copy=True` to copy the files"
|
341
|
+
"directly into the image, at the expense of some added build time.\n"
|
342
|
+
"\n"
|
343
|
+
"Example:\n"
|
344
|
+
"\n"
|
345
|
+
"my_image = (\n"
|
346
|
+
" Image.debian_slim()\n"
|
347
|
+
' .add_local_python_packages("mypak", copy=True)\n'
|
348
|
+
' .run_commands("python -m mypak") # this now works!\n'
|
349
|
+
")\n"
|
350
|
+
)
|
351
|
+
|
295
352
|
@staticmethod
|
296
353
|
def _from_args(
|
297
354
|
*,
|
@@ -306,9 +363,11 @@ class _Image(_Object, type_prefix="im"):
|
|
306
363
|
force_build: bool = False,
|
307
364
|
# For internal use only.
|
308
365
|
_namespace: int = api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
366
|
+
_do_assert_no_mount_layers: bool = True,
|
309
367
|
):
|
310
368
|
if base_images is None:
|
311
369
|
base_images = {}
|
370
|
+
|
312
371
|
if secrets is None:
|
313
372
|
secrets = []
|
314
373
|
if gpu_config is None:
|
@@ -334,6 +393,11 @@ class _Image(_Object, type_prefix="im"):
|
|
334
393
|
return deps
|
335
394
|
|
336
395
|
async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
|
396
|
+
if _do_assert_no_mount_layers:
|
397
|
+
for image in base_images.values():
|
398
|
+
# base images can't have
|
399
|
+
image._assert_no_mount_layers()
|
400
|
+
|
337
401
|
environment = await _get_environment_cached(resolver.environment_name or "", resolver.client)
|
338
402
|
# A bit hacky,but assume that the environment provides a valid builder version
|
339
403
|
image_builder_version = cast(ImageBuilderVersion, environment._settings.image_builder_version)
|
@@ -349,7 +413,9 @@ class _Image(_Object, type_prefix="im"):
|
|
349
413
|
"No commands were provided for the image — have you tried using modal.Image.debian_slim()?"
|
350
414
|
)
|
351
415
|
if dockerfile.commands and build_function:
|
352
|
-
raise InvalidError(
|
416
|
+
raise InvalidError(
|
417
|
+
"Cannot provide both build function and Dockerfile commands in the same image layer!"
|
418
|
+
)
|
353
419
|
|
354
420
|
base_images_pb2s = [
|
355
421
|
api_pb2.BaseImage(
|
@@ -482,12 +548,12 @@ class _Image(_Object, type_prefix="im"):
|
|
482
548
|
self._hydrate(image_id, resolver.client, result_response.metadata)
|
483
549
|
local_mounts = set()
|
484
550
|
for base in base_images.values():
|
485
|
-
local_mounts |= base.
|
551
|
+
local_mounts |= base._serve_mounts
|
486
552
|
if context_mount and context_mount.is_local():
|
487
553
|
local_mounts.add(context_mount)
|
488
|
-
self.
|
554
|
+
self._serve_mounts = frozenset(local_mounts)
|
489
555
|
|
490
|
-
rep = "Image()"
|
556
|
+
rep = f"Image({dockerfile_function})"
|
491
557
|
obj = _Image._from_loader(_load, rep, deps=_deps)
|
492
558
|
obj.force_build = force_build
|
493
559
|
return obj
|
@@ -553,6 +619,27 @@ class _Image(_Object, type_prefix="im"):
|
|
553
619
|
context_mount=mount,
|
554
620
|
)
|
555
621
|
|
622
|
+
def _add_local_python_packages(self, *packages: Union[str, Path], copy: bool = False) -> "_Image":
|
623
|
+
"""Adds Python package files to containers
|
624
|
+
|
625
|
+
Adds all files from the specified Python packages to containers running the Image.
|
626
|
+
|
627
|
+
Packages are added to the `/root` directory of containers, which is on the `PYTHONPATH`
|
628
|
+
of any executed Modal Functions.
|
629
|
+
|
630
|
+
By default (`copy=False`), the files are added to containers on startup and are not built into the actual Image,
|
631
|
+
which speeds up deployment.
|
632
|
+
|
633
|
+
Set `copy=True` to copy the files into an Image layer at build time instead. This can slow down iteration since
|
634
|
+
it requires a rebuild of the Image and any subsequent build steps whenever the included files change, but it is
|
635
|
+
required if you want to run additional build steps after this one.
|
636
|
+
|
637
|
+
**Note:** This excludes all dot-prefixed subdirectories or files and all `.pyc`/`__pycache__` files.
|
638
|
+
To add full directories with finer control, use `.add_local_dir()` instead.
|
639
|
+
"""
|
640
|
+
mount = _Mount.from_local_python_packages(*packages)
|
641
|
+
return self._add_mount_layer_or_copy(mount, copy=copy)
|
642
|
+
|
556
643
|
def copy_local_dir(self, local_path: Union[str, Path], remote_path: Union[str, Path] = ".") -> "_Image":
|
557
644
|
"""Copy a directory into the image as a part of building the image.
|
558
645
|
|
modal/image.pyi
CHANGED
@@ -58,11 +58,17 @@ class DockerfileSpec:
|
|
58
58
|
class _Image(modal.object._Object):
|
59
59
|
force_build: bool
|
60
60
|
inside_exceptions: typing.List[Exception]
|
61
|
-
|
61
|
+
_serve_mounts: typing.FrozenSet[modal.mount._Mount]
|
62
|
+
_deferred_mounts: typing.Sequence[modal.mount._Mount]
|
62
63
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
63
64
|
|
64
65
|
def _initialize_from_empty(self): ...
|
66
|
+
def _initialize_from_other(self, other: _Image): ...
|
65
67
|
def _hydrate_metadata(self, message: typing.Optional[google.protobuf.message.Message]): ...
|
68
|
+
def _add_mount_layer_or_copy(self, mount: modal.mount._Mount, copy: bool = False): ...
|
69
|
+
@property
|
70
|
+
def _mount_layers(self) -> typing.Tuple[modal.mount._Mount]: ...
|
71
|
+
def _assert_no_mount_layers(self): ...
|
66
72
|
@staticmethod
|
67
73
|
def _from_args(
|
68
74
|
*,
|
@@ -78,6 +84,7 @@ class _Image(modal.object._Object):
|
|
78
84
|
context_mount: typing.Optional[modal.mount._Mount] = None,
|
79
85
|
force_build: bool = False,
|
80
86
|
_namespace: int = 1,
|
87
|
+
_do_assert_no_mount_layers: bool = True,
|
81
88
|
): ...
|
82
89
|
def extend(
|
83
90
|
self,
|
@@ -90,11 +97,13 @@ class _Image(modal.object._Object):
|
|
90
97
|
context_mount: typing.Optional[modal.mount._Mount] = None,
|
91
98
|
force_build: bool = False,
|
92
99
|
_namespace: int = 1,
|
100
|
+
_do_assert_no_mount_layers: bool = True,
|
93
101
|
) -> _Image: ...
|
94
102
|
def copy_mount(self, mount: modal.mount._Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> _Image: ...
|
95
103
|
def copy_local_file(
|
96
104
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
|
97
105
|
) -> _Image: ...
|
106
|
+
def _add_local_python_packages(self, *module_names, copy: bool = False) -> _Image: ...
|
98
107
|
def copy_local_dir(
|
99
108
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "."
|
100
109
|
) -> _Image: ...
|
@@ -299,12 +308,18 @@ class _Image(modal.object._Object):
|
|
299
308
|
class Image(modal.object.Object):
|
300
309
|
force_build: bool
|
301
310
|
inside_exceptions: typing.List[Exception]
|
302
|
-
|
311
|
+
_serve_mounts: typing.FrozenSet[modal.mount.Mount]
|
312
|
+
_deferred_mounts: typing.Sequence[modal.mount.Mount]
|
303
313
|
_metadata: typing.Optional[modal_proto.api_pb2.ImageMetadata]
|
304
314
|
|
305
315
|
def __init__(self, *args, **kwargs): ...
|
306
316
|
def _initialize_from_empty(self): ...
|
317
|
+
def _initialize_from_other(self, other: Image): ...
|
307
318
|
def _hydrate_metadata(self, message: typing.Optional[google.protobuf.message.Message]): ...
|
319
|
+
def _add_mount_layer_or_copy(self, mount: modal.mount.Mount, copy: bool = False): ...
|
320
|
+
@property
|
321
|
+
def _mount_layers(self) -> typing.Tuple[modal.mount.Mount]: ...
|
322
|
+
def _assert_no_mount_layers(self): ...
|
308
323
|
@staticmethod
|
309
324
|
def _from_args(
|
310
325
|
*,
|
@@ -320,6 +335,7 @@ class Image(modal.object.Object):
|
|
320
335
|
context_mount: typing.Optional[modal.mount.Mount] = None,
|
321
336
|
force_build: bool = False,
|
322
337
|
_namespace: int = 1,
|
338
|
+
_do_assert_no_mount_layers: bool = True,
|
323
339
|
): ...
|
324
340
|
def extend(
|
325
341
|
self,
|
@@ -332,11 +348,13 @@ class Image(modal.object.Object):
|
|
332
348
|
context_mount: typing.Optional[modal.mount.Mount] = None,
|
333
349
|
force_build: bool = False,
|
334
350
|
_namespace: int = 1,
|
351
|
+
_do_assert_no_mount_layers: bool = True,
|
335
352
|
) -> Image: ...
|
336
353
|
def copy_mount(self, mount: modal.mount.Mount, remote_path: typing.Union[str, pathlib.Path] = ".") -> Image: ...
|
337
354
|
def copy_local_file(
|
338
355
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "./"
|
339
356
|
) -> Image: ...
|
357
|
+
def _add_local_python_packages(self, *module_names, copy: bool = False) -> Image: ...
|
340
358
|
def copy_local_dir(
|
341
359
|
self, local_path: typing.Union[str, pathlib.Path], remote_path: typing.Union[str, pathlib.Path] = "."
|
342
360
|
) -> Image: ...
|
modal/mount.py
CHANGED
@@ -622,12 +622,13 @@ class _Mount(_Object, type_prefix="mo"):
|
|
622
622
|
client: Optional[_Client] = None,
|
623
623
|
) -> None:
|
624
624
|
check_object_name(deployment_name, "Mount")
|
625
|
+
environment_name = _get_environment_name(environment_name, resolver=None)
|
625
626
|
self._deployment_name = deployment_name
|
626
627
|
self._namespace = namespace
|
627
628
|
self._environment_name = environment_name
|
628
629
|
if client is None:
|
629
630
|
client = await _Client.from_env()
|
630
|
-
resolver = Resolver(client=client)
|
631
|
+
resolver = Resolver(client=client, environment_name=environment_name)
|
631
632
|
await resolver.load(self)
|
632
633
|
|
633
634
|
def _get_metadata(self) -> api_pb2.MountHandleMetadata:
|
modal/network_file_system.py
CHANGED
@@ -171,34 +171,6 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
171
171
|
tc.infinite_loop(lambda: client.stub.SharedVolumeHeartbeat(request), sleep=_heartbeat_sleep)
|
172
172
|
yield cls._new_hydrated(response.shared_volume_id, client, None, is_another_app=True)
|
173
173
|
|
174
|
-
@staticmethod
|
175
|
-
def persisted(
|
176
|
-
label: str,
|
177
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
178
|
-
environment_name: Optional[str] = None,
|
179
|
-
cloud: Optional[str] = None,
|
180
|
-
):
|
181
|
-
"""mdmd:hidden"""
|
182
|
-
message = (
|
183
|
-
"`NetworkFileSystem.persisted` is deprecated."
|
184
|
-
" Please use `NetworkFileSystem.from_name(name, create_if_missing=True)` instead."
|
185
|
-
)
|
186
|
-
deprecation_error((2024, 3, 1), message)
|
187
|
-
|
188
|
-
def persist(
|
189
|
-
self,
|
190
|
-
label: str,
|
191
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
192
|
-
environment_name: Optional[str] = None,
|
193
|
-
cloud: Optional[str] = None,
|
194
|
-
):
|
195
|
-
"""mdmd:hidden"""
|
196
|
-
message = (
|
197
|
-
"`NetworkFileSystem().persist('my-volume')` is deprecated."
|
198
|
-
" Please use `NetworkFileSystem.from_name('my-volume', create_if_missing=True)` instead."
|
199
|
-
)
|
200
|
-
deprecation_error((2024, 2, 29), message)
|
201
|
-
|
202
174
|
@staticmethod
|
203
175
|
async def lookup(
|
204
176
|
label: str,
|
modal/network_file_system.pyi
CHANGED
@@ -26,13 +26,6 @@ class _NetworkFileSystem(modal.object._Object):
|
|
26
26
|
_heartbeat_sleep: float = 300,
|
27
27
|
) -> typing.AsyncContextManager[_NetworkFileSystem]: ...
|
28
28
|
@staticmethod
|
29
|
-
def persisted(
|
30
|
-
label: str, namespace=1, environment_name: typing.Optional[str] = None, cloud: typing.Optional[str] = None
|
31
|
-
): ...
|
32
|
-
def persist(
|
33
|
-
self, label: str, namespace=1, environment_name: typing.Optional[str] = None, cloud: typing.Optional[str] = None
|
34
|
-
): ...
|
35
|
-
@staticmethod
|
36
29
|
async def lookup(
|
37
30
|
label: str,
|
38
31
|
namespace=1,
|
@@ -85,13 +78,6 @@ class NetworkFileSystem(modal.object.Object):
|
|
85
78
|
environment_name: typing.Optional[str] = None,
|
86
79
|
_heartbeat_sleep: float = 300,
|
87
80
|
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[NetworkFileSystem]: ...
|
88
|
-
@staticmethod
|
89
|
-
def persisted(
|
90
|
-
label: str, namespace=1, environment_name: typing.Optional[str] = None, cloud: typing.Optional[str] = None
|
91
|
-
): ...
|
92
|
-
def persist(
|
93
|
-
self, label: str, namespace=1, environment_name: typing.Optional[str] = None, cloud: typing.Optional[str] = None
|
94
|
-
): ...
|
95
81
|
|
96
82
|
class __lookup_spec(typing_extensions.Protocol):
|
97
83
|
def __call__(
|
modal/partial_function.py
CHANGED
@@ -199,6 +199,7 @@ class _MethodDecoratorType:
|
|
199
199
|
...
|
200
200
|
|
201
201
|
|
202
|
+
# TODO(elias): fix support for coroutine type unwrapping for methods (static typing)
|
202
203
|
def _method(
|
203
204
|
_warn_parentheses_missing=None,
|
204
205
|
*,
|
@@ -207,7 +208,6 @@ def _method(
|
|
207
208
|
is_generator: Optional[bool] = None,
|
208
209
|
keep_warm: Optional[int] = None, # Deprecated: Use keep_warm on @app.cls() instead
|
209
210
|
) -> _MethodDecoratorType:
|
210
|
-
# TODO(elias): fix support for coroutine type unwrapping for methods (static typing)
|
211
211
|
"""Decorator for methods that should be transformed into a Modal Function registered against this class's App.
|
212
212
|
|
213
213
|
**Usage:**
|
@@ -379,6 +379,11 @@ def _asgi_app(
|
|
379
379
|
f"Modal will drop support for default parameters in a future release.",
|
380
380
|
)
|
381
381
|
|
382
|
+
if inspect.iscoroutinefunction(raw_f):
|
383
|
+
raise InvalidError(
|
384
|
+
f"ASGI app function {raw_f.__name__} is an async function. Only sync Python functions are supported."
|
385
|
+
)
|
386
|
+
|
382
387
|
if not wait_for_response:
|
383
388
|
deprecation_error(
|
384
389
|
(2024, 5, 13),
|
@@ -448,6 +453,11 @@ def _wsgi_app(
|
|
448
453
|
f"Modal will drop support for default parameters in a future release.",
|
449
454
|
)
|
450
455
|
|
456
|
+
if inspect.iscoroutinefunction(raw_f):
|
457
|
+
raise InvalidError(
|
458
|
+
f"WSGI app function {raw_f.__name__} is an async function. Only sync Python functions are supported."
|
459
|
+
)
|
460
|
+
|
451
461
|
if not wait_for_response:
|
452
462
|
deprecation_error(
|
453
463
|
(2024, 5, 13),
|
modal/queue.py
CHANGED
@@ -175,12 +175,6 @@ class _Queue(_Object, type_prefix="qu"):
|
|
175
175
|
|
176
176
|
return _Queue._from_loader(_load, "Queue()", is_another_app=True, hydrate_lazily=True)
|
177
177
|
|
178
|
-
@staticmethod
|
179
|
-
def persisted(label: str, namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE, environment_name: Optional[str] = None):
|
180
|
-
"""mdmd:hidden"""
|
181
|
-
message = "`Queue.persisted` is deprecated. Please use `Queue.from_name(name, create_if_missing=True)` instead."
|
182
|
-
deprecation_error((2024, 3, 1), message)
|
183
|
-
|
184
178
|
@staticmethod
|
185
179
|
async def lookup(
|
186
180
|
label: str,
|
modal/queue.pyi
CHANGED
@@ -22,8 +22,6 @@ class _Queue(modal.object._Object):
|
|
22
22
|
label: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
23
23
|
) -> _Queue: ...
|
24
24
|
@staticmethod
|
25
|
-
def persisted(label: str, namespace=1, environment_name: typing.Optional[str] = None): ...
|
26
|
-
@staticmethod
|
27
25
|
async def lookup(
|
28
26
|
label: str,
|
29
27
|
namespace=1,
|
@@ -104,8 +102,6 @@ class Queue(modal.object.Object):
|
|
104
102
|
def from_name(
|
105
103
|
label: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
106
104
|
) -> Queue: ...
|
107
|
-
@staticmethod
|
108
|
-
def persisted(label: str, namespace=1, environment_name: typing.Optional[str] = None): ...
|
109
105
|
|
110
106
|
class __lookup_spec(typing_extensions.Protocol):
|
111
107
|
def __call__(
|
modal/sandbox.py
CHANGED
@@ -148,7 +148,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
148
148
|
definition = api_pb2.Sandbox(
|
149
149
|
entrypoint_args=entrypoint_args,
|
150
150
|
image_id=image.object_id,
|
151
|
-
mount_ids=[mount.object_id for mount in mounts],
|
151
|
+
mount_ids=[mount.object_id for mount in mounts] + [mount.object_id for mount in image._mount_layers],
|
152
152
|
secret_ids=[secret.object_id for secret in secrets],
|
153
153
|
timeout_secs=timeout,
|
154
154
|
workdir=workdir,
|
modal/volume.py
CHANGED
@@ -79,15 +79,6 @@ class FileEntry:
|
|
79
79
|
size=proto.size,
|
80
80
|
)
|
81
81
|
|
82
|
-
def __getattr__(self, name: str):
|
83
|
-
deprecation_error(
|
84
|
-
(2024, 4, 15),
|
85
|
-
(
|
86
|
-
f"The FileEntry dataclass was introduced to replace a private Protobuf message. "
|
87
|
-
f"This dataclass does not have the {name} attribute."
|
88
|
-
),
|
89
|
-
)
|
90
|
-
|
91
82
|
|
92
83
|
class _Volume(_Object, type_prefix="vo"):
|
93
84
|
"""A writeable volume that can be used to share files between one or more Modal functions.
|
@@ -222,19 +213,6 @@ class _Volume(_Object, type_prefix="vo"):
|
|
222
213
|
tc.infinite_loop(lambda: client.stub.VolumeHeartbeat(request), sleep=_heartbeat_sleep)
|
223
214
|
yield cls._new_hydrated(response.volume_id, client, None, is_another_app=True)
|
224
215
|
|
225
|
-
@staticmethod
|
226
|
-
def persisted(
|
227
|
-
label: str,
|
228
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
229
|
-
environment_name: Optional[str] = None,
|
230
|
-
cloud: Optional[str] = None,
|
231
|
-
):
|
232
|
-
"""mdmd:hidden"""
|
233
|
-
message = (
|
234
|
-
"`Volume.persisted` is deprecated. Please use `Volume.from_name(name, create_if_missing=True)` instead."
|
235
|
-
)
|
236
|
-
deprecation_error((2024, 3, 1), message)
|
237
|
-
|
238
216
|
@staticmethod
|
239
217
|
async def lookup(
|
240
218
|
label: str,
|
modal/volume.pyi
CHANGED
@@ -25,7 +25,6 @@ class FileEntry:
|
|
25
25
|
|
26
26
|
@classmethod
|
27
27
|
def _from_proto(cls, proto: modal_proto.api_pb2.FileEntry) -> FileEntry: ...
|
28
|
-
def __getattr__(self, name: str): ...
|
29
28
|
def __init__(self, path: str, type: FileEntryType, mtime: int, size: int) -> None: ...
|
30
29
|
def __repr__(self): ...
|
31
30
|
def __eq__(self, other): ...
|
@@ -56,10 +55,6 @@ class _Volume(modal.object._Object):
|
|
56
55
|
_heartbeat_sleep: float = 300,
|
57
56
|
) -> typing.AsyncContextManager[_Volume]: ...
|
58
57
|
@staticmethod
|
59
|
-
def persisted(
|
60
|
-
label: str, namespace=1, environment_name: typing.Optional[str] = None, cloud: typing.Optional[str] = None
|
61
|
-
): ...
|
62
|
-
@staticmethod
|
63
58
|
async def lookup(
|
64
59
|
label: str,
|
65
60
|
namespace=1,
|
@@ -149,10 +144,6 @@ class Volume(modal.object.Object):
|
|
149
144
|
version: typing.Optional[int] = None,
|
150
145
|
_heartbeat_sleep: float = 300,
|
151
146
|
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Volume]: ...
|
152
|
-
@staticmethod
|
153
|
-
def persisted(
|
154
|
-
label: str, namespace=1, environment_name: typing.Optional[str] = None, cloud: typing.Optional[str] = None
|
155
|
-
): ...
|
156
147
|
|
157
148
|
class __lookup_spec(typing_extensions.Protocol):
|
158
149
|
def __call__(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: modal
|
3
|
-
Version: 0.66.
|
3
|
+
Version: 0.66.39
|
4
4
|
Summary: Python client library for Modal
|
5
5
|
Author: Modal Labs
|
6
6
|
Author-email: support@modal.com
|
@@ -15,7 +15,6 @@ Requires-Python: >=3.9
|
|
15
15
|
Description-Content-Type: text/markdown
|
16
16
|
License-File: LICENSE
|
17
17
|
Requires-Dist: aiohttp
|
18
|
-
Requires-Dist: aiostream (~=0.5.2)
|
19
18
|
Requires-Dist: certifi
|
20
19
|
Requires-Dist: click (>=8.1.0)
|
21
20
|
Requires-Dist: fastapi
|