modal 0.74.49__py3-none-any.whl → 0.74.51__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/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.49"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.51"
31
31
  ): ...
32
32
  def is_closed(self) -> bool: ...
33
33
  @property
@@ -85,7 +85,7 @@ class Client:
85
85
  _snapshotted: bool
86
86
 
87
87
  def __init__(
88
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.49"
88
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.74.51"
89
89
  ): ...
90
90
  def is_closed(self) -> bool: ...
91
91
  @property
modal/volume.py CHANGED
@@ -14,7 +14,6 @@ from dataclasses import dataclass
14
14
  from io import BytesIO
15
15
  from pathlib import Path, PurePosixPath
16
16
  from typing import (
17
- IO,
18
17
  Any,
19
18
  Awaitable,
20
19
  BinaryIO,
@@ -195,6 +194,16 @@ class _Volume(_Object, type_prefix="vo"):
195
194
  def _get_metadata(self) -> Optional[Message]:
196
195
  return self._metadata
197
196
 
197
+
198
+ @property
199
+ def _is_v1(self) -> bool:
200
+ return self._metadata.version in [
201
+ None,
202
+ api_pb2.VolumeFsVersion.VOLUME_FS_VERSION_UNSPECIFIED,
203
+ api_pb2.VolumeFsVersion.VOLUME_FS_VERSION_V1
204
+ ]
205
+
206
+
198
207
  @classmethod
199
208
  @asynccontextmanager
200
209
  async def ephemeral(
@@ -391,7 +400,7 @@ class _Volume(_Object, type_prefix="vo"):
391
400
  return [entry async for entry in self.iterdir(path, recursive=recursive)]
392
401
 
393
402
  @live_method_gen
394
- async def read_file(self, path: str) -> AsyncIterator[bytes]:
403
+ def read_file(self, path: str) -> AsyncIterator[bytes]:
395
404
  """
396
405
  Read a file from the modal.Volume.
397
406
 
@@ -405,6 +414,10 @@ class _Volume(_Object, type_prefix="vo"):
405
414
  print(len(data)) # == 1024 * 1024
406
415
  ```
407
416
  """
417
+ return self._read_file1(path) if self._is_v1 else self._read_file2(path)
418
+
419
+
420
+ async def _read_file1(self, path: str) -> AsyncIterator[bytes]:
408
421
  req = api_pb2.VolumeGetFileRequest(volume_id=self.object_id, path=path)
409
422
  try:
410
423
  response = await retry_transient_errors(self._client.stub.VolumeGetFile, req)
@@ -418,53 +431,20 @@ class _Volume(_Object, type_prefix="vo"):
418
431
  async for data in blob_iter(response.data_blob_id, self._client.stub):
419
432
  yield data
420
433
 
421
- @live_method
422
- async def read_file_into_fileobj(self, path: str, fileobj: IO[bytes]) -> int:
423
- """mdmd:hidden
424
434
 
425
- Read volume file into file-like IO object.
426
- In the future, this will replace the current generator implementation of the `read_file` method.
427
- """
435
+ async def _read_file2(self, path: str) -> AsyncIterator[bytes]:
436
+ req = api_pb2.VolumeGetFile2Request(volume_id=self.object_id, path=path)
428
437
 
429
- chunk_size_bytes = 8 * 1024 * 1024
430
- start = 0
431
- req = api_pb2.VolumeGetFileRequest(volume_id=self.object_id, path=path, start=start, len=chunk_size_bytes)
432
438
  try:
433
- response = await retry_transient_errors(self._client.stub.VolumeGetFile, req)
439
+ response = await retry_transient_errors(self._client.stub.VolumeGetFile2, req)
434
440
  except GRPCError as exc:
435
441
  raise FileNotFoundError(exc.message) if exc.status == Status.NOT_FOUND else exc
436
- if response.WhichOneof("data_oneof") != "data":
437
- raise RuntimeError("expected to receive 'data' in response")
438
-
439
- n = fileobj.write(response.data)
440
- if n != len(response.data):
441
- raise OSError(f"failed to write {len(response.data)} bytes to output. Wrote {n}.")
442
- elif n == response.size:
443
- return response.size
444
- elif n > response.size:
445
- raise RuntimeError(f"length of returned data exceeds reported filesize: {n} > {response.size}")
446
- # else: there's more data to read. continue reading with further ranged GET requests.
447
- file_size = response.size
448
- written = n
449
-
450
- while True:
451
- req = api_pb2.VolumeGetFileRequest(volume_id=self.object_id, path=path, start=written, len=chunk_size_bytes)
452
- response = await retry_transient_errors(self._client.stub.VolumeGetFile, req)
453
- if response.WhichOneof("data_oneof") != "data":
454
- raise RuntimeError("expected to receive 'data' in response")
455
- if len(response.data) > chunk_size_bytes:
456
- raise RuntimeError(f"received more data than requested: {len(response.data)} > {chunk_size_bytes}")
457
- elif (written + len(response.data)) > file_size:
458
- raise RuntimeError(f"received data exceeds filesize of {chunk_size_bytes}")
459
-
460
- n = fileobj.write(response.data)
461
- if n != len(response.data):
462
- raise OSError(f"failed to write {len(response.data)} bytes to output. Wrote {n}.")
463
- written += n
464
- if written == file_size:
465
- break
466
442
 
467
- return written
443
+ for url in response.get_urls:
444
+ async with ClientSessionRegistry.get_session().get(url) as get_response:
445
+ async for data in get_response.content.iter_any():
446
+ yield data
447
+
468
448
 
469
449
  @live_method
470
450
  async def remove_file(self, path: str, recursive: bool = False) -> None:
modal/volume.pyi CHANGED
@@ -52,6 +52,8 @@ class _Volume(modal._object._Object):
52
52
  ) -> _Volume: ...
53
53
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
54
54
  def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
55
+ @property
56
+ def _is_v1(self) -> bool: ...
55
57
  @classmethod
56
58
  def ephemeral(
57
59
  cls: type[_Volume],
@@ -83,7 +85,8 @@ class _Volume(modal._object._Object):
83
85
  def iterdir(self, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]: ...
84
86
  async def listdir(self, path: str, *, recursive: bool = False) -> list[FileEntry]: ...
85
87
  def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
86
- async def read_file_into_fileobj(self, path: str, fileobj: typing.IO[bytes]) -> int: ...
88
+ def _read_file1(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
89
+ def _read_file2(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
87
90
  async def remove_file(self, path: str, recursive: bool = False) -> None: ...
88
91
  async def copy_files(self, src_paths: collections.abc.Sequence[str], dst_path: str) -> None: ...
89
92
  async def batch_upload(self, force: bool = False) -> _AbstractVolumeUploadContextManager: ...
@@ -126,6 +129,8 @@ class Volume(modal.object.Object):
126
129
  ) -> Volume: ...
127
130
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
128
131
  def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
132
+ @property
133
+ def _is_v1(self) -> bool: ...
129
134
  @classmethod
130
135
  def ephemeral(
131
136
  cls: type[Volume],
@@ -213,11 +218,17 @@ class Volume(modal.object.Object):
213
218
 
214
219
  read_file: __read_file_spec[typing_extensions.Self]
215
220
 
216
- class __read_file_into_fileobj_spec(typing_extensions.Protocol[SUPERSELF]):
217
- def __call__(self, path: str, fileobj: typing.IO[bytes]) -> int: ...
218
- async def aio(self, path: str, fileobj: typing.IO[bytes]) -> int: ...
221
+ class ___read_file1_spec(typing_extensions.Protocol[SUPERSELF]):
222
+ def __call__(self, path: str) -> typing.Iterator[bytes]: ...
223
+ def aio(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
224
+
225
+ _read_file1: ___read_file1_spec[typing_extensions.Self]
226
+
227
+ class ___read_file2_spec(typing_extensions.Protocol[SUPERSELF]):
228
+ def __call__(self, path: str) -> typing.Iterator[bytes]: ...
229
+ def aio(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
219
230
 
220
- read_file_into_fileobj: __read_file_into_fileobj_spec[typing_extensions.Self]
231
+ _read_file2: ___read_file2_spec[typing_extensions.Self]
221
232
 
222
233
  class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
223
234
  def __call__(self, path: str, recursive: bool = False) -> None: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.49
3
+ Version: 0.74.51
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -20,7 +20,7 @@ Requires-Dist: aiohttp
20
20
  Requires-Dist: certifi
21
21
  Requires-Dist: click>=8.1.0
22
22
  Requires-Dist: grpclib==0.4.7
23
- Requires-Dist: protobuf!=4.24.0,<6.0,>=3.19
23
+ Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
24
24
  Requires-Dist: rich>=12.0.0
25
25
  Requires-Dist: synchronicity~=0.9.10
26
26
  Requires-Dist: toml
@@ -22,7 +22,7 @@ modal/app.py,sha256=r-9vVU1lrR1CWtJEo60fuaianvxY_oOXZyv1Qx1DEkI,51231
22
22
  modal/app.pyi,sha256=0QNtnUpAFbOPcbwCt119ge7OmoBqMFw5SajLgdE5eOw,28600
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=U-YKSw0n7J1ZLREt9cbEJCtmHe5YoPKFxl0xlkan2yc,15565
25
- modal/client.pyi,sha256=aMgJftZr5pTStBb0n4tz8sgLrlKIaRZdrBMMEh9rFVg,7593
25
+ modal/client.pyi,sha256=ogVEBy4PU2drB-wMNRCDKprT_A5VwKKO5GdfXBeEHvg,7593
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=Ypd4NKsjOTnnnqXyTGGLKq5lkocRrUURYjY5Pi67_qA,670
78
78
  modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
79
79
  modal/token_flow.py,sha256=ZYCa-uVP6jrJmUKA6wAG4lngqNem8Mgm2HXz1DR19BM,7355
80
80
  modal/token_flow.pyi,sha256=0XV3d-9CGQL3qjPdw3RgwIFVqqxo8Z-u044_mkgAM3o,2064
81
- modal/volume.py,sha256=197mYPFbjJfQWpLEvE3vinTwNpmbh0CC_B-NkOHzKo8,41979
82
- modal/volume.pyi,sha256=_gE9ED_aO-o3_f9MDCkJc6OM8qU1w5ULJ4aKoJkhlgk,18356
81
+ modal/volume.py,sha256=d8x6i2qJgacJQ5qbNMvzeNFrK7ap4jPtjxZEpWstpCw,40624
82
+ modal/volume.pyi,sha256=rjlkEt9zuCy7GLe1TuVDSTiGJliFX9v6_KGKqL8HaeM,18760
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
@@ -145,7 +145,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
145
145
  modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
146
146
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
147
147
  modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
148
- modal-0.74.49.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
148
+ modal-0.74.51.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
149
149
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
150
150
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
151
151
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -170,9 +170,9 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
170
170
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
171
  modal_version/__init__.py,sha256=m94xZNWIjH8oUtJk4l9xfovzDJede2o7X-q0MHVECtM,470
172
172
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
173
- modal_version/_version_generated.py,sha256=-h2evheb0h6ZeCRd4OY4P-xlD3f4lAxmMFdV25T_ZpM,149
174
- modal-0.74.49.dist-info/METADATA,sha256=WVM_5pHEfZr-wAqhPek3gvwIZMAWoKWpdeUjIw-Bveo,2451
175
- modal-0.74.49.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
- modal-0.74.49.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
- modal-0.74.49.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
- modal-0.74.49.dist-info/RECORD,,
173
+ modal_version/_version_generated.py,sha256=XWG6Z6Rycy47HH88imPv1R6cURBY7Kq4Khuy3MbKuh4,149
174
+ modal-0.74.51.dist-info/METADATA,sha256=ub5SsraMgh2uqk6809lzYrCsp0T6NEke1jYNdAsUezQ,2451
175
+ modal-0.74.51.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
176
+ modal-0.74.51.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
177
+ modal-0.74.51.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
178
+ modal-0.74.51.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 49 # git: b2ad6b8
4
+ build_number = 51 # git: c6fac09