modal 0.62.16__py3-none-any.whl → 0.72.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modal/__init__.py +17 -13
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +420 -937
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -59
- modal/_resources.py +51 -0
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1036 -0
- modal/_runtime/execution_context.py +89 -0
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +134 -9
- modal/_traceback.py +47 -187
- modal/_tunnel.py +52 -16
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +479 -100
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +460 -171
- modal/_utils/grpc_testing.py +47 -31
- modal/_utils/grpc_utils.py +62 -109
- modal/_utils/hash_utils.py +61 -19
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +5 -7
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +14 -12
- modal/app.py +1003 -314
- modal/app.pyi +540 -264
- modal/call_graph.py +7 -6
- modal/cli/_download.py +63 -53
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +205 -45
- modal/cli/config.py +12 -5
- modal/cli/container.py +62 -14
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +64 -58
- modal/cli/launch.py +32 -18
- modal/cli/network_file_system.py +64 -83
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +35 -10
- modal/cli/programs/vscode.py +60 -10
- modal/cli/queues.py +131 -0
- modal/cli/run.py +234 -131
- modal/cli/secret.py +8 -7
- modal/cli/token.py +7 -2
- modal/cli/utils.py +79 -10
- modal/cli/volume.py +110 -109
- modal/client.py +250 -144
- modal/client.pyi +157 -118
- modal/cloud_bucket_mount.py +108 -34
- modal/cloud_bucket_mount.pyi +32 -38
- modal/cls.py +535 -148
- modal/cls.pyi +190 -146
- modal/config.py +41 -19
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +111 -65
- modal/dict.pyi +136 -131
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +34 -43
- modal/experimental.py +61 -2
- modal/extensions/ipython.py +5 -5
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +197 -0
- modal/functions.py +906 -911
- modal/functions.pyi +466 -430
- modal/gpu.py +57 -44
- modal/image.py +1089 -479
- modal/image.pyi +584 -228
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +314 -101
- modal/mount.pyi +241 -235
- modal/network_file_system.py +92 -92
- modal/network_file_system.pyi +152 -110
- modal/object.py +67 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +434 -0
- modal/parallel_map.pyi +75 -0
- modal/partial_function.py +282 -117
- modal/partial_function.pyi +222 -129
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +182 -65
- modal/queue.pyi +218 -118
- modal/requirements/2024.04.txt +29 -0
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +48 -7
- modal/runner.py +459 -156
- modal/runner.pyi +135 -71
- modal/running_app.py +38 -0
- modal/sandbox.py +514 -236
- modal/sandbox.pyi +397 -169
- modal/schedule.py +4 -4
- modal/scheduler_placement.py +20 -3
- modal/secret.py +56 -31
- modal/secret.pyi +62 -42
- modal/serving.py +51 -56
- modal/serving.pyi +44 -36
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +285 -157
- modal/volume.pyi +249 -184
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
- modal-0.72.11.dist-info/RECORD +174 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +5 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1288 -533
- modal_proto/api_grpc.py +856 -456
- modal_proto/api_pb2.py +2165 -1157
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1674 -855
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_entrypoint.pyi +0 -378
- modal/_container_exec.py +0 -128
- modal/_sandbox_shell.py +0 -49
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal/stub.py +0 -783
- modal/stub.pyi +0 -332
- modal-0.62.16.dist-info/RECORD +0 -198
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -262
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -659
- test/client_test.py +0 -194
- test/cls_test.py +0 -630
- test/config_test.py +0 -137
- test/conftest.py +0 -1420
- test/container_app_test.py +0 -32
- test/container_test.py +0 -1389
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -33
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -653
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -141
- test/helpers.py +0 -42
- test/image_test.py +0 -669
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -329
- test/network_file_system_test.py +0 -181
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -97
- test/resolver_test.py +0 -58
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -29
- test/secret_test.py +0 -78
- test/serialization_test.py +0 -42
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -360
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -341
- test/watcher_test.py +0 -30
- test/webhook_test.py +0 -146
- /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
- /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
modal/_resolver.py
CHANGED
@@ -1,13 +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
|
-
from
|
11
|
+
from ._utils.async_utils import TaskContext
|
12
|
+
from .client import _Client
|
13
|
+
from .exception import NotFoundError
|
11
14
|
|
12
15
|
if TYPE_CHECKING:
|
13
16
|
from rich.tree import Tree
|
@@ -16,61 +19,62 @@ if TYPE_CHECKING:
|
|
16
19
|
|
17
20
|
|
18
21
|
class StatusRow:
|
19
|
-
def __init__(self, progress: "Optional[Tree]"):
|
20
|
-
from ._output import (
|
21
|
-
step_progress,
|
22
|
-
)
|
23
|
-
|
22
|
+
def __init__(self, progress: "typing.Optional[Tree]"):
|
24
23
|
self._spinner = None
|
25
24
|
self._step_node = None
|
26
25
|
if progress is not None:
|
27
|
-
|
26
|
+
from ._output import OutputManager
|
27
|
+
|
28
|
+
self._spinner = OutputManager.step_progress()
|
28
29
|
self._step_node = progress.add(self._spinner)
|
29
30
|
|
30
31
|
def message(self, message):
|
31
|
-
from ._output import step_progress_update
|
32
|
-
|
33
32
|
if self._spinner is not None:
|
34
|
-
|
33
|
+
self._spinner.update(text=message)
|
35
34
|
|
36
35
|
def finish(self, message):
|
37
|
-
from ._output import step_completed, step_progress_update
|
38
|
-
|
39
36
|
if self._step_node is not None:
|
40
|
-
|
41
|
-
|
37
|
+
from ._output import OutputManager
|
38
|
+
|
39
|
+
self._spinner.update(text=message)
|
40
|
+
self._step_node.label = OutputManager.substep_completed(message)
|
42
41
|
|
43
42
|
|
44
43
|
class Resolver:
|
45
|
-
_local_uuid_to_future:
|
44
|
+
_local_uuid_to_future: dict[str, Future]
|
46
45
|
_environment_name: Optional[str]
|
47
46
|
_app_id: Optional[str]
|
48
|
-
_deduplication_cache:
|
47
|
+
_deduplication_cache: dict[Hashable, Future]
|
48
|
+
_client: _Client
|
49
49
|
|
50
50
|
def __init__(
|
51
51
|
self,
|
52
|
-
client
|
52
|
+
client: _Client,
|
53
53
|
*,
|
54
|
-
output_mgr=None,
|
55
54
|
environment_name: Optional[str] = None,
|
56
55
|
app_id: Optional[str] = None,
|
57
56
|
):
|
58
|
-
|
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
|
59
64
|
|
60
|
-
|
65
|
+
tree = Tree(OutputManager.step_progress("Creating objects..."), guide_style="gray50")
|
66
|
+
except ImportError:
|
67
|
+
tree = None
|
61
68
|
|
62
|
-
self._output_mgr = output_mgr
|
63
69
|
self._local_uuid_to_future = {}
|
64
|
-
self._tree =
|
70
|
+
self._tree = tree
|
65
71
|
self._client = client
|
66
72
|
self._app_id = app_id
|
67
73
|
self._environment_name = environment_name
|
68
74
|
self._deduplication_cache = {}
|
69
75
|
|
70
76
|
@property
|
71
|
-
def app_id(self) -> str:
|
72
|
-
if self._app_id is None:
|
73
|
-
raise ExecutionError("Resolver has no app")
|
77
|
+
def app_id(self) -> Optional[str]:
|
74
78
|
return self._app_id
|
75
79
|
|
76
80
|
@property
|
@@ -117,7 +121,7 @@ class Resolver:
|
|
117
121
|
async def loader():
|
118
122
|
# Wait for all its dependencies
|
119
123
|
# TODO(erikbern): do we need existing_object_id for those?
|
120
|
-
await
|
124
|
+
await TaskContext.gather(*[self.load(dep) for dep in obj.deps()])
|
121
125
|
|
122
126
|
# Load the object itself
|
123
127
|
try:
|
@@ -127,16 +131,18 @@ class Resolver:
|
|
127
131
|
raise NotFoundError(exc.message)
|
128
132
|
raise
|
129
133
|
|
130
|
-
# Check that the id of functions
|
131
|
-
#
|
132
|
-
if
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
+
)
|
140
146
|
|
141
147
|
return obj
|
142
148
|
|
@@ -145,10 +151,11 @@ class Resolver:
|
|
145
151
|
if deduplication_key is not None:
|
146
152
|
self._deduplication_cache[deduplication_key] = cached_future
|
147
153
|
|
154
|
+
# TODO(elias): print original exception/trace rather than the Resolver-internal trace
|
148
155
|
return await cached_future
|
149
156
|
|
150
|
-
def objects(self) ->
|
151
|
-
unique_objects:
|
157
|
+
def objects(self) -> list["_Object"]:
|
158
|
+
unique_objects: dict[str, "_Object"] = {}
|
152
159
|
for fut in self._local_uuid_to_future.values():
|
153
160
|
if not fut.done():
|
154
161
|
# this will raise an exception if not all loads have been awaited, but that *should* never happen
|
@@ -161,27 +168,16 @@ class Resolver:
|
|
161
168
|
|
162
169
|
@contextlib.contextmanager
|
163
170
|
def display(self):
|
164
|
-
|
171
|
+
# TODO(erikbern): get rid of this wrapper
|
172
|
+
from .output import _get_output_manager
|
165
173
|
|
166
|
-
if self.
|
167
|
-
|
168
|
-
else:
|
169
|
-
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):
|
170
176
|
yield
|
171
|
-
self._tree.label = step_completed("Created objects.")
|
172
|
-
|
177
|
+
self._tree.label = output_mgr.step_completed("Created objects.")
|
178
|
+
output_mgr.print(self._tree)
|
179
|
+
else:
|
180
|
+
yield
|
173
181
|
|
174
182
|
def add_status_row(self) -> StatusRow:
|
175
183
|
return StatusRow(self._tree)
|
176
|
-
|
177
|
-
async def console_write(self, log: api_pb2.TaskLogs):
|
178
|
-
if self._output_mgr is not None:
|
179
|
-
await self._output_mgr.put_log_content(log)
|
180
|
-
|
181
|
-
def console_flush(self):
|
182
|
-
if self._output_mgr is not None:
|
183
|
-
self._output_mgr.flush_lines()
|
184
|
-
|
185
|
-
def image_snapshot_update(self, image_id: str, task_progress: api_pb2.TaskProgress):
|
186
|
-
if self._output_mgr is not None:
|
187
|
-
self._output_mgr.update_snapshot_progress(image_id, task_progress)
|
modal/_resources.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
from typing import Optional, Union
|
3
|
+
|
4
|
+
from modal_proto import api_pb2
|
5
|
+
|
6
|
+
from .exception import InvalidError
|
7
|
+
from .gpu import GPU_T, parse_gpu_config
|
8
|
+
|
9
|
+
|
10
|
+
def convert_fn_config_to_resources_config(
|
11
|
+
*,
|
12
|
+
cpu: Optional[Union[float, tuple[float, float]]],
|
13
|
+
memory: Optional[Union[int, tuple[int, int]]],
|
14
|
+
gpu: GPU_T,
|
15
|
+
ephemeral_disk: Optional[int],
|
16
|
+
) -> api_pb2.Resources:
|
17
|
+
gpu_config = parse_gpu_config(gpu)
|
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
|
+
|
34
|
+
if memory and isinstance(memory, int):
|
35
|
+
memory_mb = memory
|
36
|
+
memory_mb_max = 0 # no limit
|
37
|
+
elif memory and isinstance(memory, tuple):
|
38
|
+
memory_mb, memory_mb_max = memory
|
39
|
+
if memory_mb_max < memory_mb:
|
40
|
+
raise InvalidError(f"Cannot specify a memory limit lower than request: {memory_mb_max} < {memory_mb}")
|
41
|
+
else:
|
42
|
+
memory_mb = 0
|
43
|
+
memory_mb_max = 0
|
44
|
+
return api_pb2.Resources(
|
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,
|
51
|
+
)
|
@@ -0,0 +1 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|