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/network_file_system.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Copyright Modal Labs 2023
|
2
|
+
import functools
|
2
3
|
import os
|
3
4
|
import time
|
5
|
+
from collections.abc import AsyncIterator
|
4
6
|
from pathlib import Path, PurePosixPath
|
5
|
-
from typing import
|
7
|
+
from typing import Any, BinaryIO, Callable, Optional, Union
|
6
8
|
|
7
9
|
from grpclib import GRPCError, Status
|
8
10
|
from synchronicity.async_wrap import asynccontextmanager
|
@@ -11,12 +13,14 @@ import modal
|
|
11
13
|
from modal_proto import api_pb2
|
12
14
|
|
13
15
|
from ._resolver import Resolver
|
14
|
-
from ._utils.async_utils import
|
16
|
+
from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
|
15
17
|
from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
|
16
|
-
from ._utils.
|
18
|
+
from ._utils.deprecation import renamed_parameter
|
19
|
+
from ._utils.grpc_utils import retry_transient_errors
|
17
20
|
from ._utils.hash_utils import get_sha256_hex
|
21
|
+
from ._utils.name_utils import check_object_name
|
18
22
|
from .client import _Client
|
19
|
-
from .exception import
|
23
|
+
from .exception import InvalidError
|
20
24
|
from .object import (
|
21
25
|
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
22
26
|
_get_environment_name,
|
@@ -32,9 +36,9 @@ NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
|
|
32
36
|
|
33
37
|
|
34
38
|
def network_file_system_mount_protos(
|
35
|
-
validated_network_file_systems:
|
39
|
+
validated_network_file_systems: list[tuple[str, "_NetworkFileSystem"]],
|
36
40
|
allow_cross_region_volumes: bool,
|
37
|
-
) ->
|
41
|
+
) -> list[api_pb2.SharedVolumeMount]:
|
38
42
|
network_file_system_mounts = []
|
39
43
|
# Relies on dicts being ordered (true as of Python 3.6).
|
40
44
|
for path, volume in validated_network_file_systems:
|
@@ -60,7 +64,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
60
64
|
import modal
|
61
65
|
|
62
66
|
nfs = modal.NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
|
63
|
-
app = modal.App()
|
67
|
+
app = modal.App()
|
64
68
|
|
65
69
|
@app.function(network_file_systems={"/root/foo": nfs})
|
66
70
|
def f():
|
@@ -73,7 +77,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
73
77
|
|
74
78
|
Also see the CLI methods for accessing network file systems:
|
75
79
|
|
76
|
-
```
|
80
|
+
```
|
77
81
|
modal nfs --help
|
78
82
|
```
|
79
83
|
|
@@ -87,68 +91,52 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
87
91
|
"""
|
88
92
|
|
89
93
|
@staticmethod
|
90
|
-
|
91
|
-
"""`NetworkFileSystem.new` is deprecated.
|
92
|
-
|
93
|
-
Please use `NetworkFileSystem.from_name` (for persisted) or `NetworkFileSystem.ephemeral`
|
94
|
-
(for ephemeral) network filesystems.
|
95
|
-
"""
|
96
|
-
deprecation_warning((2024, 3, 20), NetworkFileSystem.new.__doc__)
|
97
|
-
|
98
|
-
async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
|
99
|
-
status_row = resolver.add_status_row()
|
100
|
-
if existing_object_id:
|
101
|
-
# Volume already exists; do nothing.
|
102
|
-
self._hydrate(existing_object_id, resolver.client, None)
|
103
|
-
return
|
104
|
-
|
105
|
-
if cloud:
|
106
|
-
deprecation_warning((2024, 1, 17), "Argument `cloud` is deprecated (has no effect).")
|
107
|
-
|
108
|
-
status_row.message("Creating network file system...")
|
109
|
-
req = api_pb2.SharedVolumeCreateRequest(app_id=resolver.app_id)
|
110
|
-
resp = await retry_transient_errors(resolver.client.stub.SharedVolumeCreate, req)
|
111
|
-
status_row.finish("Created network file system.")
|
112
|
-
self._hydrate(resp.shared_volume_id, resolver.client, None)
|
113
|
-
|
114
|
-
return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()")
|
115
|
-
|
116
|
-
@staticmethod
|
94
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
117
95
|
def from_name(
|
118
|
-
|
96
|
+
name: str,
|
119
97
|
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
120
98
|
environment_name: Optional[str] = None,
|
121
99
|
create_if_missing: bool = False,
|
122
100
|
) -> "_NetworkFileSystem":
|
123
|
-
"""
|
101
|
+
"""Reference a NetworkFileSystem by its name, creating if necessary.
|
124
102
|
|
125
|
-
|
103
|
+
In contrast to `modal.NetworkFileSystem.lookup`, this is a lazy method
|
104
|
+
that defers hydrating the local object with metadata from Modal servers
|
105
|
+
until the first time it is actually used.
|
126
106
|
|
127
107
|
```python notest
|
128
|
-
|
108
|
+
nfs = NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
|
129
109
|
|
130
|
-
@app.function(network_file_systems={"/
|
110
|
+
@app.function(network_file_systems={"/data": nfs})
|
131
111
|
def f():
|
132
112
|
pass
|
133
113
|
```
|
134
114
|
"""
|
115
|
+
check_object_name(name, "NetworkFileSystem")
|
135
116
|
|
136
117
|
async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
|
137
118
|
req = api_pb2.SharedVolumeGetOrCreateRequest(
|
138
|
-
deployment_name=
|
119
|
+
deployment_name=name,
|
139
120
|
namespace=namespace,
|
140
121
|
environment_name=_get_environment_name(environment_name, resolver),
|
141
122
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
142
123
|
)
|
143
|
-
|
144
|
-
|
124
|
+
try:
|
125
|
+
response = await resolver.client.stub.SharedVolumeGetOrCreate(req)
|
126
|
+
self._hydrate(response.shared_volume_id, resolver.client, None)
|
127
|
+
except GRPCError as exc:
|
128
|
+
if exc.status == Status.NOT_FOUND and exc.message == "App has wrong entity vo":
|
129
|
+
raise InvalidError(
|
130
|
+
f"Attempted to mount: `{name}` as a NetworkFileSystem " + "which already exists as a Volume"
|
131
|
+
)
|
132
|
+
raise
|
145
133
|
|
146
134
|
return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()", hydrate_lazily=True)
|
147
135
|
|
148
136
|
@classmethod
|
149
137
|
@asynccontextmanager
|
150
138
|
async def ephemeral(
|
151
|
-
cls:
|
139
|
+
cls: type["_NetworkFileSystem"],
|
152
140
|
client: Optional[_Client] = None,
|
153
141
|
environment_name: Optional[str] = None,
|
154
142
|
_heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
@@ -157,11 +145,13 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
157
145
|
|
158
146
|
Usage:
|
159
147
|
```python
|
160
|
-
with NetworkFileSystem.ephemeral() as nfs:
|
161
|
-
assert nfs.listdir() == []
|
148
|
+
with modal.NetworkFileSystem.ephemeral() as nfs:
|
149
|
+
assert nfs.listdir("/") == []
|
150
|
+
```
|
162
151
|
|
163
|
-
|
164
|
-
|
152
|
+
```python notest
|
153
|
+
async with modal.NetworkFileSystem.ephemeral() as nfs:
|
154
|
+
assert await nfs.listdir("/") == []
|
165
155
|
```
|
166
156
|
"""
|
167
157
|
if client is None:
|
@@ -177,44 +167,26 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
177
167
|
yield cls._new_hydrated(response.shared_volume_id, client, None, is_another_app=True)
|
178
168
|
|
179
169
|
@staticmethod
|
180
|
-
|
181
|
-
label: str,
|
182
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
183
|
-
environment_name: Optional[str] = None,
|
184
|
-
cloud: Optional[str] = None,
|
185
|
-
) -> "_NetworkFileSystem":
|
186
|
-
"""Deprecated! Use `NetworkFileSystem.from_name(name, create_if_missing=True)`."""
|
187
|
-
deprecation_warning((2024, 3, 1), _NetworkFileSystem.persisted.__doc__)
|
188
|
-
return _NetworkFileSystem.from_name(label, namespace, environment_name, create_if_missing=True)
|
189
|
-
|
190
|
-
def persist(
|
191
|
-
self,
|
192
|
-
label: str,
|
193
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
194
|
-
environment_name: Optional[str] = None,
|
195
|
-
cloud: Optional[str] = None,
|
196
|
-
):
|
197
|
-
"""`NetworkFileSystem().persist("my-volume")` is deprecated. Use `NetworkFileSystem.from_name("my-volume", create_if_missing=True)` instead."""
|
198
|
-
deprecation_warning((2024, 2, 29), _NetworkFileSystem.persist.__doc__)
|
199
|
-
return self.persisted(label, namespace, environment_name, cloud)
|
200
|
-
|
201
|
-
@staticmethod
|
170
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
202
171
|
async def lookup(
|
203
|
-
|
172
|
+
name: str,
|
204
173
|
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
205
174
|
client: Optional[_Client] = None,
|
206
175
|
environment_name: Optional[str] = None,
|
207
176
|
create_if_missing: bool = False,
|
208
177
|
) -> "_NetworkFileSystem":
|
209
|
-
"""Lookup a
|
178
|
+
"""Lookup a named NetworkFileSystem.
|
210
179
|
|
211
|
-
|
212
|
-
|
213
|
-
|
180
|
+
In contrast to `modal.NetworkFileSystem.from_name`, this is an eager method
|
181
|
+
that will hydrate the local object with metadata from Modal servers.
|
182
|
+
|
183
|
+
```python notest
|
184
|
+
nfs = modal.NetworkFileSystem.lookup("my-nfs")
|
185
|
+
print(nfs.listdir("/"))
|
214
186
|
```
|
215
187
|
"""
|
216
188
|
obj = _NetworkFileSystem.from_name(
|
217
|
-
|
189
|
+
name, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
|
218
190
|
)
|
219
191
|
if client is None:
|
220
192
|
client = await _Client.from_env()
|
@@ -230,6 +202,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
230
202
|
environment_name: Optional[str] = None,
|
231
203
|
) -> str:
|
232
204
|
"""mdmd:hidden"""
|
205
|
+
check_object_name(deployment_name, "NetworkFileSystem")
|
233
206
|
if client is None:
|
234
207
|
client = await _Client.from_env()
|
235
208
|
request = api_pb2.SharedVolumeGetOrCreateRequest(
|
@@ -241,8 +214,15 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
241
214
|
resp = await retry_transient_errors(client.stub.SharedVolumeGetOrCreate, request)
|
242
215
|
return resp.shared_volume_id
|
243
216
|
|
217
|
+
@staticmethod
|
218
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
219
|
+
async def delete(name: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
|
220
|
+
obj = await _NetworkFileSystem.lookup(name, client=client, environment_name=environment_name)
|
221
|
+
req = api_pb2.SharedVolumeDeleteRequest(shared_volume_id=obj.object_id)
|
222
|
+
await retry_transient_errors(obj._client.stub.SharedVolumeDelete, req)
|
223
|
+
|
244
224
|
@live_method
|
245
|
-
async def write_file(self, remote_path: str, fp: BinaryIO) -> int:
|
225
|
+
async def write_file(self, remote_path: str, fp: BinaryIO, progress_cb: Optional[Callable[..., Any]] = None) -> int:
|
246
226
|
"""Write from a file object to a path on the network file system, atomically.
|
247
227
|
|
248
228
|
Will create any needed parent directories automatically.
|
@@ -250,12 +230,20 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
250
230
|
If remote_path ends with `/` it's assumed to be a directory and the
|
251
231
|
file will be uploaded with its current name to that directory.
|
252
232
|
"""
|
233
|
+
progress_cb = progress_cb or (lambda *_, **__: None)
|
234
|
+
|
253
235
|
sha_hash = get_sha256_hex(fp)
|
254
236
|
fp.seek(0, os.SEEK_END)
|
255
237
|
data_size = fp.tell()
|
256
238
|
fp.seek(0)
|
257
239
|
if data_size > LARGE_FILE_LIMIT:
|
258
|
-
|
240
|
+
progress_task_id = progress_cb(name=remote_path, size=data_size)
|
241
|
+
blob_id = await blob_upload_file(
|
242
|
+
fp,
|
243
|
+
self._client.stub,
|
244
|
+
progress_report_cb=functools.partial(progress_cb, progress_task_id),
|
245
|
+
sha256_hex=sha_hash,
|
246
|
+
)
|
259
247
|
req = api_pb2.SharedVolumePutFileRequest(
|
260
248
|
shared_volume_id=self.object_id,
|
261
249
|
path=remote_path,
|
@@ -299,16 +287,20 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
299
287
|
|
300
288
|
* Passing a directory path lists all files in the directory (names are relative to the directory)
|
301
289
|
* Passing a file path returns a list containing only that file's listing description
|
302
|
-
* Passing a glob path (including at least one * or ** sequence) returns all files matching
|
290
|
+
* Passing a glob path (including at least one * or ** sequence) returns all files matching
|
291
|
+
that glob path (using absolute paths)
|
303
292
|
"""
|
304
293
|
req = api_pb2.SharedVolumeListFilesRequest(shared_volume_id=self.object_id, path=path)
|
305
|
-
async for batch in
|
294
|
+
async for batch in self._client.stub.SharedVolumeListFilesStream.unary_stream(req):
|
306
295
|
for entry in batch.entries:
|
307
296
|
yield FileEntry._from_proto(entry)
|
308
297
|
|
309
298
|
@live_method
|
310
299
|
async def add_local_file(
|
311
|
-
self,
|
300
|
+
self,
|
301
|
+
local_path: Union[Path, str],
|
302
|
+
remote_path: Optional[Union[str, PurePosixPath, None]] = None,
|
303
|
+
progress_cb: Optional[Callable[..., Any]] = None,
|
312
304
|
):
|
313
305
|
local_path = Path(local_path)
|
314
306
|
if remote_path is None:
|
@@ -317,13 +309,14 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
317
309
|
remote_path = PurePosixPath(remote_path).as_posix()
|
318
310
|
|
319
311
|
with local_path.open("rb") as local_file:
|
320
|
-
return await self.write_file(remote_path, local_file)
|
312
|
+
return await self.write_file(remote_path, local_file, progress_cb=progress_cb)
|
321
313
|
|
322
314
|
@live_method
|
323
315
|
async def add_local_dir(
|
324
316
|
self,
|
325
317
|
local_path: Union[Path, str],
|
326
318
|
remote_path: Optional[Union[str, PurePosixPath, None]] = None,
|
319
|
+
progress_cb: Optional[Callable[..., Any]] = None,
|
327
320
|
):
|
328
321
|
_local_path = Path(local_path)
|
329
322
|
if remote_path is None:
|
@@ -338,17 +331,23 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
338
331
|
if subpath.is_dir():
|
339
332
|
continue
|
340
333
|
relpath_str = subpath.relative_to(_local_path).as_posix()
|
341
|
-
yield
|
334
|
+
yield subpath, PurePosixPath(remote_path, relpath_str)
|
335
|
+
|
336
|
+
async def _add_local_file(paths: tuple[Path, PurePosixPath]) -> int:
|
337
|
+
return await self.add_local_file(paths[0], paths[1], progress_cb)
|
342
338
|
|
343
|
-
|
339
|
+
async with aclosing(async_map(sync_or_async_iter(gen_transfers()), _add_local_file, concurrency=20)) as stream:
|
340
|
+
async for _ in stream: # consume/execute the map
|
341
|
+
pass
|
344
342
|
|
345
343
|
@live_method
|
346
|
-
async def listdir(self, path: str) ->
|
344
|
+
async def listdir(self, path: str) -> list[FileEntry]:
|
347
345
|
"""List all files in a directory in the network file system.
|
348
346
|
|
349
347
|
* Passing a directory path lists all files in the directory (names are relative to the directory)
|
350
348
|
* Passing a file path returns a list containing only that file's listing description
|
351
|
-
* Passing a glob path (including at least one * or ** sequence) returns all files matching
|
349
|
+
* Passing a glob path (including at least one * or ** sequence) returns all files matching
|
350
|
+
that glob path (using absolute paths)
|
352
351
|
"""
|
353
352
|
return [entry async for entry in self.iterdir(path)]
|
354
353
|
|
modal/network_file_system.pyi
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import collections.abc
|
1
2
|
import modal.client
|
2
3
|
import modal.object
|
3
4
|
import modal.volume
|
@@ -7,160 +8,200 @@ import synchronicity.combined_types
|
|
7
8
|
import typing
|
8
9
|
import typing_extensions
|
9
10
|
|
10
|
-
def network_file_system_mount_protos(
|
11
|
-
|
12
|
-
|
11
|
+
def network_file_system_mount_protos(
|
12
|
+
validated_network_file_systems: list[tuple[str, _NetworkFileSystem]], allow_cross_region_volumes: bool
|
13
|
+
) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
|
13
14
|
|
14
15
|
class _NetworkFileSystem(modal.object._Object):
|
15
16
|
@staticmethod
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
@staticmethod
|
20
|
-
def from_name(label: str, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> _NetworkFileSystem:
|
21
|
-
...
|
22
|
-
|
17
|
+
def from_name(
|
18
|
+
name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
19
|
+
) -> _NetworkFileSystem: ...
|
23
20
|
@classmethod
|
24
|
-
def ephemeral(
|
25
|
-
|
26
|
-
|
21
|
+
def ephemeral(
|
22
|
+
cls: type[_NetworkFileSystem],
|
23
|
+
client: typing.Optional[modal.client._Client] = None,
|
24
|
+
environment_name: typing.Optional[str] = None,
|
25
|
+
_heartbeat_sleep: float = 300,
|
26
|
+
) -> typing.AsyncContextManager[_NetworkFileSystem]: ...
|
27
27
|
@staticmethod
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
async def lookup(
|
29
|
+
name: str,
|
30
|
+
namespace=1,
|
31
|
+
client: typing.Optional[modal.client._Client] = None,
|
32
|
+
environment_name: typing.Optional[str] = None,
|
33
|
+
create_if_missing: bool = False,
|
34
|
+
) -> _NetworkFileSystem: ...
|
34
35
|
@staticmethod
|
35
|
-
async def
|
36
|
-
|
37
|
-
|
36
|
+
async def create_deployed(
|
37
|
+
deployment_name: str,
|
38
|
+
namespace=1,
|
39
|
+
client: typing.Optional[modal.client._Client] = None,
|
40
|
+
environment_name: typing.Optional[str] = None,
|
41
|
+
) -> str: ...
|
38
42
|
@staticmethod
|
39
|
-
async def
|
40
|
-
|
41
|
-
|
42
|
-
async def write_file(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
43
|
+
async def delete(
|
44
|
+
name: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
|
45
|
+
): ...
|
46
|
+
async def write_file(
|
47
|
+
self,
|
48
|
+
remote_path: str,
|
49
|
+
fp: typing.BinaryIO,
|
50
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
51
|
+
) -> int: ...
|
52
|
+
def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
53
|
+
def iterdir(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
|
54
|
+
async def add_local_file(
|
55
|
+
self,
|
56
|
+
local_path: typing.Union[pathlib.Path, str],
|
57
|
+
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
58
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
59
|
+
): ...
|
60
|
+
async def add_local_dir(
|
61
|
+
self,
|
62
|
+
local_path: typing.Union[pathlib.Path, str],
|
63
|
+
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
64
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
65
|
+
): ...
|
66
|
+
async def listdir(self, path: str) -> list[modal.volume.FileEntry]: ...
|
67
|
+
async def remove_file(self, path: str, recursive=False): ...
|
63
68
|
|
64
69
|
class NetworkFileSystem(modal.object.Object):
|
65
|
-
def __init__(self, *args, **kwargs):
|
66
|
-
...
|
67
|
-
|
70
|
+
def __init__(self, *args, **kwargs): ...
|
68
71
|
@staticmethod
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
@staticmethod
|
73
|
-
def from_name(label: str, namespace=1, environment_name: typing.Union[str, None] = None, create_if_missing: bool = False) -> NetworkFileSystem:
|
74
|
-
...
|
75
|
-
|
72
|
+
def from_name(
|
73
|
+
name: str, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
|
74
|
+
) -> NetworkFileSystem: ...
|
76
75
|
@classmethod
|
77
|
-
def ephemeral(
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
def persist(self, label: str, namespace=1, environment_name: typing.Union[str, None] = None, cloud: typing.Union[str, None] = None):
|
85
|
-
...
|
76
|
+
def ephemeral(
|
77
|
+
cls: type[NetworkFileSystem],
|
78
|
+
client: typing.Optional[modal.client.Client] = None,
|
79
|
+
environment_name: typing.Optional[str] = None,
|
80
|
+
_heartbeat_sleep: float = 300,
|
81
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[NetworkFileSystem]: ...
|
86
82
|
|
87
83
|
class __lookup_spec(typing_extensions.Protocol):
|
88
|
-
def __call__(
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
84
|
+
def __call__(
|
85
|
+
self,
|
86
|
+
name: str,
|
87
|
+
namespace=1,
|
88
|
+
client: typing.Optional[modal.client.Client] = None,
|
89
|
+
environment_name: typing.Optional[str] = None,
|
90
|
+
create_if_missing: bool = False,
|
91
|
+
) -> NetworkFileSystem: ...
|
92
|
+
async def aio(
|
93
|
+
self,
|
94
|
+
name: str,
|
95
|
+
namespace=1,
|
96
|
+
client: typing.Optional[modal.client.Client] = None,
|
97
|
+
environment_name: typing.Optional[str] = None,
|
98
|
+
create_if_missing: bool = False,
|
99
|
+
) -> NetworkFileSystem: ...
|
93
100
|
|
94
101
|
lookup: __lookup_spec
|
95
102
|
|
96
103
|
class __create_deployed_spec(typing_extensions.Protocol):
|
97
|
-
def __call__(
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
104
|
+
def __call__(
|
105
|
+
self,
|
106
|
+
deployment_name: str,
|
107
|
+
namespace=1,
|
108
|
+
client: typing.Optional[modal.client.Client] = None,
|
109
|
+
environment_name: typing.Optional[str] = None,
|
110
|
+
) -> str: ...
|
111
|
+
async def aio(
|
112
|
+
self,
|
113
|
+
deployment_name: str,
|
114
|
+
namespace=1,
|
115
|
+
client: typing.Optional[modal.client.Client] = None,
|
116
|
+
environment_name: typing.Optional[str] = None,
|
117
|
+
) -> str: ...
|
102
118
|
|
103
119
|
create_deployed: __create_deployed_spec
|
104
120
|
|
105
|
-
class
|
106
|
-
def __call__(
|
107
|
-
|
121
|
+
class __delete_spec(typing_extensions.Protocol):
|
122
|
+
def __call__(
|
123
|
+
self,
|
124
|
+
name: str,
|
125
|
+
client: typing.Optional[modal.client.Client] = None,
|
126
|
+
environment_name: typing.Optional[str] = None,
|
127
|
+
): ...
|
128
|
+
async def aio(
|
129
|
+
self,
|
130
|
+
name: str,
|
131
|
+
client: typing.Optional[modal.client.Client] = None,
|
132
|
+
environment_name: typing.Optional[str] = None,
|
133
|
+
): ...
|
134
|
+
|
135
|
+
delete: __delete_spec
|
108
136
|
|
109
|
-
|
110
|
-
|
137
|
+
class __write_file_spec(typing_extensions.Protocol):
|
138
|
+
def __call__(
|
139
|
+
self,
|
140
|
+
remote_path: str,
|
141
|
+
fp: typing.BinaryIO,
|
142
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
143
|
+
) -> int: ...
|
144
|
+
async def aio(
|
145
|
+
self,
|
146
|
+
remote_path: str,
|
147
|
+
fp: typing.BinaryIO,
|
148
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
149
|
+
) -> int: ...
|
111
150
|
|
112
151
|
write_file: __write_file_spec
|
113
152
|
|
114
153
|
class __read_file_spec(typing_extensions.Protocol):
|
115
|
-
def __call__(self, path: str) -> typing.Iterator[bytes]:
|
116
|
-
|
117
|
-
|
118
|
-
def aio(self, path: str) -> typing.AsyncIterator[bytes]:
|
119
|
-
...
|
154
|
+
def __call__(self, path: str) -> typing.Iterator[bytes]: ...
|
155
|
+
def aio(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
120
156
|
|
121
157
|
read_file: __read_file_spec
|
122
158
|
|
123
159
|
class __iterdir_spec(typing_extensions.Protocol):
|
124
|
-
def __call__(self, path: str) -> typing.Iterator[modal.volume.FileEntry]:
|
125
|
-
|
126
|
-
|
127
|
-
def aio(self, path: str) -> typing.AsyncIterator[modal.volume.FileEntry]:
|
128
|
-
...
|
160
|
+
def __call__(self, path: str) -> typing.Iterator[modal.volume.FileEntry]: ...
|
161
|
+
def aio(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
|
129
162
|
|
130
163
|
iterdir: __iterdir_spec
|
131
164
|
|
132
165
|
class __add_local_file_spec(typing_extensions.Protocol):
|
133
|
-
def __call__(
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
166
|
+
def __call__(
|
167
|
+
self,
|
168
|
+
local_path: typing.Union[pathlib.Path, str],
|
169
|
+
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
170
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
171
|
+
): ...
|
172
|
+
async def aio(
|
173
|
+
self,
|
174
|
+
local_path: typing.Union[pathlib.Path, str],
|
175
|
+
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
176
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
177
|
+
): ...
|
138
178
|
|
139
179
|
add_local_file: __add_local_file_spec
|
140
180
|
|
141
181
|
class __add_local_dir_spec(typing_extensions.Protocol):
|
142
|
-
def __call__(
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
182
|
+
def __call__(
|
183
|
+
self,
|
184
|
+
local_path: typing.Union[pathlib.Path, str],
|
185
|
+
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
186
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
187
|
+
): ...
|
188
|
+
async def aio(
|
189
|
+
self,
|
190
|
+
local_path: typing.Union[pathlib.Path, str],
|
191
|
+
remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
|
192
|
+
progress_cb: typing.Optional[typing.Callable[..., typing.Any]] = None,
|
193
|
+
): ...
|
147
194
|
|
148
195
|
add_local_dir: __add_local_dir_spec
|
149
196
|
|
150
197
|
class __listdir_spec(typing_extensions.Protocol):
|
151
|
-
def __call__(self, path: str) ->
|
152
|
-
|
153
|
-
|
154
|
-
async def aio(self, *args, **kwargs) -> typing.List[modal.volume.FileEntry]:
|
155
|
-
...
|
198
|
+
def __call__(self, path: str) -> list[modal.volume.FileEntry]: ...
|
199
|
+
async def aio(self, path: str) -> list[modal.volume.FileEntry]: ...
|
156
200
|
|
157
201
|
listdir: __listdir_spec
|
158
202
|
|
159
203
|
class __remove_file_spec(typing_extensions.Protocol):
|
160
|
-
def __call__(self, path: str, recursive=False):
|
161
|
-
|
162
|
-
|
163
|
-
async def aio(self, *args, **kwargs):
|
164
|
-
...
|
204
|
+
def __call__(self, path: str, recursive=False): ...
|
205
|
+
async def aio(self, path: str, recursive=False): ...
|
165
206
|
|
166
207
|
remove_file: __remove_file_spec
|