modal 0.62.16__py3-none-any.whl → 0.72.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modal/__init__.py +17 -13
- modal/__main__.py +41 -3
- modal/_clustered_functions.py +80 -0
- modal/_clustered_functions.pyi +22 -0
- modal/_container_entrypoint.py +420 -937
- modal/_ipython.py +3 -13
- modal/_location.py +17 -10
- modal/_output.py +243 -99
- modal/_pty.py +2 -2
- modal/_resolver.py +55 -59
- modal/_resources.py +51 -0
- modal/_runtime/__init__.py +1 -0
- modal/_runtime/asgi.py +519 -0
- modal/_runtime/container_io_manager.py +1036 -0
- modal/_runtime/execution_context.py +89 -0
- modal/_runtime/telemetry.py +169 -0
- modal/_runtime/user_code_imports.py +356 -0
- modal/_serialization.py +134 -9
- modal/_traceback.py +47 -187
- modal/_tunnel.py +52 -16
- modal/_tunnel.pyi +19 -36
- modal/_utils/app_utils.py +3 -17
- modal/_utils/async_utils.py +479 -100
- modal/_utils/blob_utils.py +157 -186
- modal/_utils/bytes_io_segment_payload.py +97 -0
- modal/_utils/deprecation.py +89 -0
- modal/_utils/docker_utils.py +98 -0
- modal/_utils/function_utils.py +460 -171
- modal/_utils/grpc_testing.py +47 -31
- modal/_utils/grpc_utils.py +62 -109
- modal/_utils/hash_utils.py +61 -19
- modal/_utils/http_utils.py +39 -9
- modal/_utils/logger.py +2 -1
- modal/_utils/mount_utils.py +34 -16
- modal/_utils/name_utils.py +58 -0
- modal/_utils/package_utils.py +14 -1
- modal/_utils/pattern_utils.py +205 -0
- modal/_utils/rand_pb_testing.py +5 -7
- modal/_utils/shell_utils.py +15 -49
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +14 -12
- modal/app.py +1003 -314
- modal/app.pyi +540 -264
- modal/call_graph.py +7 -6
- modal/cli/_download.py +63 -53
- modal/cli/_traceback.py +200 -0
- modal/cli/app.py +205 -45
- modal/cli/config.py +12 -5
- modal/cli/container.py +62 -14
- modal/cli/dict.py +128 -0
- modal/cli/entry_point.py +26 -13
- modal/cli/environment.py +40 -9
- modal/cli/import_refs.py +64 -58
- modal/cli/launch.py +32 -18
- modal/cli/network_file_system.py +64 -83
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +35 -10
- modal/cli/programs/vscode.py +60 -10
- modal/cli/queues.py +131 -0
- modal/cli/run.py +234 -131
- modal/cli/secret.py +8 -7
- modal/cli/token.py +7 -2
- modal/cli/utils.py +79 -10
- modal/cli/volume.py +110 -109
- modal/client.py +250 -144
- modal/client.pyi +157 -118
- modal/cloud_bucket_mount.py +108 -34
- modal/cloud_bucket_mount.pyi +32 -38
- modal/cls.py +535 -148
- modal/cls.pyi +190 -146
- modal/config.py +41 -19
- modal/container_process.py +177 -0
- modal/container_process.pyi +82 -0
- modal/dict.py +111 -65
- modal/dict.pyi +136 -131
- modal/environments.py +106 -5
- modal/environments.pyi +77 -25
- modal/exception.py +34 -43
- modal/experimental.py +61 -2
- modal/extensions/ipython.py +5 -5
- modal/file_io.py +537 -0
- modal/file_io.pyi +235 -0
- modal/file_pattern_matcher.py +197 -0
- modal/functions.py +906 -911
- modal/functions.pyi +466 -430
- modal/gpu.py +57 -44
- modal/image.py +1089 -479
- modal/image.pyi +584 -228
- modal/io_streams.py +434 -0
- modal/io_streams.pyi +122 -0
- modal/mount.py +314 -101
- modal/mount.pyi +241 -235
- modal/network_file_system.py +92 -92
- modal/network_file_system.pyi +152 -110
- modal/object.py +67 -36
- modal/object.pyi +166 -143
- modal/output.py +63 -0
- modal/parallel_map.py +434 -0
- modal/parallel_map.pyi +75 -0
- modal/partial_function.py +282 -117
- modal/partial_function.pyi +222 -129
- modal/proxy.py +15 -12
- modal/proxy.pyi +3 -8
- modal/queue.py +182 -65
- modal/queue.pyi +218 -118
- modal/requirements/2024.04.txt +29 -0
- modal/requirements/2024.10.txt +16 -0
- modal/requirements/README.md +21 -0
- modal/requirements/base-images.json +22 -0
- modal/retries.py +48 -7
- modal/runner.py +459 -156
- modal/runner.pyi +135 -71
- modal/running_app.py +38 -0
- modal/sandbox.py +514 -236
- modal/sandbox.pyi +397 -169
- modal/schedule.py +4 -4
- modal/scheduler_placement.py +20 -3
- modal/secret.py +56 -31
- modal/secret.pyi +62 -42
- modal/serving.py +51 -56
- modal/serving.pyi +44 -36
- modal/stream_type.py +15 -0
- modal/token_flow.py +5 -3
- modal/token_flow.pyi +37 -32
- modal/volume.py +285 -157
- modal/volume.pyi +249 -184
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/METADATA +7 -7
- modal-0.72.11.dist-info/RECORD +174 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/top_level.txt +0 -1
- modal_docs/gen_reference_docs.py +3 -1
- modal_docs/mdmd/mdmd.py +0 -1
- modal_docs/mdmd/signatures.py +5 -2
- modal_global_objects/images/base_images.py +28 -0
- modal_global_objects/mounts/python_standalone.py +2 -2
- modal_proto/__init__.py +1 -1
- modal_proto/api.proto +1288 -533
- modal_proto/api_grpc.py +856 -456
- modal_proto/api_pb2.py +2165 -1157
- modal_proto/api_pb2.pyi +8859 -0
- modal_proto/api_pb2_grpc.py +1674 -855
- modal_proto/api_pb2_grpc.pyi +1416 -0
- modal_proto/modal_api_grpc.py +149 -0
- modal_proto/modal_options_grpc.py +3 -0
- modal_proto/options_pb2.pyi +20 -0
- modal_proto/options_pb2_grpc.pyi +7 -0
- modal_proto/py.typed +0 -0
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +2 -2
- modal/_asgi.py +0 -370
- modal/_container_entrypoint.pyi +0 -378
- modal/_container_exec.py +0 -128
- modal/_sandbox_shell.py +0 -49
- modal/shared_volume.py +0 -23
- modal/shared_volume.pyi +0 -24
- modal/stub.py +0 -783
- modal/stub.pyi +0 -332
- modal-0.62.16.dist-info/RECORD +0 -198
- modal_global_objects/images/conda.py +0 -15
- modal_global_objects/images/debian_slim.py +0 -15
- modal_global_objects/images/micromamba.py +0 -15
- test/__init__.py +0 -1
- test/aio_test.py +0 -12
- test/async_utils_test.py +0 -262
- test/blob_test.py +0 -67
- test/cli_imports_test.py +0 -149
- test/cli_test.py +0 -659
- test/client_test.py +0 -194
- test/cls_test.py +0 -630
- test/config_test.py +0 -137
- test/conftest.py +0 -1420
- test/container_app_test.py +0 -32
- test/container_test.py +0 -1389
- test/cpu_test.py +0 -23
- test/decorator_test.py +0 -85
- test/deprecation_test.py +0 -34
- test/dict_test.py +0 -33
- test/e2e_test.py +0 -68
- test/error_test.py +0 -7
- test/function_serialization_test.py +0 -32
- test/function_test.py +0 -653
- test/function_utils_test.py +0 -101
- test/gpu_test.py +0 -159
- test/grpc_utils_test.py +0 -141
- test/helpers.py +0 -42
- test/image_test.py +0 -669
- test/live_reload_test.py +0 -80
- test/lookup_test.py +0 -70
- test/mdmd_test.py +0 -329
- test/mount_test.py +0 -162
- test/mounted_files_test.py +0 -329
- test/network_file_system_test.py +0 -181
- test/notebook_test.py +0 -66
- test/object_test.py +0 -41
- test/package_utils_test.py +0 -25
- test/queue_test.py +0 -97
- test/resolver_test.py +0 -58
- test/retries_test.py +0 -67
- test/runner_test.py +0 -85
- test/sandbox_test.py +0 -191
- test/schedule_test.py +0 -15
- test/scheduler_placement_test.py +0 -29
- test/secret_test.py +0 -78
- test/serialization_test.py +0 -42
- test/stub_composition_test.py +0 -10
- test/stub_test.py +0 -360
- test/test_asgi_wrapper.py +0 -234
- test/token_flow_test.py +0 -18
- test/traceback_test.py +0 -135
- test/tunnel_test.py +0 -29
- test/utils_test.py +0 -88
- test/version_test.py +0 -14
- test/volume_test.py +0 -341
- test/watcher_test.py +0 -30
- test/webhook_test.py +0 -146
- /modal/{requirements.312.txt → requirements/2023.12.312.txt} +0 -0
- /modal/{requirements.txt → requirements/2023.12.txt} +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/LICENSE +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/WHEEL +0 -0
- {modal-0.62.16.dist-info → modal-0.72.11.dist-info}/entry_points.txt +0 -0
modal/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,
|
@@ -24,6 +28,7 @@ from .object import (
|
|
24
28
|
live_method,
|
25
29
|
live_method_gen,
|
26
30
|
)
|
31
|
+
from .volume import FileEntry
|
27
32
|
|
28
33
|
NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
|
29
34
|
10 * 60
|
@@ -31,9 +36,9 @@ NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT = (
|
|
31
36
|
|
32
37
|
|
33
38
|
def network_file_system_mount_protos(
|
34
|
-
validated_network_file_systems:
|
39
|
+
validated_network_file_systems: list[tuple[str, "_NetworkFileSystem"]],
|
35
40
|
allow_cross_region_volumes: bool,
|
36
|
-
) ->
|
41
|
+
) -> list[api_pb2.SharedVolumeMount]:
|
37
42
|
network_file_system_mounts = []
|
38
43
|
# Relies on dicts being ordered (true as of Python 3.6).
|
39
44
|
for path, volume in validated_network_file_systems:
|
@@ -59,20 +64,20 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
59
64
|
import modal
|
60
65
|
|
61
66
|
nfs = modal.NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
|
62
|
-
|
67
|
+
app = modal.App()
|
63
68
|
|
64
|
-
@
|
69
|
+
@app.function(network_file_systems={"/root/foo": nfs})
|
65
70
|
def f():
|
66
71
|
pass
|
67
72
|
|
68
|
-
@
|
73
|
+
@app.function(network_file_systems={"/root/goo": nfs})
|
69
74
|
def g():
|
70
75
|
pass
|
71
76
|
```
|
72
77
|
|
73
78
|
Also see the CLI methods for accessing network file systems:
|
74
79
|
|
75
|
-
```
|
80
|
+
```
|
76
81
|
modal nfs --help
|
77
82
|
```
|
78
83
|
|
@@ -86,68 +91,52 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
86
91
|
"""
|
87
92
|
|
88
93
|
@staticmethod
|
89
|
-
|
90
|
-
"""`NetworkFileSystem.new` is deprecated.
|
91
|
-
|
92
|
-
Please use `NetworkFileSystem.from_name` (for persisted) or `NetworkFileSystem.ephemeral`
|
93
|
-
(for ephemeral) network filesystems.
|
94
|
-
"""
|
95
|
-
deprecation_warning((2024, 3, 20), NetworkFileSystem.new.__doc__)
|
96
|
-
|
97
|
-
async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
|
98
|
-
status_row = resolver.add_status_row()
|
99
|
-
if existing_object_id:
|
100
|
-
# Volume already exists; do nothing.
|
101
|
-
self._hydrate(existing_object_id, resolver.client, None)
|
102
|
-
return
|
103
|
-
|
104
|
-
if cloud:
|
105
|
-
deprecation_warning((2024, 1, 17), "Argument `cloud` is deprecated (has no effect).")
|
106
|
-
|
107
|
-
status_row.message("Creating network file system...")
|
108
|
-
req = api_pb2.SharedVolumeCreateRequest(app_id=resolver.app_id)
|
109
|
-
resp = await retry_transient_errors(resolver.client.stub.SharedVolumeCreate, req)
|
110
|
-
status_row.finish("Created network file system.")
|
111
|
-
self._hydrate(resp.shared_volume_id, resolver.client, None)
|
112
|
-
|
113
|
-
return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()")
|
114
|
-
|
115
|
-
@staticmethod
|
94
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
116
95
|
def from_name(
|
117
|
-
|
96
|
+
name: str,
|
118
97
|
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
119
98
|
environment_name: Optional[str] = None,
|
120
99
|
create_if_missing: bool = False,
|
121
100
|
) -> "_NetworkFileSystem":
|
122
|
-
"""
|
101
|
+
"""Reference a NetworkFileSystem by its name, creating if necessary.
|
123
102
|
|
124
|
-
|
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.
|
125
106
|
|
126
107
|
```python notest
|
127
|
-
|
108
|
+
nfs = NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
|
128
109
|
|
129
|
-
@
|
110
|
+
@app.function(network_file_systems={"/data": nfs})
|
130
111
|
def f():
|
131
112
|
pass
|
132
113
|
```
|
133
114
|
"""
|
115
|
+
check_object_name(name, "NetworkFileSystem")
|
134
116
|
|
135
117
|
async def _load(self: _NetworkFileSystem, resolver: Resolver, existing_object_id: Optional[str]):
|
136
118
|
req = api_pb2.SharedVolumeGetOrCreateRequest(
|
137
|
-
deployment_name=
|
119
|
+
deployment_name=name,
|
138
120
|
namespace=namespace,
|
139
121
|
environment_name=_get_environment_name(environment_name, resolver),
|
140
122
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
141
123
|
)
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
133
|
+
|
134
|
+
return _NetworkFileSystem._from_loader(_load, "NetworkFileSystem()", hydrate_lazily=True)
|
146
135
|
|
147
136
|
@classmethod
|
148
137
|
@asynccontextmanager
|
149
138
|
async def ephemeral(
|
150
|
-
cls:
|
139
|
+
cls: type["_NetworkFileSystem"],
|
151
140
|
client: Optional[_Client] = None,
|
152
141
|
environment_name: Optional[str] = None,
|
153
142
|
_heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
@@ -156,11 +145,13 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
156
145
|
|
157
146
|
Usage:
|
158
147
|
```python
|
159
|
-
with NetworkFileSystem.ephemeral() as nfs:
|
160
|
-
assert nfs.listdir() == []
|
148
|
+
with modal.NetworkFileSystem.ephemeral() as nfs:
|
149
|
+
assert nfs.listdir("/") == []
|
150
|
+
```
|
161
151
|
|
162
|
-
|
163
|
-
|
152
|
+
```python notest
|
153
|
+
async with modal.NetworkFileSystem.ephemeral() as nfs:
|
154
|
+
assert await nfs.listdir("/") == []
|
164
155
|
```
|
165
156
|
"""
|
166
157
|
if client is None:
|
@@ -176,44 +167,26 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
176
167
|
yield cls._new_hydrated(response.shared_volume_id, client, None, is_another_app=True)
|
177
168
|
|
178
169
|
@staticmethod
|
179
|
-
|
180
|
-
label: str,
|
181
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
182
|
-
environment_name: Optional[str] = None,
|
183
|
-
cloud: Optional[str] = None,
|
184
|
-
) -> "_NetworkFileSystem":
|
185
|
-
"""Deprecated! Use `NetworkFileSystem.from_name(name, create_if_missing=True)`."""
|
186
|
-
deprecation_warning((2024, 3, 1), _NetworkFileSystem.persisted.__doc__)
|
187
|
-
return _NetworkFileSystem.from_name(label, namespace, environment_name, create_if_missing=True)
|
188
|
-
|
189
|
-
def persist(
|
190
|
-
self,
|
191
|
-
label: str,
|
192
|
-
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
193
|
-
environment_name: Optional[str] = None,
|
194
|
-
cloud: Optional[str] = None,
|
195
|
-
):
|
196
|
-
"""`NetworkFileSystem().persist("my-volume")` is deprecated. Use `NetworkFileSystem.from_name("my-volume", create_if_missing=True)` instead."""
|
197
|
-
deprecation_warning((2024, 2, 29), _NetworkFileSystem.persist.__doc__)
|
198
|
-
return self.persisted(label, namespace, environment_name, cloud)
|
199
|
-
|
200
|
-
@staticmethod
|
170
|
+
@renamed_parameter((2024, 12, 18), "label", "name")
|
201
171
|
async def lookup(
|
202
|
-
|
172
|
+
name: str,
|
203
173
|
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
204
174
|
client: Optional[_Client] = None,
|
205
175
|
environment_name: Optional[str] = None,
|
206
176
|
create_if_missing: bool = False,
|
207
177
|
) -> "_NetworkFileSystem":
|
208
|
-
"""Lookup a
|
178
|
+
"""Lookup a named NetworkFileSystem.
|
209
179
|
|
210
|
-
|
211
|
-
|
212
|
-
|
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("/"))
|
213
186
|
```
|
214
187
|
"""
|
215
188
|
obj = _NetworkFileSystem.from_name(
|
216
|
-
|
189
|
+
name, namespace=namespace, environment_name=environment_name, create_if_missing=create_if_missing
|
217
190
|
)
|
218
191
|
if client is None:
|
219
192
|
client = await _Client.from_env()
|
@@ -229,6 +202,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
229
202
|
environment_name: Optional[str] = None,
|
230
203
|
) -> str:
|
231
204
|
"""mdmd:hidden"""
|
205
|
+
check_object_name(deployment_name, "NetworkFileSystem")
|
232
206
|
if client is None:
|
233
207
|
client = await _Client.from_env()
|
234
208
|
request = api_pb2.SharedVolumeGetOrCreateRequest(
|
@@ -240,8 +214,15 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
240
214
|
resp = await retry_transient_errors(client.stub.SharedVolumeGetOrCreate, request)
|
241
215
|
return resp.shared_volume_id
|
242
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
|
+
|
243
224
|
@live_method
|
244
|
-
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:
|
245
226
|
"""Write from a file object to a path on the network file system, atomically.
|
246
227
|
|
247
228
|
Will create any needed parent directories automatically.
|
@@ -249,12 +230,20 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
249
230
|
If remote_path ends with `/` it's assumed to be a directory and the
|
250
231
|
file will be uploaded with its current name to that directory.
|
251
232
|
"""
|
233
|
+
progress_cb = progress_cb or (lambda *_, **__: None)
|
234
|
+
|
252
235
|
sha_hash = get_sha256_hex(fp)
|
253
236
|
fp.seek(0, os.SEEK_END)
|
254
237
|
data_size = fp.tell()
|
255
238
|
fp.seek(0)
|
256
239
|
if data_size > LARGE_FILE_LIMIT:
|
257
|
-
|
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
|
+
)
|
258
247
|
req = api_pb2.SharedVolumePutFileRequest(
|
259
248
|
shared_volume_id=self.object_id,
|
260
249
|
path=remote_path,
|
@@ -293,21 +282,25 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
293
282
|
yield data
|
294
283
|
|
295
284
|
@live_method_gen
|
296
|
-
async def iterdir(self, path: str) -> AsyncIterator[
|
285
|
+
async def iterdir(self, path: str) -> AsyncIterator[FileEntry]:
|
297
286
|
"""Iterate over all files in a directory in the network file system.
|
298
287
|
|
299
288
|
* Passing a directory path lists all files in the directory (names are relative to the directory)
|
300
289
|
* Passing a file path returns a list containing only that file's listing description
|
301
|
-
* 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)
|
302
292
|
"""
|
303
293
|
req = api_pb2.SharedVolumeListFilesRequest(shared_volume_id=self.object_id, path=path)
|
304
|
-
async for batch in
|
294
|
+
async for batch in self._client.stub.SharedVolumeListFilesStream.unary_stream(req):
|
305
295
|
for entry in batch.entries:
|
306
|
-
yield entry
|
296
|
+
yield FileEntry._from_proto(entry)
|
307
297
|
|
308
298
|
@live_method
|
309
299
|
async def add_local_file(
|
310
|
-
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,
|
311
304
|
):
|
312
305
|
local_path = Path(local_path)
|
313
306
|
if remote_path is None:
|
@@ -316,13 +309,14 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
316
309
|
remote_path = PurePosixPath(remote_path).as_posix()
|
317
310
|
|
318
311
|
with local_path.open("rb") as local_file:
|
319
|
-
return await self.write_file(remote_path, local_file)
|
312
|
+
return await self.write_file(remote_path, local_file, progress_cb=progress_cb)
|
320
313
|
|
321
314
|
@live_method
|
322
315
|
async def add_local_dir(
|
323
316
|
self,
|
324
317
|
local_path: Union[Path, str],
|
325
318
|
remote_path: Optional[Union[str, PurePosixPath, None]] = None,
|
319
|
+
progress_cb: Optional[Callable[..., Any]] = None,
|
326
320
|
):
|
327
321
|
_local_path = Path(local_path)
|
328
322
|
if remote_path is None:
|
@@ -337,17 +331,23 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
337
331
|
if subpath.is_dir():
|
338
332
|
continue
|
339
333
|
relpath_str = subpath.relative_to(_local_path).as_posix()
|
340
|
-
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)
|
341
338
|
|
342
|
-
|
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
|
343
342
|
|
344
343
|
@live_method
|
345
|
-
async def listdir(self, path: str) ->
|
344
|
+
async def listdir(self, path: str) -> list[FileEntry]:
|
346
345
|
"""List all files in a directory in the network file system.
|
347
346
|
|
348
347
|
* Passing a directory path lists all files in the directory (names are relative to the directory)
|
349
348
|
* Passing a file path returns a list containing only that file's listing description
|
350
|
-
* 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)
|
351
351
|
"""
|
352
352
|
return [entry async for entry in self.iterdir(path)]
|
353
353
|
|