modal 0.62.115__py3-none-any.whl → 0.72.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modal/__init__.py +13 -9
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +402 -398
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -60
- modal/_resources.py +26 -7
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1025 -0
- modal/{execution_context.py → _runtime/execution_context.py} +11 -2
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +123 -6
- modal/_traceback.py +47 -187
- modal/_tunnel.py +50 -14
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +386 -104
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +299 -98
- modal/_utils/grpc_testing.py +47 -34
- modal/_utils/grpc_utils.py +54 -21
- modal/_utils/hash_utils.py +51 -10
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +3 -3
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +12 -10
- modal/app.py +561 -323
- modal/app.pyi +474 -262
- modal/call_graph.py +7 -6
- modal/cli/_download.py +22 -6
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +203 -42
- modal/cli/config.py +12 -5
- modal/cli/container.py +61 -13
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +21 -48
- modal/cli/launch.py +28 -14
- modal/cli/network_file_system.py +57 -21
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +34 -9
- modal/cli/programs/vscode.py +58 -8
- modal/cli/queues.py +131 -0
- modal/cli/run.py +199 -96
- modal/cli/secret.py +5 -4
- modal/cli/token.py +7 -2
- modal/cli/utils.py +74 -8
- modal/cli/volume.py +97 -56
- modal/client.py +248 -144
- modal/client.pyi +156 -124
- modal/cloud_bucket_mount.py +43 -30
- modal/cloud_bucket_mount.pyi +32 -25
- modal/cls.py +528 -141
- modal/cls.pyi +189 -145
- modal/config.py +32 -15
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +50 -54
- modal/dict.pyi +120 -164
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +30 -43
- modal/experimental.py +62 -2
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +196 -0
- modal/functions.py +846 -428
- modal/functions.pyi +446 -387
- modal/gpu.py +57 -44
- modal/image.py +943 -417
- modal/image.pyi +584 -245
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +223 -90
- modal/mount.pyi +241 -243
- modal/network_file_system.py +85 -86
- modal/network_file_system.pyi +151 -110
- modal/object.py +66 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +73 -47
- modal/parallel_map.pyi +51 -63
- modal/partial_function.py +272 -107
- modal/partial_function.pyi +219 -120
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +96 -72
- modal/queue.pyi +210 -135
- modal/requirements/2024.04.txt +2 -1
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +45 -4
- modal/runner.py +325 -203
- modal/runner.pyi +124 -110
- modal/running_app.py +27 -4
- modal/sandbox.py +509 -231
- modal/sandbox.pyi +396 -169
- modal/schedule.py +2 -2
- modal/scheduler_placement.py +20 -3
- modal/secret.py +41 -25
- modal/secret.pyi +62 -42
- modal/serving.py +39 -49
- modal/serving.pyi +37 -43
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +123 -137
- modal/volume.pyi +228 -221
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/METADATA +5 -5
- modal-0.72.13.dist-info/RECORD +174 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +1 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1231 -531
- modal_proto/api_grpc.py +750 -430
- modal_proto/api_pb2.py +2102 -1176
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1329 -675
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_exec.py +0 -128
- modal/_container_io_manager.py +0 -646
- modal/_container_io_manager.pyi +0 -412
- modal/_sandbox_shell.py +0 -49
- modal/app_utils.py +0 -20
- modal/app_utils.pyi +0 -17
- modal/execution_context.pyi +0 -37
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal-0.62.115.dist-info/RECORD +0 -207
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -279
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -674
- test/client_test.py +0 -203
- test/cloud_bucket_mount_test.py +0 -22
- test/cls_test.py +0 -636
- test/config_test.py +0 -149
- test/conftest.py +0 -1485
- test/container_app_test.py +0 -50
- test/container_test.py +0 -1405
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -51
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -791
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -82
- test/helpers.py +0 -47
- test/image_test.py +0 -814
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -327
- test/network_file_system_test.py +0 -188
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -115
- test/resolver_test.py +0 -59
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -57
- test/secret_test.py +0 -89
- test/serialization_test.py +0 -50
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -361
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -397
- test/watcher_test.py +0 -58
- test/webhook_test.py +0 -145
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/LICENSE +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/WHEEL +0 -0
- {modal-0.62.115.dist-info → modal-0.72.13.dist-info}/entry_points.txt +0 -0
modal/_resolver.py
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# Copyright Modal Labs 2023
|
2
2
|
import asyncio
|
3
3
|
import contextlib
|
4
|
+
import typing
|
4
5
|
from asyncio import Future
|
5
|
-
from
|
6
|
+
from collections.abc import Hashable
|
7
|
+
from typing import TYPE_CHECKING, Optional
|
6
8
|
|
7
9
|
from grpclib import GRPCError, Status
|
8
10
|
|
9
|
-
from
|
10
|
-
|
11
|
-
from .exception import
|
11
|
+
from ._utils.async_utils import TaskContext
|
12
|
+
from .client import _Client
|
13
|
+
from .exception import NotFoundError
|
12
14
|
|
13
15
|
if TYPE_CHECKING:
|
14
16
|
from rich.tree import Tree
|
@@ -17,61 +19,62 @@ if TYPE_CHECKING:
|
|
17
19
|
|
18
20
|
|
19
21
|
class StatusRow:
|
20
|
-
def __init__(self, progress: "Optional[Tree]"):
|
21
|
-
from ._output import (
|
22
|
-
step_progress,
|
23
|
-
)
|
24
|
-
|
22
|
+
def __init__(self, progress: "typing.Optional[Tree]"):
|
25
23
|
self._spinner = None
|
26
24
|
self._step_node = None
|
27
25
|
if progress is not None:
|
28
|
-
|
26
|
+
from ._output import OutputManager
|
27
|
+
|
28
|
+
self._spinner = OutputManager.step_progress()
|
29
29
|
self._step_node = progress.add(self._spinner)
|
30
30
|
|
31
31
|
def message(self, message):
|
32
|
-
from ._output import step_progress_update
|
33
|
-
|
34
32
|
if self._spinner is not None:
|
35
|
-
|
33
|
+
self._spinner.update(text=message)
|
36
34
|
|
37
35
|
def finish(self, message):
|
38
|
-
from ._output import step_completed, step_progress_update
|
39
|
-
|
40
36
|
if self._step_node is not None:
|
41
|
-
|
42
|
-
|
37
|
+
from ._output import OutputManager
|
38
|
+
|
39
|
+
self._spinner.update(text=message)
|
40
|
+
self._step_node.label = OutputManager.substep_completed(message)
|
43
41
|
|
44
42
|
|
45
43
|
class Resolver:
|
46
|
-
_local_uuid_to_future:
|
44
|
+
_local_uuid_to_future: dict[str, Future]
|
47
45
|
_environment_name: Optional[str]
|
48
46
|
_app_id: Optional[str]
|
49
|
-
_deduplication_cache:
|
47
|
+
_deduplication_cache: dict[Hashable, Future]
|
48
|
+
_client: _Client
|
50
49
|
|
51
50
|
def __init__(
|
52
51
|
self,
|
53
|
-
client
|
52
|
+
client: _Client,
|
54
53
|
*,
|
55
|
-
output_mgr=None,
|
56
54
|
environment_name: Optional[str] = None,
|
57
55
|
app_id: Optional[str] = None,
|
58
56
|
):
|
59
|
-
|
57
|
+
try:
|
58
|
+
# TODO(michael) If we don't clean this up more thoroughly, it would probably
|
59
|
+
# be good to have a single source of truth for "rich is installed" rather than
|
60
|
+
# doing a try/catch everywhere we want to use it.
|
61
|
+
from rich.tree import Tree
|
62
|
+
|
63
|
+
from ._output import OutputManager
|
60
64
|
|
61
|
-
|
65
|
+
tree = Tree(OutputManager.step_progress("Creating objects..."), guide_style="gray50")
|
66
|
+
except ImportError:
|
67
|
+
tree = None
|
62
68
|
|
63
|
-
self._output_mgr = output_mgr
|
64
69
|
self._local_uuid_to_future = {}
|
65
|
-
self._tree =
|
70
|
+
self._tree = tree
|
66
71
|
self._client = client
|
67
72
|
self._app_id = app_id
|
68
73
|
self._environment_name = environment_name
|
69
74
|
self._deduplication_cache = {}
|
70
75
|
|
71
76
|
@property
|
72
|
-
def app_id(self) -> str:
|
73
|
-
if self._app_id is None:
|
74
|
-
raise ExecutionError("Resolver has no app")
|
77
|
+
def app_id(self) -> Optional[str]:
|
75
78
|
return self._app_id
|
76
79
|
|
77
80
|
@property
|
@@ -118,7 +121,7 @@ class Resolver:
|
|
118
121
|
async def loader():
|
119
122
|
# Wait for all its dependencies
|
120
123
|
# TODO(erikbern): do we need existing_object_id for those?
|
121
|
-
await
|
124
|
+
await TaskContext.gather(*[self.load(dep) for dep in obj.deps()])
|
122
125
|
|
123
126
|
# Load the object itself
|
124
127
|
try:
|
@@ -128,16 +131,18 @@ class Resolver:
|
|
128
131
|
raise NotFoundError(exc.message)
|
129
132
|
raise
|
130
133
|
|
131
|
-
# Check that the id of functions
|
132
|
-
#
|
133
|
-
if
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
134
|
+
# Check that the id of functions didn't change
|
135
|
+
# Persisted refs are ignored because their life cycle is managed independently.
|
136
|
+
if (
|
137
|
+
not obj._is_another_app
|
138
|
+
and existing_object_id is not None
|
139
|
+
and existing_object_id.startswith("fu-")
|
140
|
+
and obj.object_id != existing_object_id
|
141
|
+
):
|
142
|
+
raise Exception(
|
143
|
+
f"Tried creating an object using existing id {existing_object_id}"
|
144
|
+
f" but it has id {obj.object_id}"
|
145
|
+
)
|
141
146
|
|
142
147
|
return obj
|
143
148
|
|
@@ -146,10 +151,11 @@ class Resolver:
|
|
146
151
|
if deduplication_key is not None:
|
147
152
|
self._deduplication_cache[deduplication_key] = cached_future
|
148
153
|
|
154
|
+
# TODO(elias): print original exception/trace rather than the Resolver-internal trace
|
149
155
|
return await cached_future
|
150
156
|
|
151
|
-
def objects(self) ->
|
152
|
-
unique_objects:
|
157
|
+
def objects(self) -> list["_Object"]:
|
158
|
+
unique_objects: dict[str, "_Object"] = {}
|
153
159
|
for fut in self._local_uuid_to_future.values():
|
154
160
|
if not fut.done():
|
155
161
|
# this will raise an exception if not all loads have been awaited, but that *should* never happen
|
@@ -162,27 +168,16 @@ class Resolver:
|
|
162
168
|
|
163
169
|
@contextlib.contextmanager
|
164
170
|
def display(self):
|
165
|
-
|
171
|
+
# TODO(erikbern): get rid of this wrapper
|
172
|
+
from .output import _get_output_manager
|
166
173
|
|
167
|
-
if self.
|
168
|
-
|
169
|
-
else:
|
170
|
-
with self._output_mgr.ctx_if_visible(self._output_mgr.make_live(self._tree)):
|
174
|
+
if self._tree and (output_mgr := _get_output_manager()):
|
175
|
+
with output_mgr.make_live(self._tree):
|
171
176
|
yield
|
172
|
-
self._tree.label = step_completed("Created objects.")
|
173
|
-
|
177
|
+
self._tree.label = output_mgr.step_completed("Created objects.")
|
178
|
+
output_mgr.print(self._tree)
|
179
|
+
else:
|
180
|
+
yield
|
174
181
|
|
175
182
|
def add_status_row(self) -> StatusRow:
|
176
183
|
return StatusRow(self._tree)
|
177
|
-
|
178
|
-
async def console_write(self, log: api_pb2.TaskLogs):
|
179
|
-
if self._output_mgr is not None:
|
180
|
-
await self._output_mgr.put_log_content(log)
|
181
|
-
|
182
|
-
def console_flush(self):
|
183
|
-
if self._output_mgr is not None:
|
184
|
-
self._output_mgr.flush_lines()
|
185
|
-
|
186
|
-
def image_snapshot_update(self, image_id: str, task_progress: api_pb2.TaskProgress):
|
187
|
-
if self._output_mgr is not None:
|
188
|
-
self._output_mgr.update_snapshot_progress(image_id, task_progress)
|
modal/_resources.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Copyright Modal Labs 2024
|
2
|
-
from typing import Optional,
|
2
|
+
from typing import Optional, Union
|
3
3
|
|
4
4
|
from modal_proto import api_pb2
|
5
5
|
|
@@ -9,14 +9,28 @@ from .gpu import GPU_T, parse_gpu_config
|
|
9
9
|
|
10
10
|
def convert_fn_config_to_resources_config(
|
11
11
|
*,
|
12
|
-
cpu: Optional[float],
|
13
|
-
memory: Optional[Union[int,
|
12
|
+
cpu: Optional[Union[float, tuple[float, float]]],
|
13
|
+
memory: Optional[Union[int, tuple[int, int]]],
|
14
14
|
gpu: GPU_T,
|
15
|
+
ephemeral_disk: Optional[int],
|
15
16
|
) -> api_pb2.Resources:
|
16
|
-
if cpu is not None and cpu < 0.1:
|
17
|
-
raise InvalidError(f"Invalid fractional CPU value {cpu}. Cannot have less than 0.10 CPU resources.")
|
18
17
|
gpu_config = parse_gpu_config(gpu)
|
19
|
-
|
18
|
+
if cpu and isinstance(cpu, tuple):
|
19
|
+
if not cpu[0]:
|
20
|
+
raise InvalidError("CPU request must be a positive number")
|
21
|
+
elif not cpu[1]:
|
22
|
+
raise InvalidError("CPU limit must be a positive number")
|
23
|
+
milli_cpu = int(1000 * cpu[0])
|
24
|
+
milli_cpu_max = int(1000 * cpu[1])
|
25
|
+
if milli_cpu_max < milli_cpu:
|
26
|
+
raise InvalidError(f"Cannot specify a CPU limit lower than request: {milli_cpu_max} < {milli_cpu}")
|
27
|
+
elif cpu and isinstance(cpu, (float, int)):
|
28
|
+
milli_cpu = int(1000 * cpu)
|
29
|
+
milli_cpu_max = None
|
30
|
+
else:
|
31
|
+
milli_cpu = None
|
32
|
+
milli_cpu_max = None
|
33
|
+
|
20
34
|
if memory and isinstance(memory, int):
|
21
35
|
memory_mb = memory
|
22
36
|
memory_mb_max = 0 # no limit
|
@@ -28,5 +42,10 @@ def convert_fn_config_to_resources_config(
|
|
28
42
|
memory_mb = 0
|
29
43
|
memory_mb_max = 0
|
30
44
|
return api_pb2.Resources(
|
31
|
-
milli_cpu=milli_cpu,
|
45
|
+
milli_cpu=milli_cpu,
|
46
|
+
milli_cpu_max=milli_cpu_max,
|
47
|
+
gpu_config=gpu_config,
|
48
|
+
memory_mb=memory_mb,
|
49
|
+
memory_mb_max=memory_mb_max,
|
50
|
+
ephemeral_disk_mb=ephemeral_disk,
|
32
51
|
)
|
@@ -0,0 +1 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|