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/object.py
CHANGED
@@ -1,24 +1,27 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import uuid
|
3
|
+
from collections.abc import Awaitable, Hashable, Sequence
|
3
4
|
from functools import wraps
|
4
|
-
from typing import
|
5
|
+
from typing import Callable, ClassVar, Optional, TypeVar
|
5
6
|
|
6
7
|
from google.protobuf.message import Message
|
7
8
|
|
9
|
+
from modal._utils.async_utils import aclosing
|
10
|
+
|
8
11
|
from ._resolver import Resolver
|
9
12
|
from ._utils.async_utils import synchronize_api
|
10
13
|
from .client import _Client
|
11
|
-
from .config import config
|
14
|
+
from .config import config, logger
|
12
15
|
from .exception import ExecutionError, InvalidError
|
13
16
|
|
14
17
|
O = TypeVar("O", bound="_Object")
|
15
18
|
|
16
19
|
_BLOCKING_O = synchronize_api(O)
|
17
20
|
|
18
|
-
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP = 300
|
21
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
|
19
22
|
|
20
23
|
|
21
|
-
def _get_environment_name(environment_name: Optional[str], resolver: Optional[Resolver] = None) -> Optional[str]:
|
24
|
+
def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
|
22
25
|
if environment_name:
|
23
26
|
return environment_name
|
24
27
|
elif resolver and resolver.environment_name:
|
@@ -29,7 +32,7 @@ def _get_environment_name(environment_name: Optional[str], resolver: Optional[Re
|
|
29
32
|
|
30
33
|
class _Object:
|
31
34
|
_type_prefix: ClassVar[Optional[str]] = None
|
32
|
-
_prefix_to_type: ClassVar[
|
35
|
+
_prefix_to_type: ClassVar[dict[str, type]] = {}
|
33
36
|
|
34
37
|
# For constructors
|
35
38
|
_load: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]]
|
@@ -37,13 +40,14 @@ class _Object:
|
|
37
40
|
_rep: str
|
38
41
|
_is_another_app: bool
|
39
42
|
_hydrate_lazily: bool
|
40
|
-
_deps: Optional[Callable[...,
|
43
|
+
_deps: Optional[Callable[..., list["_Object"]]]
|
41
44
|
_deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
|
42
45
|
|
43
46
|
# For hydrated objects
|
44
47
|
_object_id: str
|
45
48
|
_client: _Client
|
46
49
|
_is_hydrated: bool
|
50
|
+
_is_rehydrated: bool
|
47
51
|
|
48
52
|
@classmethod
|
49
53
|
def __init_subclass__(cls, type_prefix: Optional[str] = None):
|
@@ -62,7 +66,7 @@ class _Object:
|
|
62
66
|
is_another_app: bool = False,
|
63
67
|
preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
|
64
68
|
hydrate_lazily: bool = False,
|
65
|
-
deps: Optional[Callable[...,
|
69
|
+
deps: Optional[Callable[..., list["_Object"]]] = None,
|
66
70
|
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
67
71
|
):
|
68
72
|
self._local_uuid = str(uuid.uuid4())
|
@@ -77,6 +81,7 @@ class _Object:
|
|
77
81
|
self._object_id = None
|
78
82
|
self._client = None
|
79
83
|
self._is_hydrated = False
|
84
|
+
self._is_rehydrated = False
|
80
85
|
|
81
86
|
self._initialize_from_empty()
|
82
87
|
|
@@ -91,7 +96,9 @@ class _Object:
|
|
91
96
|
|
92
97
|
def _initialize_from_other(self, other):
|
93
98
|
# default implementation, can be overriden in subclasses
|
94
|
-
|
99
|
+
self._object_id = other._object_id
|
100
|
+
self._is_hydrated = other._is_hydrated
|
101
|
+
self._client = other._client
|
95
102
|
|
96
103
|
def _hydrate(self, object_id: str, client: _Client, metadata: Optional[Message]):
|
97
104
|
assert isinstance(object_id, str)
|
@@ -116,17 +123,25 @@ class _Object:
|
|
116
123
|
# the object_id is already provided by other means
|
117
124
|
return
|
118
125
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
126
|
+
def _validate_is_hydrated(self: O):
|
127
|
+
if not self._is_hydrated:
|
128
|
+
object_type = self.__class__.__name__.strip("_")
|
129
|
+
if hasattr(self, "_app") and getattr(self._app, "_running_app", "") is None:
|
130
|
+
# The most common cause of this error: e.g., user called a Function without using App.run()
|
131
|
+
reason = ", because the App it is defined on is not running"
|
132
|
+
else:
|
133
|
+
# Technically possible, but with an ambiguous cause.
|
134
|
+
reason = ""
|
135
|
+
raise ExecutionError(
|
136
|
+
f"{object_type} has not been hydrated with the metadata it needs to run on Modal{reason}."
|
137
|
+
)
|
122
138
|
|
123
139
|
def clone(self: O) -> O:
|
124
140
|
"""mdmd:hidden Clone a given hydrated object."""
|
125
141
|
|
126
142
|
# Object to clone must already be hydrated, otherwise from_loader is more suitable.
|
127
|
-
|
128
|
-
|
129
|
-
obj = _Object.__new__(type(self))
|
143
|
+
self._validate_is_hydrated()
|
144
|
+
obj = type(self).__new__(type(self))
|
130
145
|
obj._initialize_from_other(self)
|
131
146
|
return obj
|
132
147
|
|
@@ -138,7 +153,7 @@ class _Object:
|
|
138
153
|
is_another_app: bool = False,
|
139
154
|
preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
|
140
155
|
hydrate_lazily: bool = False,
|
141
|
-
deps: Optional[Callable[...,
|
156
|
+
deps: Optional[Callable[..., Sequence["_Object"]]] = None,
|
142
157
|
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
143
158
|
):
|
144
159
|
# TODO(erikbern): flip the order of the two first arguments
|
@@ -146,26 +161,34 @@ class _Object:
|
|
146
161
|
obj._init(rep, load, is_another_app, preload, hydrate_lazily, deps, deduplication_key)
|
147
162
|
return obj
|
148
163
|
|
164
|
+
@classmethod
|
165
|
+
def _get_type_from_id(cls: type[O], object_id: str) -> type[O]:
|
166
|
+
parts = object_id.split("-")
|
167
|
+
if len(parts) != 2:
|
168
|
+
raise InvalidError(f"Object id {object_id} has no dash in it")
|
169
|
+
prefix = parts[0]
|
170
|
+
if prefix not in cls._prefix_to_type:
|
171
|
+
raise InvalidError(f"Object prefix {prefix} does not correspond to a type")
|
172
|
+
return cls._prefix_to_type[prefix]
|
173
|
+
|
174
|
+
@classmethod
|
175
|
+
def _is_id_type(cls: type[O], object_id) -> bool:
|
176
|
+
return cls._get_type_from_id(object_id) == cls
|
177
|
+
|
149
178
|
@classmethod
|
150
179
|
def _new_hydrated(
|
151
|
-
cls:
|
180
|
+
cls: type[O], object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
|
152
181
|
) -> O:
|
153
182
|
if cls._type_prefix is not None:
|
154
183
|
# This is called directly on a subclass, e.g. Secret.from_id
|
155
184
|
if not object_id.startswith(cls._type_prefix + "-"):
|
156
185
|
raise InvalidError(f"Object {object_id} does not start with {cls._type_prefix}")
|
157
|
-
|
186
|
+
obj_cls = cls
|
158
187
|
else:
|
159
188
|
# This is called on the base class, e.g. Handle.from_id
|
160
|
-
|
161
|
-
if len(parts) != 2:
|
162
|
-
raise InvalidError(f"Object id {object_id} has no dash in it")
|
163
|
-
prefix = parts[0]
|
164
|
-
if prefix not in cls._prefix_to_type:
|
165
|
-
raise InvalidError(f"Object prefix {prefix} does not correspond to a type")
|
189
|
+
obj_cls = cls._get_type_from_id(object_id)
|
166
190
|
|
167
191
|
# Instantiate provider
|
168
|
-
obj_cls = cls._prefix_to_type[prefix]
|
169
192
|
obj = _Object.__new__(obj_cls)
|
170
193
|
rep = f"Object({object_id})" # TODO(erikbern): dumb
|
171
194
|
obj._init(rep, is_another_app=is_another_app)
|
@@ -185,7 +208,7 @@ class _Object:
|
|
185
208
|
return self._local_uuid
|
186
209
|
|
187
210
|
@property
|
188
|
-
def object_id(self):
|
211
|
+
def object_id(self) -> str:
|
189
212
|
"""mdmd:hidden"""
|
190
213
|
return self._object_id
|
191
214
|
|
@@ -195,24 +218,30 @@ class _Object:
|
|
195
218
|
return self._is_hydrated
|
196
219
|
|
197
220
|
@property
|
198
|
-
def deps(self) -> Callable[...,
|
221
|
+
def deps(self) -> Callable[..., list["_Object"]]:
|
199
222
|
"""mdmd:hidden"""
|
200
223
|
return self._deps if self._deps is not None else lambda: []
|
201
224
|
|
202
|
-
async def resolve(self):
|
225
|
+
async def resolve(self, client: Optional[_Client] = None):
|
203
226
|
"""mdmd:hidden"""
|
204
227
|
if self._is_hydrated:
|
228
|
+
# memory snapshots capture references which must be rehydrated
|
229
|
+
# on restore to handle staleness.
|
230
|
+
if self._client._snapshotted and not self._is_rehydrated:
|
231
|
+
logger.debug(f"rehydrating {self} after snapshot")
|
232
|
+
self._is_hydrated = False # un-hydrate and re-resolve
|
233
|
+
c = client if client is not None else await _Client.from_env()
|
234
|
+
resolver = Resolver(c)
|
235
|
+
await resolver.load(self)
|
236
|
+
self._is_rehydrated = True
|
237
|
+
logger.debug(f"rehydrated {self} with client {id(c)}")
|
205
238
|
return
|
206
239
|
elif not self._hydrate_lazily:
|
207
|
-
|
208
|
-
"Object has not been hydrated and doesn't support lazy hydration."
|
209
|
-
" This might happen if an object is defined on a different stub,"
|
210
|
-
" or if it's on the same stub but it didn't get created because it"
|
211
|
-
" wasn't defined in global scope."
|
212
|
-
)
|
240
|
+
self._validate_is_hydrated()
|
213
241
|
else:
|
214
242
|
# TODO: this client and/or resolver can't be changed by a caller to X.from_name()
|
215
|
-
|
243
|
+
c = client if client is not None else await _Client.from_env()
|
244
|
+
resolver = Resolver(c)
|
216
245
|
await resolver.load(self)
|
217
246
|
|
218
247
|
|
@@ -232,7 +261,8 @@ def live_method_gen(method):
|
|
232
261
|
@wraps(method)
|
233
262
|
async def wrapped(self, *args, **kwargs):
|
234
263
|
await self.resolve()
|
235
|
-
async
|
236
|
-
|
264
|
+
async with aclosing(method(self, *args, **kwargs)) as stream:
|
265
|
+
async for item in stream:
|
266
|
+
yield item
|
237
267
|
|
238
268
|
return wrapped
|
modal/object.pyi
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import collections.abc
|
1
2
|
import google.protobuf.message
|
2
3
|
import modal._resolver
|
3
4
|
import modal.client
|
@@ -8,189 +9,211 @@ O = typing.TypeVar("O", bound="_Object")
|
|
8
9
|
|
9
10
|
_BLOCKING_O = typing.TypeVar("_BLOCKING_O", bound="Object")
|
10
11
|
|
11
|
-
def _get_environment_name(
|
12
|
-
|
13
|
-
|
12
|
+
def _get_environment_name(
|
13
|
+
environment_name: typing.Optional[str] = None, resolver: typing.Optional[modal._resolver.Resolver] = None
|
14
|
+
) -> typing.Optional[str]: ...
|
14
15
|
|
15
16
|
class _Object:
|
16
|
-
_type_prefix: typing.ClassVar[typing.
|
17
|
-
_prefix_to_type: typing.ClassVar[
|
18
|
-
_load: typing.
|
19
|
-
|
17
|
+
_type_prefix: typing.ClassVar[typing.Optional[str]]
|
18
|
+
_prefix_to_type: typing.ClassVar[dict[str, type]]
|
19
|
+
_load: typing.Optional[
|
20
|
+
typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
21
|
+
]
|
22
|
+
_preload: typing.Optional[
|
23
|
+
typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
24
|
+
]
|
20
25
|
_rep: str
|
21
26
|
_is_another_app: bool
|
22
27
|
_hydrate_lazily: bool
|
23
|
-
_deps: typing.
|
24
|
-
_deduplication_key: typing.
|
28
|
+
_deps: typing.Optional[typing.Callable[..., list[_Object]]]
|
29
|
+
_deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]]
|
25
30
|
_object_id: str
|
26
31
|
_client: modal.client._Client
|
27
32
|
_is_hydrated: bool
|
33
|
+
_is_rehydrated: bool
|
28
34
|
|
29
35
|
@classmethod
|
30
|
-
def __init_subclass__(cls, type_prefix: typing.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
def
|
58
|
-
...
|
59
|
-
|
60
|
-
def clone(self: O) -> O:
|
61
|
-
...
|
62
|
-
|
36
|
+
def __init_subclass__(cls, type_prefix: typing.Optional[str] = None): ...
|
37
|
+
def __init__(self, *args, **kwargs): ...
|
38
|
+
def _init(
|
39
|
+
self,
|
40
|
+
rep: str,
|
41
|
+
load: typing.Optional[
|
42
|
+
typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
43
|
+
] = None,
|
44
|
+
is_another_app: bool = False,
|
45
|
+
preload: typing.Optional[
|
46
|
+
typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
47
|
+
] = None,
|
48
|
+
hydrate_lazily: bool = False,
|
49
|
+
deps: typing.Optional[typing.Callable[..., list[_Object]]] = None,
|
50
|
+
deduplication_key: typing.Optional[
|
51
|
+
typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
|
52
|
+
] = None,
|
53
|
+
): ...
|
54
|
+
def _unhydrate(self): ...
|
55
|
+
def _initialize_from_empty(self): ...
|
56
|
+
def _initialize_from_other(self, other): ...
|
57
|
+
def _hydrate(
|
58
|
+
self, object_id: str, client: modal.client._Client, metadata: typing.Optional[google.protobuf.message.Message]
|
59
|
+
): ...
|
60
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
61
|
+
def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
|
62
|
+
def _validate_is_hydrated(self: O): ...
|
63
|
+
def clone(self: O) -> O: ...
|
63
64
|
@classmethod
|
64
|
-
def _from_loader(
|
65
|
-
|
66
|
-
|
65
|
+
def _from_loader(
|
66
|
+
cls,
|
67
|
+
load: typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]],
|
68
|
+
rep: str,
|
69
|
+
is_another_app: bool = False,
|
70
|
+
preload: typing.Optional[
|
71
|
+
typing.Callable[[O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
72
|
+
] = None,
|
73
|
+
hydrate_lazily: bool = False,
|
74
|
+
deps: typing.Optional[typing.Callable[..., collections.abc.Sequence[_Object]]] = None,
|
75
|
+
deduplication_key: typing.Optional[
|
76
|
+
typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
|
77
|
+
] = None,
|
78
|
+
): ...
|
67
79
|
@classmethod
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
80
|
+
def _get_type_from_id(cls: type[O], object_id: str) -> type[O]: ...
|
81
|
+
@classmethod
|
82
|
+
def _is_id_type(cls: type[O], object_id) -> bool: ...
|
83
|
+
@classmethod
|
84
|
+
def _new_hydrated(
|
85
|
+
cls: type[O],
|
86
|
+
object_id: str,
|
87
|
+
client: modal.client._Client,
|
88
|
+
handle_metadata: typing.Optional[google.protobuf.message.Message],
|
89
|
+
is_another_app: bool = False,
|
90
|
+
) -> O: ...
|
91
|
+
def _hydrate_from_other(self, other: O): ...
|
92
|
+
def __repr__(self): ...
|
77
93
|
@property
|
78
|
-
def local_uuid(self):
|
79
|
-
...
|
80
|
-
|
94
|
+
def local_uuid(self): ...
|
81
95
|
@property
|
82
|
-
def object_id(self):
|
83
|
-
...
|
84
|
-
|
96
|
+
def object_id(self) -> str: ...
|
85
97
|
@property
|
86
|
-
def is_hydrated(self) -> bool:
|
87
|
-
...
|
88
|
-
|
98
|
+
def is_hydrated(self) -> bool: ...
|
89
99
|
@property
|
90
|
-
def deps(self) -> typing.Callable[...,
|
91
|
-
|
92
|
-
|
93
|
-
async def resolve(self):
|
94
|
-
...
|
95
|
-
|
100
|
+
def deps(self) -> typing.Callable[..., list[_Object]]: ...
|
101
|
+
async def resolve(self, client: typing.Optional[modal.client._Client] = None): ...
|
96
102
|
|
97
103
|
class Object:
|
98
|
-
_type_prefix: typing.ClassVar[typing.
|
99
|
-
_prefix_to_type: typing.ClassVar[
|
100
|
-
_load: typing.
|
101
|
-
|
104
|
+
_type_prefix: typing.ClassVar[typing.Optional[str]]
|
105
|
+
_prefix_to_type: typing.ClassVar[dict[str, type]]
|
106
|
+
_load: typing.Optional[
|
107
|
+
typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
108
|
+
]
|
109
|
+
_preload: typing.Optional[
|
110
|
+
typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]]
|
111
|
+
]
|
102
112
|
_rep: str
|
103
113
|
_is_another_app: bool
|
104
114
|
_hydrate_lazily: bool
|
105
|
-
_deps: typing.
|
106
|
-
_deduplication_key: typing.
|
115
|
+
_deps: typing.Optional[typing.Callable[..., list[Object]]]
|
116
|
+
_deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]]
|
107
117
|
_object_id: str
|
108
118
|
_client: modal.client.Client
|
109
119
|
_is_hydrated: bool
|
120
|
+
_is_rehydrated: bool
|
110
121
|
|
111
|
-
def __init__(self, *args, **kwargs):
|
112
|
-
...
|
113
|
-
|
122
|
+
def __init__(self, *args, **kwargs): ...
|
114
123
|
@classmethod
|
115
|
-
def __init_subclass__(cls, type_prefix: typing.
|
116
|
-
...
|
124
|
+
def __init_subclass__(cls, type_prefix: typing.Optional[str] = None): ...
|
117
125
|
|
118
126
|
class ___init_spec(typing_extensions.Protocol):
|
119
|
-
def __call__(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
127
|
+
def __call__(
|
128
|
+
self,
|
129
|
+
rep: str,
|
130
|
+
load: typing.Optional[
|
131
|
+
typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None]
|
132
|
+
] = None,
|
133
|
+
is_another_app: bool = False,
|
134
|
+
preload: typing.Optional[
|
135
|
+
typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None]
|
136
|
+
] = None,
|
137
|
+
hydrate_lazily: bool = False,
|
138
|
+
deps: typing.Optional[typing.Callable[..., list[Object]]] = None,
|
139
|
+
deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Hashable]] = None,
|
140
|
+
): ...
|
141
|
+
def aio(
|
142
|
+
self,
|
143
|
+
rep: str,
|
144
|
+
load: typing.Optional[
|
145
|
+
typing.Callable[
|
146
|
+
[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
|
147
|
+
]
|
148
|
+
] = None,
|
149
|
+
is_another_app: bool = False,
|
150
|
+
preload: typing.Optional[
|
151
|
+
typing.Callable[
|
152
|
+
[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], collections.abc.Awaitable[None]
|
153
|
+
]
|
154
|
+
] = None,
|
155
|
+
hydrate_lazily: bool = False,
|
156
|
+
deps: typing.Optional[typing.Callable[..., list[Object]]] = None,
|
157
|
+
deduplication_key: typing.Optional[
|
158
|
+
typing.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
|
159
|
+
] = None,
|
160
|
+
): ...
|
124
161
|
|
125
162
|
_init: ___init_spec
|
126
163
|
|
127
|
-
def _unhydrate(self):
|
128
|
-
|
129
|
-
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
def
|
134
|
-
|
135
|
-
|
136
|
-
def
|
137
|
-
...
|
138
|
-
|
139
|
-
def _hydrate_metadata(self, metadata: typing.Union[google.protobuf.message.Message, None]):
|
140
|
-
...
|
141
|
-
|
142
|
-
def _get_metadata(self) -> typing.Union[google.protobuf.message.Message, None]:
|
143
|
-
...
|
144
|
-
|
145
|
-
def _init_from_other(self, other: _BLOCKING_O):
|
146
|
-
...
|
147
|
-
|
148
|
-
def clone(self: _BLOCKING_O) -> _BLOCKING_O:
|
149
|
-
...
|
150
|
-
|
164
|
+
def _unhydrate(self): ...
|
165
|
+
def _initialize_from_empty(self): ...
|
166
|
+
def _initialize_from_other(self, other): ...
|
167
|
+
def _hydrate(
|
168
|
+
self, object_id: str, client: modal.client.Client, metadata: typing.Optional[google.protobuf.message.Message]
|
169
|
+
): ...
|
170
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
171
|
+
def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
|
172
|
+
def _validate_is_hydrated(self: _BLOCKING_O): ...
|
173
|
+
def clone(self: _BLOCKING_O) -> _BLOCKING_O: ...
|
151
174
|
@classmethod
|
152
|
-
def _from_loader(
|
153
|
-
|
154
|
-
|
175
|
+
def _from_loader(
|
176
|
+
cls,
|
177
|
+
load: typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None],
|
178
|
+
rep: str,
|
179
|
+
is_another_app: bool = False,
|
180
|
+
preload: typing.Optional[
|
181
|
+
typing.Callable[[_BLOCKING_O, modal._resolver.Resolver, typing.Optional[str]], None]
|
182
|
+
] = None,
|
183
|
+
hydrate_lazily: bool = False,
|
184
|
+
deps: typing.Optional[typing.Callable[..., collections.abc.Sequence[Object]]] = None,
|
185
|
+
deduplication_key: typing.Optional[typing.Callable[[], collections.abc.Hashable]] = None,
|
186
|
+
): ...
|
155
187
|
@classmethod
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
188
|
+
def _get_type_from_id(cls: type[_BLOCKING_O], object_id: str) -> type[_BLOCKING_O]: ...
|
189
|
+
@classmethod
|
190
|
+
def _is_id_type(cls: type[_BLOCKING_O], object_id) -> bool: ...
|
191
|
+
@classmethod
|
192
|
+
def _new_hydrated(
|
193
|
+
cls: type[_BLOCKING_O],
|
194
|
+
object_id: str,
|
195
|
+
client: modal.client.Client,
|
196
|
+
handle_metadata: typing.Optional[google.protobuf.message.Message],
|
197
|
+
is_another_app: bool = False,
|
198
|
+
) -> _BLOCKING_O: ...
|
199
|
+
def _hydrate_from_other(self, other: _BLOCKING_O): ...
|
200
|
+
def __repr__(self): ...
|
165
201
|
@property
|
166
|
-
def local_uuid(self):
|
167
|
-
...
|
168
|
-
|
202
|
+
def local_uuid(self): ...
|
169
203
|
@property
|
170
|
-
def object_id(self):
|
171
|
-
...
|
172
|
-
|
204
|
+
def object_id(self) -> str: ...
|
173
205
|
@property
|
174
|
-
def is_hydrated(self) -> bool:
|
175
|
-
...
|
176
|
-
|
206
|
+
def is_hydrated(self) -> bool: ...
|
177
207
|
@property
|
178
|
-
def deps(self) -> typing.Callable[...,
|
179
|
-
...
|
208
|
+
def deps(self) -> typing.Callable[..., list[Object]]: ...
|
180
209
|
|
181
210
|
class __resolve_spec(typing_extensions.Protocol):
|
182
|
-
def __call__(self):
|
183
|
-
|
184
|
-
|
185
|
-
async def aio(self, *args, **kwargs):
|
186
|
-
...
|
211
|
+
def __call__(self, client: typing.Optional[modal.client.Client] = None): ...
|
212
|
+
async def aio(self, client: typing.Optional[modal.client.Client] = None): ...
|
187
213
|
|
188
214
|
resolve: __resolve_spec
|
189
215
|
|
216
|
+
def live_method(method): ...
|
217
|
+
def live_method_gen(method): ...
|
190
218
|
|
191
|
-
|
192
|
-
...
|
193
|
-
|
194
|
-
|
195
|
-
def live_method_gen(method):
|
196
|
-
...
|
219
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int
|
modal/output.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
"""Interface to Modal's OutputManager functionality.
|
3
|
+
|
4
|
+
These functions live here so that Modal library code can import them without
|
5
|
+
transitively importing Rich, as we do in global scope in _output.py. This allows
|
6
|
+
us to avoid importing Rich for client code that runs in the container environment.
|
7
|
+
|
8
|
+
"""
|
9
|
+
import contextlib
|
10
|
+
from collections.abc import Generator
|
11
|
+
from typing import TYPE_CHECKING, Optional
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from ._output import OutputManager
|
15
|
+
|
16
|
+
|
17
|
+
OUTPUT_ENABLED = False
|
18
|
+
|
19
|
+
|
20
|
+
@contextlib.contextmanager
|
21
|
+
def enable_output(show_progress: bool = True) -> Generator[None, None, None]:
|
22
|
+
"""Context manager that enable output when using the Python SDK.
|
23
|
+
|
24
|
+
This will print to stdout and stderr things such as
|
25
|
+
1. Logs from running functions
|
26
|
+
2. Status of creating objects
|
27
|
+
3. Map progress
|
28
|
+
|
29
|
+
Example:
|
30
|
+
```python
|
31
|
+
app = modal.App()
|
32
|
+
with modal.enable_output():
|
33
|
+
with app.run():
|
34
|
+
...
|
35
|
+
```
|
36
|
+
"""
|
37
|
+
from ._output import OutputManager
|
38
|
+
|
39
|
+
# Toggle the output flag from within this function so that we can
|
40
|
+
# call _get_output_manager from within the library and only import
|
41
|
+
# the _output module if output is explicitly enabled. That prevents
|
42
|
+
# us from trying to import rich inside a container environment where
|
43
|
+
# it might not be installed. This is sort of hacky and I would prefer
|
44
|
+
# a more thorough refactor where the OutputManager is fully useable
|
45
|
+
# without rich installed, but that's a larger project.
|
46
|
+
global OUTPUT_ENABLED
|
47
|
+
|
48
|
+
try:
|
49
|
+
with OutputManager.enable_output(show_progress):
|
50
|
+
OUTPUT_ENABLED = True
|
51
|
+
yield
|
52
|
+
finally:
|
53
|
+
OUTPUT_ENABLED = False
|
54
|
+
|
55
|
+
|
56
|
+
def _get_output_manager() -> Optional["OutputManager"]:
|
57
|
+
"""Interface to the OutputManager that returns None when output is not enabled."""
|
58
|
+
if OUTPUT_ENABLED:
|
59
|
+
from ._output import OutputManager
|
60
|
+
|
61
|
+
return OutputManager.get()
|
62
|
+
else:
|
63
|
+
return None
|