modal 0.74.58__py3-none-any.whl → 0.74.59__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/cli/_download.py +17 -7
- modal/client.pyi +2 -2
- modal/volume.py +90 -0
- modal/volume.pyi +50 -0
- {modal-0.74.58.dist-info → modal-0.74.59.dist-info}/METADATA +1 -1
- {modal-0.74.58.dist-info → modal-0.74.59.dist-info}/RECORD +11 -11
- modal_version/_version_generated.py +1 -1
- {modal-0.74.58.dist-info → modal-0.74.59.dist-info}/WHEEL +0 -0
- {modal-0.74.58.dist-info → modal-0.74.59.dist-info}/entry_points.txt +0 -0
- {modal-0.74.58.dist-info → modal-0.74.59.dist-info}/licenses/LICENSE +0 -0
- {modal-0.74.58.dist-info → modal-0.74.59.dist-info}/top_level.txt +0 -0
modal/cli/_download.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2023
|
2
2
|
import asyncio
|
3
|
+
import functools
|
3
4
|
import os
|
4
5
|
import shutil
|
5
6
|
import sys
|
@@ -70,21 +71,30 @@ async def _volume_download(
|
|
70
71
|
if is_pipe:
|
71
72
|
if entry.type == FileEntryType.FILE:
|
72
73
|
progress_task_id = progress_cb(name=entry.path, size=entry.size)
|
74
|
+
file_progress_cb = functools.partial(progress_cb, task_id=progress_task_id)
|
75
|
+
|
73
76
|
async for chunk in volume.read_file(entry.path):
|
74
77
|
sys.stdout.buffer.write(chunk)
|
75
|
-
|
76
|
-
|
78
|
+
file_progress_cb(advance=len(chunk))
|
79
|
+
|
80
|
+
file_progress_cb(complete=True)
|
77
81
|
else:
|
78
82
|
if entry.type == FileEntryType.FILE:
|
79
83
|
progress_task_id = progress_cb(name=entry.path, size=entry.size)
|
80
84
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
85
|
+
file_progress_cb = functools.partial(progress_cb, task_id=progress_task_id)
|
86
|
+
|
81
87
|
with output_path.open("wb") as fp:
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
if isinstance(volume, _Volume):
|
89
|
+
b = await volume.read_file_into_fileobj(entry.path, fp, file_progress_cb)
|
90
|
+
else:
|
91
|
+
b = 0
|
92
|
+
async for chunk in volume.read_file(entry.path):
|
93
|
+
b += fp.write(chunk)
|
94
|
+
file_progress_cb(advance=len(chunk))
|
95
|
+
|
86
96
|
logger.debug(f"Wrote {b} bytes to {output_path}")
|
87
|
-
|
97
|
+
file_progress_cb(complete=True)
|
88
98
|
elif entry.type == FileEntryType.DIRECTORY:
|
89
99
|
output_path.mkdir(parents=True, exist_ok=True)
|
90
100
|
finally:
|
modal/client.pyi
CHANGED
@@ -27,7 +27,7 @@ class _Client:
|
|
27
27
|
_snapshotted: bool
|
28
28
|
|
29
29
|
def __init__(
|
30
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.
|
30
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.59"
|
31
31
|
): ...
|
32
32
|
def is_closed(self) -> bool: ...
|
33
33
|
@property
|
@@ -86,7 +86,7 @@ class Client:
|
|
86
86
|
_snapshotted: bool
|
87
87
|
|
88
88
|
def __init__(
|
89
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.
|
89
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.59"
|
90
90
|
): ...
|
91
91
|
def is_closed(self) -> bool: ...
|
92
92
|
@property
|
modal/volume.py
CHANGED
@@ -463,6 +463,96 @@ class _Volume(_Object, type_prefix="vo"):
|
|
463
463
|
yield value
|
464
464
|
|
465
465
|
|
466
|
+
@live_method
|
467
|
+
async def read_file_into_fileobj(
|
468
|
+
self,
|
469
|
+
path: str,
|
470
|
+
fileobj: typing.IO[bytes],
|
471
|
+
progress_cb: Optional[Callable[..., Any]] = None
|
472
|
+
) -> int:
|
473
|
+
"""mdmd:hidden
|
474
|
+
Read volume file into file-like IO object.
|
475
|
+
"""
|
476
|
+
if progress_cb is None:
|
477
|
+
def progress_cb(*_, **__):
|
478
|
+
pass
|
479
|
+
|
480
|
+
if self._is_v1:
|
481
|
+
return await self._read_file_into_fileobj1(path, fileobj, progress_cb)
|
482
|
+
else:
|
483
|
+
return await self._read_file_into_fileobj2(path, fileobj, progress_cb)
|
484
|
+
|
485
|
+
|
486
|
+
async def _read_file_into_fileobj1(
|
487
|
+
self,
|
488
|
+
path: str,
|
489
|
+
fileobj: typing.IO[bytes],
|
490
|
+
progress_cb: Callable[..., Any]
|
491
|
+
) -> int:
|
492
|
+
num_bytes_written = 0
|
493
|
+
|
494
|
+
async for chunk in self._read_file1(path):
|
495
|
+
num_chunk_bytes_written = 0
|
496
|
+
|
497
|
+
while num_chunk_bytes_written < len(chunk):
|
498
|
+
# TODO(dflemstr): this is a small write, but nonetheless might block the event loop for some time:
|
499
|
+
n = fileobj.write(chunk)
|
500
|
+
num_chunk_bytes_written += n
|
501
|
+
progress_cb(advance=n)
|
502
|
+
|
503
|
+
num_bytes_written += len(chunk)
|
504
|
+
|
505
|
+
return num_bytes_written
|
506
|
+
|
507
|
+
|
508
|
+
async def _read_file_into_fileobj2(
|
509
|
+
self,
|
510
|
+
path: str,
|
511
|
+
fileobj: typing.IO[bytes],
|
512
|
+
progress_cb: Callable[..., Any]
|
513
|
+
) -> int:
|
514
|
+
req = api_pb2.VolumeGetFile2Request(volume_id=self.object_id, path=path)
|
515
|
+
|
516
|
+
try:
|
517
|
+
response = await retry_transient_errors(self._client.stub.VolumeGetFile2, req)
|
518
|
+
except GRPCError as exc:
|
519
|
+
raise FileNotFoundError(exc.message) if exc.status == Status.NOT_FOUND else exc
|
520
|
+
|
521
|
+
# TODO(dflemstr): Sane default limit? Make configurable?
|
522
|
+
download_semaphore = asyncio.Semaphore(multiprocessing.cpu_count())
|
523
|
+
write_lock = asyncio.Lock()
|
524
|
+
start_pos = fileobj.tell()
|
525
|
+
|
526
|
+
async def download_block(idx, url) -> int:
|
527
|
+
block_start_pos = start_pos + idx * BLOCK_SIZE
|
528
|
+
num_bytes_written = 0
|
529
|
+
|
530
|
+
async with download_semaphore, ClientSessionRegistry.get_session().get(url) as get_response:
|
531
|
+
async for chunk in get_response.content:
|
532
|
+
num_chunk_bytes_written = 0
|
533
|
+
|
534
|
+
while num_chunk_bytes_written < len(chunk):
|
535
|
+
async with write_lock:
|
536
|
+
fileobj.seek(block_start_pos + num_bytes_written + num_chunk_bytes_written)
|
537
|
+
# TODO(dflemstr): this is a small write, but nonetheless might block the event loop for some
|
538
|
+
# time:
|
539
|
+
n = fileobj.write(chunk)
|
540
|
+
|
541
|
+
num_chunk_bytes_written += n
|
542
|
+
progress_cb(advance=n)
|
543
|
+
|
544
|
+
num_bytes_written += len(chunk)
|
545
|
+
|
546
|
+
return num_bytes_written
|
547
|
+
|
548
|
+
coros = [download_block(idx, url) for idx, url in enumerate(response.get_urls)]
|
549
|
+
|
550
|
+
total_size = sum(await asyncio.gather(*coros))
|
551
|
+
fileobj.seek(start_pos + total_size)
|
552
|
+
|
553
|
+
return total_size
|
554
|
+
|
555
|
+
|
466
556
|
@live_method
|
467
557
|
async def remove_file(self, path: str, recursive: bool = False) -> None:
|
468
558
|
"""Remove a file or directory from a volume."""
|
modal/volume.pyi
CHANGED
@@ -87,6 +87,18 @@ class _Volume(modal._object._Object):
|
|
87
87
|
def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
88
88
|
def _read_file1(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
89
89
|
def _read_file2(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
|
90
|
+
async def read_file_into_fileobj(
|
91
|
+
self,
|
92
|
+
path: str,
|
93
|
+
fileobj: typing.IO[bytes],
|
94
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
95
|
+
) -> int: ...
|
96
|
+
async def _read_file_into_fileobj1(
|
97
|
+
self, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
|
98
|
+
) -> int: ...
|
99
|
+
async def _read_file_into_fileobj2(
|
100
|
+
self, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
|
101
|
+
) -> int: ...
|
90
102
|
async def remove_file(self, path: str, recursive: bool = False) -> None: ...
|
91
103
|
async def copy_files(self, src_paths: collections.abc.Sequence[str], dst_path: str) -> None: ...
|
92
104
|
async def batch_upload(self, force: bool = False) -> _AbstractVolumeUploadContextManager: ...
|
@@ -234,6 +246,44 @@ class Volume(modal.object.Object):
|
|
234
246
|
|
235
247
|
_read_file2: ___read_file2_spec[typing_extensions.Self]
|
236
248
|
|
249
|
+
class __read_file_into_fileobj_spec(typing_extensions.Protocol[SUPERSELF]):
|
250
|
+
def __call__(
|
251
|
+
self,
|
252
|
+
/,
|
253
|
+
path: str,
|
254
|
+
fileobj: typing.IO[bytes],
|
255
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
256
|
+
) -> int: ...
|
257
|
+
async def aio(
|
258
|
+
self,
|
259
|
+
/,
|
260
|
+
path: str,
|
261
|
+
fileobj: typing.IO[bytes],
|
262
|
+
progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
|
263
|
+
) -> int: ...
|
264
|
+
|
265
|
+
read_file_into_fileobj: __read_file_into_fileobj_spec[typing_extensions.Self]
|
266
|
+
|
267
|
+
class ___read_file_into_fileobj1_spec(typing_extensions.Protocol[SUPERSELF]):
|
268
|
+
def __call__(
|
269
|
+
self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
|
270
|
+
) -> int: ...
|
271
|
+
async def aio(
|
272
|
+
self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
|
273
|
+
) -> int: ...
|
274
|
+
|
275
|
+
_read_file_into_fileobj1: ___read_file_into_fileobj1_spec[typing_extensions.Self]
|
276
|
+
|
277
|
+
class ___read_file_into_fileobj2_spec(typing_extensions.Protocol[SUPERSELF]):
|
278
|
+
def __call__(
|
279
|
+
self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
|
280
|
+
) -> int: ...
|
281
|
+
async def aio(
|
282
|
+
self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
|
283
|
+
) -> int: ...
|
284
|
+
|
285
|
+
_read_file_into_fileobj2: ___read_file_into_fileobj2_spec[typing_extensions.Self]
|
286
|
+
|
237
287
|
class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
|
238
288
|
def __call__(self, /, path: str, recursive: bool = False) -> None: ...
|
239
289
|
async def aio(self, /, path: str, recursive: bool = False) -> None: ...
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=xojuGZv4LaQwZU5ntj7WbmMjeNuB9Gll8Mzqe2LyiEs,51323
|
|
22
22
|
modal/app.pyi,sha256=zNwR1_2LpmQc9AhemuAeVdk90XNYDw9keOkXAwAATeA,28732
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
24
24
|
modal/client.py,sha256=o-aQThHpvDHUzg_kUafyhWzACViUBhY2WLZ2EitnSHA,16787
|
25
|
-
modal/client.pyi,sha256=
|
25
|
+
modal/client.pyi,sha256=DHS5JgIcHrXAE9ADaeYMxYw_IcuSVQbDo8oaRZly784,8385
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
|
28
28
|
modal/cls.py,sha256=aHoMEWMZUN7bOezs3tRPxzS1FP3gTxZBORVjbPmtxyg,35338
|
@@ -78,8 +78,8 @@ modal/snapshot.pyi,sha256=dIEBdTPb7O3VwkQ8TMPjfyU17RLuS9i0DnACxxHy8X4,676
|
|
78
78
|
modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
|
79
79
|
modal/token_flow.py,sha256=0_4KabXKsuE4OXTJ1OuLOtA-b1sesShztMZkkRFK7tA,7605
|
80
80
|
modal/token_flow.pyi,sha256=ILbRv6JsZq-jK8jcJM7eB74e0PsbzwBm7hyPcV9lBlQ,2121
|
81
|
-
modal/volume.py,sha256=
|
82
|
-
modal/volume.pyi,sha256
|
81
|
+
modal/volume.py,sha256=DFkdjipvjmWvedd1FL_CjIwyfT7QN-VxusJ9O3fVCw4,44206
|
82
|
+
modal/volume.pyi,sha256=9hPIMRBzGZycVL8uRfGpjSmNu_pCbkGAOyrnE86bU2Y,21113
|
83
83
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
84
84
|
modal/_runtime/asgi.py,sha256=_2xSTsDD27Cit7xnMs4lzkJA2wzer2_N4Oa3BkXFzVA,22521
|
85
85
|
modal/_runtime/container_io_manager.py,sha256=6j0jO2-s9ShckM4SK45OapoQxWW9HQwQjFaBkXPJPwU,44763
|
@@ -115,7 +115,7 @@ modal/_vendor/a2wsgi_wsgi.py,sha256=Q1AsjpV_Q_vzQsz_cSqmP9jWzsGsB-ARFU6vpQYml8k,
|
|
115
115
|
modal/_vendor/cloudpickle.py,sha256=avxOIgNKqL9KyPNuIOVQzBm0D1l9ipeB4RrcUMUGmeQ,55216
|
116
116
|
modal/_vendor/tblib.py,sha256=g1O7QUDd3sDoLd8YPFltkXkih7r_fyZOjgmGuligv3s,9722
|
117
117
|
modal/cli/__init__.py,sha256=6FRleWQxBDT19y7OayO4lBOzuL6Bs9r0rLINYYYbHwQ,769
|
118
|
-
modal/cli/_download.py,sha256=
|
118
|
+
modal/cli/_download.py,sha256=tV8JFkncTtQKh85bSguQg6AW5aRRlynf-rvyN7ruigc,4337
|
119
119
|
modal/cli/_traceback.py,sha256=4ywtmFcmPnY3tqb4-3fA061N2tRiM01xs8fSagtkwhE,7293
|
120
120
|
modal/cli/app.py,sha256=87LWg3bTQQIHFOqs8iiJYD_X03omXBZ6lFYR0rMJV-I,8433
|
121
121
|
modal/cli/cluster.py,sha256=EBDhkzfOtPSbwknYdYPBGYvRAwl4Gm7OJkD6_zxrcus,3106
|
@@ -146,7 +146,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
|
|
146
146
|
modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
|
147
147
|
modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
|
148
148
|
modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
|
149
|
-
modal-0.74.
|
149
|
+
modal-0.74.59.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
150
150
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
151
151
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
152
152
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
@@ -171,9 +171,9 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
171
171
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
172
172
|
modal_version/__init__.py,sha256=m94xZNWIjH8oUtJk4l9xfovzDJede2o7X-q0MHVECtM,470
|
173
173
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
174
|
-
modal_version/_version_generated.py,sha256=
|
175
|
-
modal-0.74.
|
176
|
-
modal-0.74.
|
177
|
-
modal-0.74.
|
178
|
-
modal-0.74.
|
179
|
-
modal-0.74.
|
174
|
+
modal_version/_version_generated.py,sha256=gO9v_hSDkfPZyxi6-dUheFZ5pAFiRJ0MvryhC8RNw8w,149
|
175
|
+
modal-0.74.59.dist-info/METADATA,sha256=FjOM1ctCE-exDej2RbPnPh3Pcm5HjkV4M7lgk8vFjBY,2451
|
176
|
+
modal-0.74.59.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
177
|
+
modal-0.74.59.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
178
|
+
modal-0.74.59.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
179
|
+
modal-0.74.59.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|