modal 0.68.11__py3-none-any.whl → 0.68.14__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/_ipython.py +3 -13
- modal/_utils/blob_utils.py +19 -6
- modal/_utils/hash_utils.py +38 -9
- modal/client.pyi +2 -2
- modal/mount.py +3 -1
- modal/network_file_system.py +4 -1
- modal/volume.py +5 -1
- {modal-0.68.11.dist-info → modal-0.68.14.dist-info}/METADATA +1 -1
- {modal-0.68.11.dist-info → modal-0.68.14.dist-info}/RECORD +14 -14
- modal_version/_version_generated.py +1 -1
- {modal-0.68.11.dist-info → modal-0.68.14.dist-info}/LICENSE +0 -0
- {modal-0.68.11.dist-info → modal-0.68.14.dist-info}/WHEEL +0 -0
- {modal-0.68.11.dist-info → modal-0.68.14.dist-info}/entry_points.txt +0 -0
- {modal-0.68.11.dist-info → modal-0.68.14.dist-info}/top_level.txt +0 -0
modal/_ipython.py
CHANGED
@@ -1,21 +1,11 @@
|
|
1
1
|
# Copyright Modal Labs 2022
|
2
2
|
import sys
|
3
|
-
import warnings
|
4
|
-
|
5
|
-
ipy_outstream = None
|
6
|
-
try:
|
7
|
-
with warnings.catch_warnings():
|
8
|
-
warnings.simplefilter("ignore")
|
9
|
-
import ipykernel.iostream
|
10
|
-
|
11
|
-
ipy_outstream = ipykernel.iostream.OutStream
|
12
|
-
except ImportError:
|
13
|
-
pass
|
14
3
|
|
15
4
|
|
16
5
|
def is_notebook(stdout=None):
|
17
|
-
|
6
|
+
ipykernel_iostream = sys.modules.get("ipykernel.iostream")
|
7
|
+
if ipykernel_iostream is None:
|
18
8
|
return False
|
19
9
|
if stdout is None:
|
20
10
|
stdout = sys.stdout
|
21
|
-
return isinstance(stdout,
|
11
|
+
return isinstance(stdout, ipykernel_iostream.OutStream)
|
modal/_utils/blob_utils.py
CHANGED
@@ -21,7 +21,7 @@ from modal_proto.modal_api_grpc import ModalClientModal
|
|
21
21
|
from ..exception import ExecutionError
|
22
22
|
from .async_utils import TaskContext, retry
|
23
23
|
from .grpc_utils import retry_transient_errors
|
24
|
-
from .hash_utils import UploadHashes,
|
24
|
+
from .hash_utils import UploadHashes, get_upload_hashes
|
25
25
|
from .http_utils import ClientSessionRegistry
|
26
26
|
from .logger import logger
|
27
27
|
|
@@ -38,6 +38,11 @@ BLOB_MAX_PARALLELISM = 10
|
|
38
38
|
# read ~16MiB chunks by default
|
39
39
|
DEFAULT_SEGMENT_CHUNK_SIZE = 2**24
|
40
40
|
|
41
|
+
# Files larger than this will be multipart uploaded. The server might request multipart upload for smaller files as
|
42
|
+
# well, but the limit will never be raised.
|
43
|
+
# TODO(dano): remove this once we stop requiring md5 for blobs
|
44
|
+
MULTIPART_UPLOAD_THRESHOLD = 1024**3
|
45
|
+
|
41
46
|
|
42
47
|
class BytesIOSegmentPayload(BytesIOPayload):
|
43
48
|
"""Modified bytes payload for concurrent sends of chunks from the same file.
|
@@ -305,9 +310,13 @@ async def blob_upload(payload: bytes, stub: ModalClientModal) -> str:
|
|
305
310
|
|
306
311
|
|
307
312
|
async def blob_upload_file(
|
308
|
-
file_obj: BinaryIO,
|
313
|
+
file_obj: BinaryIO,
|
314
|
+
stub: ModalClientModal,
|
315
|
+
progress_report_cb: Optional[Callable] = None,
|
316
|
+
sha256_hex: Optional[str] = None,
|
317
|
+
md5_hex: Optional[str] = None,
|
309
318
|
) -> str:
|
310
|
-
upload_hashes = get_upload_hashes(file_obj)
|
319
|
+
upload_hashes = get_upload_hashes(file_obj, sha256_hex=sha256_hex, md5_hex=md5_hex)
|
311
320
|
return await _blob_upload(upload_hashes, file_obj, stub, progress_report_cb)
|
312
321
|
|
313
322
|
|
@@ -366,6 +375,7 @@ class FileUploadSpec:
|
|
366
375
|
use_blob: bool
|
367
376
|
content: Optional[bytes] # typically None if using blob, required otherwise
|
368
377
|
sha256_hex: str
|
378
|
+
md5_hex: str
|
369
379
|
mode: int # file permission bits (last 12 bits of st_mode)
|
370
380
|
size: int
|
371
381
|
|
@@ -383,13 +393,15 @@ def _get_file_upload_spec(
|
|
383
393
|
fp.seek(0)
|
384
394
|
|
385
395
|
if size >= LARGE_FILE_LIMIT:
|
396
|
+
# TODO(dano): remove the placeholder md5 once we stop requiring md5 for blobs
|
397
|
+
md5_hex = "baadbaadbaadbaadbaadbaadbaadbaad" if size > MULTIPART_UPLOAD_THRESHOLD else None
|
386
398
|
use_blob = True
|
387
399
|
content = None
|
388
|
-
|
400
|
+
hashes = get_upload_hashes(fp, md5_hex=md5_hex)
|
389
401
|
else:
|
390
402
|
use_blob = False
|
391
403
|
content = fp.read()
|
392
|
-
|
404
|
+
hashes = get_upload_hashes(content)
|
393
405
|
|
394
406
|
return FileUploadSpec(
|
395
407
|
source=source,
|
@@ -397,7 +409,8 @@ def _get_file_upload_spec(
|
|
397
409
|
mount_filename=mount_filename.as_posix(),
|
398
410
|
use_blob=use_blob,
|
399
411
|
content=content,
|
400
|
-
sha256_hex=sha256_hex,
|
412
|
+
sha256_hex=hashes.sha256_hex(),
|
413
|
+
md5_hex=hashes.md5_hex(),
|
401
414
|
mode=mode & 0o7777,
|
402
415
|
size=size,
|
403
416
|
)
|
modal/_utils/hash_utils.py
CHANGED
@@ -3,14 +3,14 @@ import base64
|
|
3
3
|
import dataclasses
|
4
4
|
import hashlib
|
5
5
|
import time
|
6
|
-
from typing import BinaryIO, Callable, Union
|
6
|
+
from typing import BinaryIO, Callable, Optional, Sequence, Union
|
7
7
|
|
8
8
|
from modal.config import logger
|
9
9
|
|
10
10
|
HASH_CHUNK_SIZE = 65536
|
11
11
|
|
12
12
|
|
13
|
-
def _update(hashers:
|
13
|
+
def _update(hashers: Sequence[Callable[[bytes], None]], data: Union[bytes, BinaryIO]) -> None:
|
14
14
|
if isinstance(data, bytes):
|
15
15
|
for hasher in hashers:
|
16
16
|
hasher(data)
|
@@ -57,15 +57,44 @@ class UploadHashes:
|
|
57
57
|
md5_base64: str
|
58
58
|
sha256_base64: str
|
59
59
|
|
60
|
+
def md5_hex(self) -> str:
|
61
|
+
return base64.b64decode(self.md5_base64).hex()
|
60
62
|
|
61
|
-
def
|
63
|
+
def sha256_hex(self) -> str:
|
64
|
+
return base64.b64decode(self.sha256_base64).hex()
|
65
|
+
|
66
|
+
|
67
|
+
def get_upload_hashes(
|
68
|
+
data: Union[bytes, BinaryIO], sha256_hex: Optional[str] = None, md5_hex: Optional[str] = None
|
69
|
+
) -> UploadHashes:
|
62
70
|
t0 = time.monotonic()
|
63
|
-
|
64
|
-
|
65
|
-
|
71
|
+
hashers = {}
|
72
|
+
|
73
|
+
if not sha256_hex:
|
74
|
+
sha256 = hashlib.sha256()
|
75
|
+
hashers["sha256"] = sha256
|
76
|
+
if not md5_hex:
|
77
|
+
md5 = hashlib.md5()
|
78
|
+
hashers["md5"] = md5
|
79
|
+
|
80
|
+
if hashers:
|
81
|
+
updaters = [h.update for h in hashers.values()]
|
82
|
+
_update(updaters, data)
|
83
|
+
|
84
|
+
if sha256_hex:
|
85
|
+
sha256_base64 = base64.b64encode(bytes.fromhex(sha256_hex)).decode("ascii")
|
86
|
+
else:
|
87
|
+
sha256_base64 = base64.b64encode(hashers["sha256"].digest()).decode("ascii")
|
88
|
+
|
89
|
+
if md5_hex:
|
90
|
+
md5_base64 = base64.b64encode(bytes.fromhex(md5_hex)).decode("ascii")
|
91
|
+
else:
|
92
|
+
md5_base64 = base64.b64encode(hashers["md5"].digest()).decode("ascii")
|
93
|
+
|
66
94
|
hashes = UploadHashes(
|
67
|
-
md5_base64=
|
68
|
-
sha256_base64=
|
95
|
+
md5_base64=md5_base64,
|
96
|
+
sha256_base64=sha256_base64,
|
69
97
|
)
|
70
|
-
|
98
|
+
|
99
|
+
logger.debug("get_upload_hashes took %.3fs (%s)", time.monotonic() - t0, hashers.keys())
|
71
100
|
return hashes
|
modal/client.pyi
CHANGED
@@ -26,7 +26,7 @@ class _Client:
|
|
26
26
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
27
|
|
28
28
|
def __init__(
|
29
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.14"
|
30
30
|
): ...
|
31
31
|
def is_closed(self) -> bool: ...
|
32
32
|
@property
|
@@ -81,7 +81,7 @@ class Client:
|
|
81
81
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
82
|
|
83
83
|
def __init__(
|
84
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.14"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/mount.py
CHANGED
@@ -483,7 +483,9 @@ class _Mount(_Object, type_prefix="mo"):
|
|
483
483
|
logger.debug(f"Creating blob file for {file_spec.source_description} ({file_spec.size} bytes)")
|
484
484
|
async with blob_upload_concurrency:
|
485
485
|
with file_spec.source() as fp:
|
486
|
-
blob_id = await blob_upload_file(
|
486
|
+
blob_id = await blob_upload_file(
|
487
|
+
fp, resolver.client.stub, sha256_hex=file_spec.sha256_hex, md5_hex=file_spec.md5_hex
|
488
|
+
)
|
487
489
|
logger.debug(f"Uploading blob file {file_spec.source_description} as {remote_filename}")
|
488
490
|
request2 = api_pb2.MountPutFileRequest(data_blob_id=blob_id, sha256_hex=file_spec.sha256_hex)
|
489
491
|
else:
|
modal/network_file_system.py
CHANGED
@@ -245,7 +245,10 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
245
245
|
if data_size > LARGE_FILE_LIMIT:
|
246
246
|
progress_task_id = progress_cb(name=remote_path, size=data_size)
|
247
247
|
blob_id = await blob_upload_file(
|
248
|
-
fp,
|
248
|
+
fp,
|
249
|
+
self._client.stub,
|
250
|
+
progress_report_cb=functools.partial(progress_cb, progress_task_id),
|
251
|
+
sha256_hex=sha_hash,
|
249
252
|
)
|
250
253
|
req = api_pb2.SharedVolumePutFileRequest(
|
251
254
|
shared_volume_id=self.object_id,
|
modal/volume.py
CHANGED
@@ -632,7 +632,11 @@ class _VolumeUploadContextManager:
|
|
632
632
|
logger.debug(f"Creating blob file for {file_spec.source_description} ({file_spec.size} bytes)")
|
633
633
|
with file_spec.source() as fp:
|
634
634
|
blob_id = await blob_upload_file(
|
635
|
-
fp,
|
635
|
+
fp,
|
636
|
+
self._client.stub,
|
637
|
+
functools.partial(self._progress_cb, progress_task_id),
|
638
|
+
sha256_hex=file_spec.sha256_hex,
|
639
|
+
md5_hex=file_spec.md5_hex,
|
636
640
|
)
|
637
641
|
logger.debug(f"Uploading blob file {file_spec.source_description} as {remote_filename}")
|
638
642
|
request2 = api_pb2.MountPutFileRequest(data_blob_id=blob_id, sha256_hex=file_spec.sha256_hex)
|
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=scYhGFqh8OJcVDo-VOxIT6CCwxOgzgflYWMnIZiMRqE,2871
|
|
3
3
|
modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
|
4
4
|
modal/_clustered_functions.pyi,sha256=vllkegc99A0jrUOWa8mdlSbdp6uz36TsHhGxysAOpaQ,771
|
5
5
|
modal/_container_entrypoint.py,sha256=wk10vA5vRZZsVwQ0yINOLd0i-NwH7x6XbhTslumvGjo,28910
|
6
|
-
modal/_ipython.py,sha256=
|
6
|
+
modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
|
7
7
|
modal/_location.py,sha256=S3lSxIU3h9HkWpkJ3Pwo0pqjIOSB1fjeSgUsY3x7eec,1202
|
8
8
|
modal/_output.py,sha256=0fWX_KQwhER--U81ys16CL-pA5A-LN20C0EZjElKGJQ,25410
|
9
9
|
modal/_proxy_tunnel.py,sha256=gnKyCfmVB7x2d1A6c-JDysNIP3kEFxmXzhcXhPrzPn0,1906
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
|
|
19
19
|
modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=nyPjfromWBeOyurexpFP2QLQNk822RPggMCLyX9j1jA,15247
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=4x5j0yBlE_sZf8cS2eChs-pYuxr-_SsRiKrf1fSJmfA,7280
|
23
23
|
modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
|
25
25
|
modal/cls.py,sha256=ONnrfZ2vPcaY2JuKypPiBA9eTiyg8Qfg-Ull40nn9zs,30956
|
@@ -42,9 +42,9 @@ modal/image.py,sha256=cQ6WP1xHXZT_nY8z3aEFiGwKzrTV0yxi3Ab8JzF91eo,79653
|
|
42
42
|
modal/image.pyi,sha256=PIKH6JBA4L5TfdJrQu3pm2ykyIITmiP920TpP8cdyQA,24585
|
43
43
|
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
44
44
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
45
|
-
modal/mount.py,sha256=
|
45
|
+
modal/mount.py,sha256=7FJrS-QkRJGndKuvRnMz452wfUcbLpd_UEnmFgQCKQQ,27770
|
46
46
|
modal/mount.pyi,sha256=3e4nkXUeeVmUmOyK8Tiyk_EQlHeWruN3yGJVnmDUVrI,9761
|
47
|
-
modal/network_file_system.py,sha256=
|
47
|
+
modal/network_file_system.py,sha256=kwwQLCJVO086FTiAWSF_jz9BkqijZLpSbEYXpFvS0Ik,14600
|
48
48
|
modal/network_file_system.pyi,sha256=8mHKXuRkxHPazF6ljIW7g4M5aVqLSl6eKUPLgDCug5c,7901
|
49
49
|
modal/object.py,sha256=HZs3N59C6JxlMuPQWJYvrWV1FEEkH9txUovVDorVUbs,9763
|
50
50
|
modal/object.pyi,sha256=MO78H9yFSE5i1gExPEwyyQzLdlshkcGHN1aQ0ylyvq0,8802
|
@@ -73,7 +73,7 @@ modal/serving.pyi,sha256=ncV-9jY_vZYFnGs5ZnMb3ffrX8LmcLdIMHBC56xRbtE,1711
|
|
73
73
|
modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
|
74
74
|
modal/token_flow.py,sha256=LcgSce_MSQ2p7j55DPwpVRpiAtCDe8GRSEwzO7muNR8,6774
|
75
75
|
modal/token_flow.pyi,sha256=gOYtYujrWt_JFZeiI8EmfahXPx5GCR5Na-VaPQcWgEY,1937
|
76
|
-
modal/volume.py,sha256=
|
76
|
+
modal/volume.py,sha256=PGzbninvRU-IhSwJgM2jZKzD8llRhZhadsOxZ-YNwaM,29316
|
77
77
|
modal/volume.pyi,sha256=St0mDiaojfep6Bs4sBbkRJmeacYHF6lh6FKOWGmheHA,11182
|
78
78
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
79
79
|
modal/_runtime/asgi.py,sha256=GvuxZqWnIHMIR-Bx5f7toCQlkERaJO8CHjTPNM9IFIw,21537
|
@@ -84,11 +84,11 @@ modal/_runtime/user_code_imports.py,sha256=4fI0F9OIaNOcO_S4Tx2JcnYwZwZq6JdhMAkUz
|
|
84
84
|
modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
|
85
85
|
modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
|
86
86
|
modal/_utils/async_utils.py,sha256=9ubwMkwiDB4gzOYG2jL9j7Fs-5dxHjcifZe3r7JRg-k,25091
|
87
|
-
modal/_utils/blob_utils.py,sha256=
|
87
|
+
modal/_utils/blob_utils.py,sha256=1D_dXspFdsVxkW3gsYH-wJUUHiCpYvlwhmCgYZZgN9k,17237
|
88
88
|
modal/_utils/function_utils.py,sha256=LgcveUUb4XU_dWxtqgK_3ujZBvS3cGVzcDOkljyFZ2w,25066
|
89
89
|
modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
|
90
90
|
modal/_utils/grpc_utils.py,sha256=PPB5ay-vXencXNIWPVw5modr3EH7gfq2QPcO5YJ1lMU,7737
|
91
|
-
modal/_utils/hash_utils.py,sha256=
|
91
|
+
modal/_utils/hash_utils.py,sha256=zg3J6OGxTFGSFri1qQ12giDz90lWk8bzaxCTUCRtiX4,3034
|
92
92
|
modal/_utils/http_utils.py,sha256=VKXYNPJtrSwZ1ttcXVGQUWmn8cLAXiOTv05g2ac3GbU,2179
|
93
93
|
modal/_utils/logger.py,sha256=ePzdudrtx9jJCjuO6-bcL_kwUJfi4AwloUmIiNtqkY0,1330
|
94
94
|
modal/_utils/mount_utils.py,sha256=J-FRZbPQv1i__Tob-FIpbB1oXWpFLAwZiB4OCiJpFG0,3206
|
@@ -161,10 +161,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
161
161
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
162
162
|
modal_version/__init__.py,sha256=RT6zPoOdFO99u5Wcxxaoir4ZCuPTbQ22cvzFAXl3vUY,470
|
163
163
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
164
|
-
modal_version/_version_generated.py,sha256=
|
165
|
-
modal-0.68.
|
166
|
-
modal-0.68.
|
167
|
-
modal-0.68.
|
168
|
-
modal-0.68.
|
169
|
-
modal-0.68.
|
170
|
-
modal-0.68.
|
164
|
+
modal_version/_version_generated.py,sha256=SWqJPFBaGp3gDChTb6Zx02oI_iQyIRtKpGgcw69Yegs,149
|
165
|
+
modal-0.68.14.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
166
|
+
modal-0.68.14.dist-info/METADATA,sha256=gHSGyvKb5gQiL1YbO3mRis3T8En0t19ug9mfOY4HYos,2329
|
167
|
+
modal-0.68.14.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
168
|
+
modal-0.68.14.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
169
|
+
modal-0.68.14.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
170
|
+
modal-0.68.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|