modal 0.72.4__py3-none-any.whl → 0.72.48__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 +5 -10
- modal/_object.py +297 -0
- modal/_resolver.py +7 -5
- modal/_runtime/container_io_manager.py +0 -11
- modal/_runtime/user_code_imports.py +7 -7
- modal/_serialization.py +4 -3
- modal/_tunnel.py +1 -1
- modal/app.py +14 -61
- modal/app.pyi +25 -25
- modal/cli/app.py +3 -2
- modal/cli/container.py +1 -1
- modal/cli/import_refs.py +185 -113
- modal/cli/launch.py +10 -5
- modal/cli/programs/run_jupyter.py +2 -2
- modal/cli/programs/vscode.py +3 -3
- modal/cli/run.py +134 -68
- modal/client.py +1 -0
- modal/client.pyi +18 -14
- modal/cloud_bucket_mount.py +4 -0
- modal/cloud_bucket_mount.pyi +4 -0
- modal/cls.py +33 -5
- modal/cls.pyi +20 -5
- modal/container_process.pyi +8 -6
- modal/dict.py +1 -1
- modal/dict.pyi +32 -29
- modal/environments.py +1 -1
- modal/environments.pyi +2 -1
- modal/experimental.py +47 -11
- modal/experimental.pyi +29 -0
- modal/file_io.pyi +30 -28
- modal/file_pattern_matcher.py +32 -25
- modal/functions.py +31 -23
- modal/functions.pyi +57 -50
- modal/gpu.py +19 -26
- modal/image.py +47 -19
- modal/image.pyi +28 -21
- modal/io_streams.pyi +14 -12
- modal/mount.py +14 -5
- modal/mount.pyi +28 -25
- modal/network_file_system.py +7 -7
- modal/network_file_system.pyi +27 -24
- modal/object.py +2 -265
- modal/object.pyi +46 -130
- modal/parallel_map.py +2 -2
- modal/parallel_map.pyi +10 -7
- modal/partial_function.py +22 -3
- modal/partial_function.pyi +45 -27
- modal/proxy.py +1 -1
- modal/proxy.pyi +2 -1
- modal/queue.py +1 -1
- modal/queue.pyi +26 -23
- modal/runner.py +14 -3
- modal/sandbox.py +11 -7
- modal/sandbox.pyi +30 -27
- modal/secret.py +1 -1
- modal/secret.pyi +2 -1
- modal/token_flow.pyi +6 -4
- modal/volume.py +1 -1
- modal/volume.pyi +36 -33
- {modal-0.72.4.dist-info → modal-0.72.48.dist-info}/METADATA +2 -2
- {modal-0.72.4.dist-info → modal-0.72.48.dist-info}/RECORD +73 -71
- modal_proto/api.proto +151 -4
- modal_proto/api_grpc.py +113 -0
- modal_proto/api_pb2.py +998 -795
- modal_proto/api_pb2.pyi +430 -11
- modal_proto/api_pb2_grpc.py +233 -1
- modal_proto/api_pb2_grpc.pyi +75 -3
- modal_proto/modal_api_grpc.py +7 -0
- modal_version/_version_generated.py +1 -1
- {modal-0.72.4.dist-info → modal-0.72.48.dist-info}/LICENSE +0 -0
- {modal-0.72.4.dist-info → modal-0.72.48.dist-info}/WHEEL +0 -0
- {modal-0.72.4.dist-info → modal-0.72.48.dist-info}/entry_points.txt +0 -0
- {modal-0.72.4.dist-info → modal-0.72.48.dist-info}/top_level.txt +0 -0
modal/mount.pyi
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import collections.abc
|
2
2
|
import google.protobuf.message
|
3
|
+
import modal._object
|
3
4
|
import modal._resolver
|
4
5
|
import modal._utils.blob_utils
|
5
6
|
import modal.client
|
@@ -35,7 +36,7 @@ class _MountFile(_MountEntry):
|
|
35
36
|
class _MountDir(_MountEntry):
|
36
37
|
local_dir: pathlib.Path
|
37
38
|
remote_path: pathlib.PurePosixPath
|
38
|
-
ignore:
|
39
|
+
ignore: collections.abc.Callable[[pathlib.Path], bool]
|
39
40
|
recursive: bool
|
40
41
|
|
41
42
|
def description(self): ...
|
@@ -46,7 +47,7 @@ class _MountDir(_MountEntry):
|
|
46
47
|
self,
|
47
48
|
local_dir: pathlib.Path,
|
48
49
|
remote_path: pathlib.PurePosixPath,
|
49
|
-
ignore:
|
50
|
+
ignore: collections.abc.Callable[[pathlib.Path], bool],
|
50
51
|
recursive: bool,
|
51
52
|
) -> None: ...
|
52
53
|
def __repr__(self): ...
|
@@ -58,7 +59,7 @@ def module_mount_ignore_condition(module_base: pathlib.Path): ...
|
|
58
59
|
class _MountedPythonModule(_MountEntry):
|
59
60
|
module_name: str
|
60
61
|
remote_dir: typing.Union[pathlib.PurePosixPath, str]
|
61
|
-
ignore: typing.Optional[
|
62
|
+
ignore: typing.Optional[collections.abc.Callable[[pathlib.Path], bool]]
|
62
63
|
|
63
64
|
def description(self) -> str: ...
|
64
65
|
def _proxy_entries(self) -> list[_MountEntry]: ...
|
@@ -69,14 +70,14 @@ class _MountedPythonModule(_MountEntry):
|
|
69
70
|
self,
|
70
71
|
module_name: str,
|
71
72
|
remote_dir: typing.Union[pathlib.PurePosixPath, str] = "/root",
|
72
|
-
ignore: typing.Optional[
|
73
|
+
ignore: typing.Optional[collections.abc.Callable[[pathlib.Path], bool]] = None,
|
73
74
|
) -> None: ...
|
74
75
|
def __repr__(self): ...
|
75
76
|
def __eq__(self, other): ...
|
76
77
|
|
77
78
|
class NonLocalMountError(Exception): ...
|
78
79
|
|
79
|
-
class _Mount(modal.
|
80
|
+
class _Mount(modal._object._Object):
|
80
81
|
_entries: typing.Optional[list[_MountEntry]]
|
81
82
|
_deployment_name: typing.Optional[str]
|
82
83
|
_namespace: typing.Optional[int]
|
@@ -95,14 +96,14 @@ class _Mount(modal.object._Object):
|
|
95
96
|
def _add_local_dir(
|
96
97
|
local_path: pathlib.Path,
|
97
98
|
remote_path: pathlib.PurePosixPath,
|
98
|
-
ignore:
|
99
|
+
ignore: collections.abc.Callable[[pathlib.Path], bool] = modal.file_pattern_matcher._NOTHING,
|
99
100
|
): ...
|
100
101
|
def add_local_dir(
|
101
102
|
self,
|
102
103
|
local_path: typing.Union[str, pathlib.Path],
|
103
104
|
*,
|
104
105
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
105
|
-
condition: typing.Optional[
|
106
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
106
107
|
recursive: bool = True,
|
107
108
|
) -> _Mount: ...
|
108
109
|
@staticmethod
|
@@ -110,7 +111,7 @@ class _Mount(modal.object._Object):
|
|
110
111
|
local_path: typing.Union[str, pathlib.Path],
|
111
112
|
*,
|
112
113
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
113
|
-
condition: typing.Optional[
|
114
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
114
115
|
recursive: bool = True,
|
115
116
|
) -> _Mount: ...
|
116
117
|
@staticmethod
|
@@ -118,7 +119,7 @@ class _Mount(modal.object._Object):
|
|
118
119
|
local_path: typing.Union[str, pathlib.Path],
|
119
120
|
*,
|
120
121
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
121
|
-
condition: typing.Optional[
|
122
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
122
123
|
recursive: bool = True,
|
123
124
|
) -> _Mount: ...
|
124
125
|
def add_local_file(
|
@@ -147,15 +148,15 @@ class _Mount(modal.object._Object):
|
|
147
148
|
def from_local_python_packages(
|
148
149
|
*module_names: str,
|
149
150
|
remote_dir: typing.Union[str, pathlib.PurePosixPath] = "/root",
|
150
|
-
condition: typing.Optional[
|
151
|
-
ignore: typing.Union[typing.Sequence[str],
|
151
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
152
|
+
ignore: typing.Union[typing.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], None] = None,
|
152
153
|
) -> _Mount: ...
|
153
154
|
@staticmethod
|
154
155
|
def _from_local_python_packages(
|
155
156
|
*module_names: str,
|
156
157
|
remote_dir: typing.Union[str, pathlib.PurePosixPath] = "/root",
|
157
|
-
condition: typing.Optional[
|
158
|
-
ignore: typing.Union[typing.Sequence[str],
|
158
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
159
|
+
ignore: typing.Union[typing.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], None] = None,
|
159
160
|
) -> _Mount: ...
|
160
161
|
@staticmethod
|
161
162
|
def from_name(name: str, namespace=1, environment_name: typing.Optional[str] = None) -> _Mount: ...
|
@@ -176,6 +177,8 @@ class _Mount(modal.object._Object):
|
|
176
177
|
) -> None: ...
|
177
178
|
def _get_metadata(self) -> modal_proto.api_pb2.MountHandleMetadata: ...
|
178
179
|
|
180
|
+
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
181
|
+
|
179
182
|
class Mount(modal.object.Object):
|
180
183
|
_entries: typing.Optional[list[_MountEntry]]
|
181
184
|
_deployment_name: typing.Optional[str]
|
@@ -196,14 +199,14 @@ class Mount(modal.object.Object):
|
|
196
199
|
def _add_local_dir(
|
197
200
|
local_path: pathlib.Path,
|
198
201
|
remote_path: pathlib.PurePosixPath,
|
199
|
-
ignore:
|
202
|
+
ignore: collections.abc.Callable[[pathlib.Path], bool] = modal.file_pattern_matcher._NOTHING,
|
200
203
|
): ...
|
201
204
|
def add_local_dir(
|
202
205
|
self,
|
203
206
|
local_path: typing.Union[str, pathlib.Path],
|
204
207
|
*,
|
205
208
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
206
|
-
condition: typing.Optional[
|
209
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
207
210
|
recursive: bool = True,
|
208
211
|
) -> Mount: ...
|
209
212
|
@staticmethod
|
@@ -211,7 +214,7 @@ class Mount(modal.object.Object):
|
|
211
214
|
local_path: typing.Union[str, pathlib.Path],
|
212
215
|
*,
|
213
216
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
214
|
-
condition: typing.Optional[
|
217
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
215
218
|
recursive: bool = True,
|
216
219
|
) -> Mount: ...
|
217
220
|
@staticmethod
|
@@ -219,7 +222,7 @@ class Mount(modal.object.Object):
|
|
219
222
|
local_path: typing.Union[str, pathlib.Path],
|
220
223
|
*,
|
221
224
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
222
|
-
condition: typing.Optional[
|
225
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
223
226
|
recursive: bool = True,
|
224
227
|
) -> Mount: ...
|
225
228
|
def add_local_file(
|
@@ -248,25 +251,25 @@ class Mount(modal.object.Object):
|
|
248
251
|
|
249
252
|
_get_files: ___get_files_spec
|
250
253
|
|
251
|
-
class ___load_mount_spec(typing_extensions.Protocol):
|
254
|
+
class ___load_mount_spec(typing_extensions.Protocol[SUPERSELF]):
|
252
255
|
def __call__(self, resolver: modal._resolver.Resolver, existing_object_id: typing.Optional[str]): ...
|
253
256
|
async def aio(self, resolver: modal._resolver.Resolver, existing_object_id: typing.Optional[str]): ...
|
254
257
|
|
255
|
-
_load_mount: ___load_mount_spec
|
258
|
+
_load_mount: ___load_mount_spec[typing_extensions.Self]
|
256
259
|
|
257
260
|
@staticmethod
|
258
261
|
def from_local_python_packages(
|
259
262
|
*module_names: str,
|
260
263
|
remote_dir: typing.Union[str, pathlib.PurePosixPath] = "/root",
|
261
|
-
condition: typing.Optional[
|
262
|
-
ignore: typing.Union[typing.Sequence[str],
|
264
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
265
|
+
ignore: typing.Union[typing.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], None] = None,
|
263
266
|
) -> Mount: ...
|
264
267
|
@staticmethod
|
265
268
|
def _from_local_python_packages(
|
266
269
|
*module_names: str,
|
267
270
|
remote_dir: typing.Union[str, pathlib.PurePosixPath] = "/root",
|
268
|
-
condition: typing.Optional[
|
269
|
-
ignore: typing.Union[typing.Sequence[str],
|
271
|
+
condition: typing.Optional[collections.abc.Callable[[str], bool]] = None,
|
272
|
+
ignore: typing.Union[typing.Sequence[str], collections.abc.Callable[[pathlib.Path], bool], None] = None,
|
270
273
|
) -> Mount: ...
|
271
274
|
@staticmethod
|
272
275
|
def from_name(name: str, namespace=1, environment_name: typing.Optional[str] = None) -> Mount: ...
|
@@ -279,7 +282,7 @@ class Mount(modal.object.Object):
|
|
279
282
|
environment_name: typing.Optional[str] = None,
|
280
283
|
) -> Mount: ...
|
281
284
|
|
282
|
-
class ___deploy_spec(typing_extensions.Protocol):
|
285
|
+
class ___deploy_spec(typing_extensions.Protocol[SUPERSELF]):
|
283
286
|
def __call__(
|
284
287
|
self,
|
285
288
|
deployment_name: typing.Optional[str] = None,
|
@@ -295,7 +298,7 @@ class Mount(modal.object.Object):
|
|
295
298
|
client: typing.Optional[modal.client.Client] = None,
|
296
299
|
) -> None: ...
|
297
300
|
|
298
|
-
_deploy: ___deploy_spec
|
301
|
+
_deploy: ___deploy_spec[typing_extensions.Self]
|
299
302
|
|
300
303
|
def _get_metadata(self) -> modal_proto.api_pb2.MountHandleMetadata: ...
|
301
304
|
|
modal/network_file_system.py
CHANGED
@@ -12,6 +12,13 @@ from synchronicity.async_wrap import asynccontextmanager
|
|
12
12
|
import modal
|
13
13
|
from modal_proto import api_pb2
|
14
14
|
|
15
|
+
from ._object import (
|
16
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
17
|
+
_get_environment_name,
|
18
|
+
_Object,
|
19
|
+
live_method,
|
20
|
+
live_method_gen,
|
21
|
+
)
|
15
22
|
from ._resolver import Resolver
|
16
23
|
from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
|
17
24
|
from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
|
@@ -21,13 +28,6 @@ from ._utils.hash_utils import get_sha256_hex
|
|
21
28
|
from ._utils.name_utils import check_object_name
|
22
29
|
from .client import _Client
|
23
30
|
from .exception import InvalidError
|
24
|
-
from .object import (
|
25
|
-
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
26
|
-
_get_environment_name,
|
27
|
-
_Object,
|
28
|
-
live_method,
|
29
|
-
live_method_gen,
|
30
|
-
)
|
31
31
|
from .volume import FileEntry
|
32
32
|
|
33
33
|
NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
|
modal/network_file_system.pyi
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import collections.abc
|
2
|
+
import modal._object
|
2
3
|
import modal.client
|
3
4
|
import modal.object
|
4
5
|
import modal.volume
|
@@ -12,7 +13,7 @@ def network_file_system_mount_protos(
|
|
12
13
|
validated_network_file_systems: list[tuple[str, _NetworkFileSystem]], allow_cross_region_volumes: bool
|
13
14
|
) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
|
14
15
|
|
15
|
-
class _NetworkFileSystem(modal.
|
16
|
+
class _NetworkFileSystem(modal._object._Object):
|
16
17
|
@staticmethod
|
17
18
|
def from_name(
|
18
19
|
name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
@@ -47,7 +48,7 @@ class _NetworkFileSystem(modal.object._Object):
|
|
47
48
|
self,
|
48
49
|
remote_path: str,
|
49
50
|
fp: typing.BinaryIO,
|
50
|
-
progress_cb: typing.Optional[
|
51
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
51
52
|
) -> int: ...
|
52
53
|
def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
53
54
|
def iterdir(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
|
@@ -55,17 +56,19 @@ class _NetworkFileSystem(modal.object._Object):
|
|
55
56
|
self,
|
56
57
|
local_path: typing.Union[pathlib.Path, str],
|
57
58
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
58
|
-
progress_cb: typing.Optional[
|
59
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
59
60
|
): ...
|
60
61
|
async def add_local_dir(
|
61
62
|
self,
|
62
63
|
local_path: typing.Union[pathlib.Path, str],
|
63
64
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
64
|
-
progress_cb: typing.Optional[
|
65
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
65
66
|
): ...
|
66
67
|
async def listdir(self, path: str) -> list[modal.volume.FileEntry]: ...
|
67
68
|
async def remove_file(self, path: str, recursive=False): ...
|
68
69
|
|
70
|
+
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
71
|
+
|
69
72
|
class NetworkFileSystem(modal.object.Object):
|
70
73
|
def __init__(self, *args, **kwargs): ...
|
71
74
|
@staticmethod
|
@@ -134,74 +137,74 @@ class NetworkFileSystem(modal.object.Object):
|
|
134
137
|
|
135
138
|
delete: __delete_spec
|
136
139
|
|
137
|
-
class __write_file_spec(typing_extensions.Protocol):
|
140
|
+
class __write_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
138
141
|
def __call__(
|
139
142
|
self,
|
140
143
|
remote_path: str,
|
141
144
|
fp: typing.BinaryIO,
|
142
|
-
progress_cb: typing.Optional[
|
145
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
143
146
|
) -> int: ...
|
144
147
|
async def aio(
|
145
148
|
self,
|
146
149
|
remote_path: str,
|
147
150
|
fp: typing.BinaryIO,
|
148
|
-
progress_cb: typing.Optional[
|
151
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
149
152
|
) -> int: ...
|
150
153
|
|
151
|
-
write_file: __write_file_spec
|
154
|
+
write_file: __write_file_spec[typing_extensions.Self]
|
152
155
|
|
153
|
-
class __read_file_spec(typing_extensions.Protocol):
|
156
|
+
class __read_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
154
157
|
def __call__(self, path: str) -> typing.Iterator[bytes]: ...
|
155
158
|
def aio(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
156
159
|
|
157
|
-
read_file: __read_file_spec
|
160
|
+
read_file: __read_file_spec[typing_extensions.Self]
|
158
161
|
|
159
|
-
class __iterdir_spec(typing_extensions.Protocol):
|
162
|
+
class __iterdir_spec(typing_extensions.Protocol[SUPERSELF]):
|
160
163
|
def __call__(self, path: str) -> typing.Iterator[modal.volume.FileEntry]: ...
|
161
164
|
def aio(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
|
162
165
|
|
163
|
-
iterdir: __iterdir_spec
|
166
|
+
iterdir: __iterdir_spec[typing_extensions.Self]
|
164
167
|
|
165
|
-
class __add_local_file_spec(typing_extensions.Protocol):
|
168
|
+
class __add_local_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
166
169
|
def __call__(
|
167
170
|
self,
|
168
171
|
local_path: typing.Union[pathlib.Path, str],
|
169
172
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
170
|
-
progress_cb: typing.Optional[
|
173
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
171
174
|
): ...
|
172
175
|
async def aio(
|
173
176
|
self,
|
174
177
|
local_path: typing.Union[pathlib.Path, str],
|
175
178
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
176
|
-
progress_cb: typing.Optional[
|
179
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
177
180
|
): ...
|
178
181
|
|
179
|
-
add_local_file: __add_local_file_spec
|
182
|
+
add_local_file: __add_local_file_spec[typing_extensions.Self]
|
180
183
|
|
181
|
-
class __add_local_dir_spec(typing_extensions.Protocol):
|
184
|
+
class __add_local_dir_spec(typing_extensions.Protocol[SUPERSELF]):
|
182
185
|
def __call__(
|
183
186
|
self,
|
184
187
|
local_path: typing.Union[pathlib.Path, str],
|
185
188
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
186
|
-
progress_cb: typing.Optional[
|
189
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
187
190
|
): ...
|
188
191
|
async def aio(
|
189
192
|
self,
|
190
193
|
local_path: typing.Union[pathlib.Path, str],
|
191
194
|
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
192
|
-
progress_cb: typing.Optional[
|
195
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
193
196
|
): ...
|
194
197
|
|
195
|
-
add_local_dir: __add_local_dir_spec
|
198
|
+
add_local_dir: __add_local_dir_spec[typing_extensions.Self]
|
196
199
|
|
197
|
-
class __listdir_spec(typing_extensions.Protocol):
|
200
|
+
class __listdir_spec(typing_extensions.Protocol[SUPERSELF]):
|
198
201
|
def __call__(self, path: str) -> list[modal.volume.FileEntry]: ...
|
199
202
|
async def aio(self, path: str) -> list[modal.volume.FileEntry]: ...
|
200
203
|
|
201
|
-
listdir: __listdir_spec
|
204
|
+
listdir: __listdir_spec[typing_extensions.Self]
|
202
205
|
|
203
|
-
class __remove_file_spec(typing_extensions.Protocol):
|
206
|
+
class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
204
207
|
def __call__(self, path: str, recursive=False): ...
|
205
208
|
async def aio(self, path: str, recursive=False): ...
|
206
209
|
|
207
|
-
remove_file: __remove_file_spec
|
210
|
+
remove_file: __remove_file_spec[typing_extensions.Self]
|
modal/object.py
CHANGED
@@ -1,268 +1,5 @@
|
|
1
|
-
# Copyright Modal Labs
|
2
|
-
import
|
3
|
-
from collections.abc import Awaitable, Hashable, Sequence
|
4
|
-
from functools import wraps
|
5
|
-
from typing import Callable, ClassVar, Optional, TypeVar
|
6
|
-
|
7
|
-
from google.protobuf.message import Message
|
8
|
-
|
9
|
-
from modal._utils.async_utils import aclosing
|
10
|
-
|
11
|
-
from ._resolver import Resolver
|
1
|
+
# Copyright Modal Labs 2025
|
2
|
+
from ._object import _Object
|
12
3
|
from ._utils.async_utils import synchronize_api
|
13
|
-
from .client import _Client
|
14
|
-
from .config import config, logger
|
15
|
-
from .exception import ExecutionError, InvalidError
|
16
|
-
|
17
|
-
O = TypeVar("O", bound="_Object")
|
18
|
-
|
19
|
-
_BLOCKING_O = synchronize_api(O)
|
20
|
-
|
21
|
-
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
|
22
|
-
|
23
|
-
|
24
|
-
def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
|
25
|
-
if environment_name:
|
26
|
-
return environment_name
|
27
|
-
elif resolver and resolver.environment_name:
|
28
|
-
return resolver.environment_name
|
29
|
-
else:
|
30
|
-
return config.get("environment")
|
31
|
-
|
32
|
-
|
33
|
-
class _Object:
|
34
|
-
_type_prefix: ClassVar[Optional[str]] = None
|
35
|
-
_prefix_to_type: ClassVar[dict[str, type]] = {}
|
36
|
-
|
37
|
-
# For constructors
|
38
|
-
_load: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]]
|
39
|
-
_preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]]
|
40
|
-
_rep: str
|
41
|
-
_is_another_app: bool
|
42
|
-
_hydrate_lazily: bool
|
43
|
-
_deps: Optional[Callable[..., list["_Object"]]]
|
44
|
-
_deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
|
45
|
-
|
46
|
-
# For hydrated objects
|
47
|
-
_object_id: str
|
48
|
-
_client: _Client
|
49
|
-
_is_hydrated: bool
|
50
|
-
_is_rehydrated: bool
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def __init_subclass__(cls, type_prefix: Optional[str] = None):
|
54
|
-
super().__init_subclass__()
|
55
|
-
if type_prefix is not None:
|
56
|
-
cls._type_prefix = type_prefix
|
57
|
-
cls._prefix_to_type[type_prefix] = cls
|
58
|
-
|
59
|
-
def __init__(self, *args, **kwargs):
|
60
|
-
raise InvalidError(f"Class {type(self).__name__} has no constructor. Use class constructor methods instead.")
|
61
|
-
|
62
|
-
def _init(
|
63
|
-
self,
|
64
|
-
rep: str,
|
65
|
-
load: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
|
66
|
-
is_another_app: bool = False,
|
67
|
-
preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
|
68
|
-
hydrate_lazily: bool = False,
|
69
|
-
deps: Optional[Callable[..., list["_Object"]]] = None,
|
70
|
-
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
71
|
-
):
|
72
|
-
self._local_uuid = str(uuid.uuid4())
|
73
|
-
self._load = load
|
74
|
-
self._preload = preload
|
75
|
-
self._rep = rep
|
76
|
-
self._is_another_app = is_another_app
|
77
|
-
self._hydrate_lazily = hydrate_lazily
|
78
|
-
self._deps = deps
|
79
|
-
self._deduplication_key = deduplication_key
|
80
|
-
|
81
|
-
self._object_id = None
|
82
|
-
self._client = None
|
83
|
-
self._is_hydrated = False
|
84
|
-
self._is_rehydrated = False
|
85
|
-
|
86
|
-
self._initialize_from_empty()
|
87
|
-
|
88
|
-
def _unhydrate(self):
|
89
|
-
self._object_id = None
|
90
|
-
self._client = None
|
91
|
-
self._is_hydrated = False
|
92
|
-
|
93
|
-
def _initialize_from_empty(self):
|
94
|
-
# default implementation, can be overriden in subclasses
|
95
|
-
pass
|
96
|
-
|
97
|
-
def _initialize_from_other(self, other):
|
98
|
-
# default implementation, can be overriden in subclasses
|
99
|
-
self._object_id = other._object_id
|
100
|
-
self._is_hydrated = other._is_hydrated
|
101
|
-
self._client = other._client
|
102
|
-
|
103
|
-
def _hydrate(self, object_id: str, client: _Client, metadata: Optional[Message]):
|
104
|
-
assert isinstance(object_id, str)
|
105
|
-
if not object_id.startswith(self._type_prefix):
|
106
|
-
raise ExecutionError(
|
107
|
-
f"Can not hydrate {type(self)}:"
|
108
|
-
f" it has type prefix {self._type_prefix}"
|
109
|
-
f" but the object_id starts with {object_id[:3]}"
|
110
|
-
)
|
111
|
-
self._object_id = object_id
|
112
|
-
self._client = client
|
113
|
-
self._hydrate_metadata(metadata)
|
114
|
-
self._is_hydrated = True
|
115
|
-
|
116
|
-
def _hydrate_metadata(self, metadata: Optional[Message]):
|
117
|
-
# override this is subclasses that need additional data (other than an object_id) for a functioning Handle
|
118
|
-
pass
|
119
|
-
|
120
|
-
def _get_metadata(self) -> Optional[Message]:
|
121
|
-
# return the necessary metadata from this handle to be able to re-hydrate in another context if one is needed
|
122
|
-
# used to provide a handle's handle_metadata for serializing/pickling a live handle
|
123
|
-
# the object_id is already provided by other means
|
124
|
-
return
|
125
|
-
|
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
|
-
)
|
138
|
-
|
139
|
-
def clone(self: O) -> O:
|
140
|
-
"""mdmd:hidden Clone a given hydrated object."""
|
141
|
-
|
142
|
-
# Object to clone must already be hydrated, otherwise from_loader is more suitable.
|
143
|
-
self._validate_is_hydrated()
|
144
|
-
obj = type(self).__new__(type(self))
|
145
|
-
obj._initialize_from_other(self)
|
146
|
-
return obj
|
147
|
-
|
148
|
-
@classmethod
|
149
|
-
def _from_loader(
|
150
|
-
cls,
|
151
|
-
load: Callable[[O, Resolver, Optional[str]], Awaitable[None]],
|
152
|
-
rep: str,
|
153
|
-
is_another_app: bool = False,
|
154
|
-
preload: Optional[Callable[[O, Resolver, Optional[str]], Awaitable[None]]] = None,
|
155
|
-
hydrate_lazily: bool = False,
|
156
|
-
deps: Optional[Callable[..., Sequence["_Object"]]] = None,
|
157
|
-
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
158
|
-
):
|
159
|
-
# TODO(erikbern): flip the order of the two first arguments
|
160
|
-
obj = _Object.__new__(cls)
|
161
|
-
obj._init(rep, load, is_another_app, preload, hydrate_lazily, deps, deduplication_key)
|
162
|
-
return obj
|
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
|
-
|
178
|
-
@classmethod
|
179
|
-
def _new_hydrated(
|
180
|
-
cls: type[O], object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
|
181
|
-
) -> O:
|
182
|
-
if cls._type_prefix is not None:
|
183
|
-
# This is called directly on a subclass, e.g. Secret.from_id
|
184
|
-
if not object_id.startswith(cls._type_prefix + "-"):
|
185
|
-
raise InvalidError(f"Object {object_id} does not start with {cls._type_prefix}")
|
186
|
-
obj_cls = cls
|
187
|
-
else:
|
188
|
-
# This is called on the base class, e.g. Handle.from_id
|
189
|
-
obj_cls = cls._get_type_from_id(object_id)
|
190
|
-
|
191
|
-
# Instantiate provider
|
192
|
-
obj = _Object.__new__(obj_cls)
|
193
|
-
rep = f"Object({object_id})" # TODO(erikbern): dumb
|
194
|
-
obj._init(rep, is_another_app=is_another_app)
|
195
|
-
obj._hydrate(object_id, client, handle_metadata)
|
196
|
-
|
197
|
-
return obj
|
198
|
-
|
199
|
-
def _hydrate_from_other(self, other: O):
|
200
|
-
self._hydrate(other._object_id, other._client, other._get_metadata())
|
201
|
-
|
202
|
-
def __repr__(self):
|
203
|
-
return self._rep
|
204
|
-
|
205
|
-
@property
|
206
|
-
def local_uuid(self):
|
207
|
-
"""mdmd:hidden"""
|
208
|
-
return self._local_uuid
|
209
|
-
|
210
|
-
@property
|
211
|
-
def object_id(self) -> str:
|
212
|
-
"""mdmd:hidden"""
|
213
|
-
return self._object_id
|
214
|
-
|
215
|
-
@property
|
216
|
-
def is_hydrated(self) -> bool:
|
217
|
-
"""mdmd:hidden"""
|
218
|
-
return self._is_hydrated
|
219
|
-
|
220
|
-
@property
|
221
|
-
def deps(self) -> Callable[..., list["_Object"]]:
|
222
|
-
"""mdmd:hidden"""
|
223
|
-
return self._deps if self._deps is not None else lambda: []
|
224
|
-
|
225
|
-
async def resolve(self, client: Optional[_Client] = None):
|
226
|
-
"""mdmd:hidden"""
|
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)}")
|
238
|
-
return
|
239
|
-
elif not self._hydrate_lazily:
|
240
|
-
self._validate_is_hydrated()
|
241
|
-
else:
|
242
|
-
# TODO: this client and/or resolver can't be changed by a caller to X.from_name()
|
243
|
-
c = client if client is not None else await _Client.from_env()
|
244
|
-
resolver = Resolver(c)
|
245
|
-
await resolver.load(self)
|
246
|
-
|
247
4
|
|
248
5
|
Object = synchronize_api(_Object, target_module=__name__)
|
249
|
-
|
250
|
-
|
251
|
-
def live_method(method):
|
252
|
-
@wraps(method)
|
253
|
-
async def wrapped(self, *args, **kwargs):
|
254
|
-
await self.resolve()
|
255
|
-
return await method(self, *args, **kwargs)
|
256
|
-
|
257
|
-
return wrapped
|
258
|
-
|
259
|
-
|
260
|
-
def live_method_gen(method):
|
261
|
-
@wraps(method)
|
262
|
-
async def wrapped(self, *args, **kwargs):
|
263
|
-
await self.resolve()
|
264
|
-
async with aclosing(method(self, *args, **kwargs)) as stream:
|
265
|
-
async for item in stream:
|
266
|
-
yield item
|
267
|
-
|
268
|
-
return wrapped
|