modal 1.2.1.dev5__py3-none-any.whl → 1.2.1.dev6__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

modal/cli/_download.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2023
2
2
  import asyncio
3
3
  import functools
4
+ import multiprocessing
4
5
  import os
5
6
  import shutil
6
7
  import sys
@@ -23,12 +24,22 @@ async def _volume_download(
23
24
  remote_path: str,
24
25
  local_destination: Path,
25
26
  overwrite: bool,
26
- progress_cb: Callable,
27
+ concurrency: Optional[int] = None,
28
+ progress_cb: Optional[Callable] = None,
27
29
  ):
30
+ if progress_cb is None:
31
+
32
+ def progress_cb(*_, **__):
33
+ pass
34
+
35
+ if concurrency is None:
36
+ concurrency = max(128, 2 * multiprocessing.cpu_count())
37
+
28
38
  is_pipe = local_destination == PIPE_PATH
29
39
 
30
40
  q: asyncio.Queue[tuple[Optional[Path], Optional[FileEntry]]] = asyncio.Queue()
31
- num_consumers = 1 if is_pipe else 10 # concurrency limit for downloading files
41
+ num_consumers = 1 if is_pipe else concurrency # concurrency limit for downloading files
42
+ download_semaphore = asyncio.Semaphore(concurrency)
32
43
 
33
44
  async def producer():
34
45
  iterator: AsyncIterator[FileEntry]
@@ -86,7 +97,12 @@ async def _volume_download(
86
97
 
87
98
  with output_path.open("wb") as fp:
88
99
  if isinstance(volume, _Volume):
89
- b = await volume.read_file_into_fileobj(entry.path, fp, file_progress_cb)
100
+ b = await volume._read_file_into_fileobj(
101
+ path=entry.path,
102
+ fileobj=fp,
103
+ download_semaphore=download_semaphore,
104
+ progress_cb=file_progress_cb,
105
+ )
90
106
  else:
91
107
  b = 0
92
108
  async for chunk in volume.read_file(entry.path):
modal/cli/volume.py CHANGED
@@ -96,7 +96,13 @@ async def get(
96
96
  console = make_console()
97
97
  progress_handler = ProgressHandler(type="download", console=console)
98
98
  with progress_handler.live:
99
- await _volume_download(volume, remote_path, destination, force, progress_cb=progress_handler.progress)
99
+ await _volume_download(
100
+ volume=volume,
101
+ remote_path=remote_path,
102
+ local_destination=destination,
103
+ overwrite=force,
104
+ progress_cb=progress_handler.progress,
105
+ )
100
106
  console.print(OutputManager.step_completed("Finished downloading files to local!"))
101
107
 
102
108
 
modal/client.pyi CHANGED
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.2.1.dev5",
36
+ version: str = "1.2.1.dev6",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.1.dev5",
167
+ version: str = "1.2.1.dev6",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
modal/volume.py CHANGED
@@ -670,16 +670,33 @@ class _Volume(_Object, type_prefix="vo"):
670
670
 
671
671
  @live_method
672
672
  async def read_file_into_fileobj(
673
- self, path: str, fileobj: typing.IO[bytes], progress_cb: Optional[Callable[..., Any]] = None
673
+ self,
674
+ path: str,
675
+ fileobj: typing.IO[bytes],
676
+ progress_cb: Optional[Callable[..., Any]] = None,
674
677
  ) -> int:
675
678
  """mdmd:hidden
676
679
  Read volume file into file-like IO object.
677
680
  """
681
+ return await self._read_file_into_fileobj(path, fileobj, progress_cb=progress_cb)
682
+
683
+ @live_method
684
+ async def _read_file_into_fileobj(
685
+ self,
686
+ path: str,
687
+ fileobj: typing.IO[bytes],
688
+ concurrency: Optional[int] = None,
689
+ download_semaphore: Optional[asyncio.Semaphore] = None,
690
+ progress_cb: Optional[Callable[..., Any]] = None,
691
+ ) -> int:
678
692
  if progress_cb is None:
679
693
 
680
694
  def progress_cb(*_, **__):
681
695
  pass
682
696
 
697
+ if concurrency is None:
698
+ concurrency = multiprocessing.cpu_count()
699
+
683
700
  req = api_pb2.VolumeGetFile2Request(volume_id=self.object_id, path=path)
684
701
 
685
702
  try:
@@ -687,8 +704,9 @@ class _Volume(_Object, type_prefix="vo"):
687
704
  except modal.exception.NotFoundError as exc:
688
705
  raise FileNotFoundError(exc.args[0])
689
706
 
690
- # TODO(dflemstr): Sane default limit? Make configurable?
691
- download_semaphore = asyncio.Semaphore(multiprocessing.cpu_count())
707
+ if download_semaphore is None:
708
+ download_semaphore = asyncio.Semaphore(concurrency)
709
+
692
710
  write_lock = asyncio.Lock()
693
711
  start_pos = fileobj.tell()
694
712
 
modal/volume.pyi CHANGED
@@ -626,6 +626,14 @@ class _Volume(modal._object._Object):
626
626
  """
627
627
  ...
628
628
 
629
+ async def _read_file_into_fileobj(
630
+ self,
631
+ path: str,
632
+ fileobj: typing.IO[bytes],
633
+ concurrency: typing.Optional[int] = None,
634
+ download_semaphore: typing.Optional[asyncio.locks.Semaphore] = None,
635
+ progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
636
+ ) -> int: ...
629
637
  async def remove_file(self, path: str, recursive: bool = False) -> None:
630
638
  """Remove a file or directory from a volume."""
631
639
  ...
@@ -1065,6 +1073,28 @@ class Volume(modal.object.Object):
1065
1073
 
1066
1074
  read_file_into_fileobj: __read_file_into_fileobj_spec[typing_extensions.Self]
1067
1075
 
1076
+ class ___read_file_into_fileobj_spec(typing_extensions.Protocol[SUPERSELF]):
1077
+ def __call__(
1078
+ self,
1079
+ /,
1080
+ path: str,
1081
+ fileobj: typing.IO[bytes],
1082
+ concurrency: typing.Optional[int] = None,
1083
+ download_semaphore: typing.Optional[asyncio.locks.Semaphore] = None,
1084
+ progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
1085
+ ) -> int: ...
1086
+ async def aio(
1087
+ self,
1088
+ /,
1089
+ path: str,
1090
+ fileobj: typing.IO[bytes],
1091
+ concurrency: typing.Optional[int] = None,
1092
+ download_semaphore: typing.Optional[asyncio.locks.Semaphore] = None,
1093
+ progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
1094
+ ) -> int: ...
1095
+
1096
+ _read_file_into_fileobj: ___read_file_into_fileobj_spec[typing_extensions.Self]
1097
+
1068
1098
  class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
1069
1099
  def __call__(self, /, path: str, recursive: bool = False) -> None:
1070
1100
  """Remove a file or directory from a volume."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev5
3
+ Version: 1.2.1.dev6
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -24,7 +24,7 @@ modal/app.pyi,sha256=6wlYK9nNp0GuHXyUxGmcZ6J92EjsWS-KK6KRuejqXEY,49718
24
24
  modal/billing.py,sha256=zmQ3bcCJlwa4KD1IA_QgdWpm1pn13c-7qfy79iEauYI,195
25
25
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
26
26
  modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
27
- modal/client.pyi,sha256=jEOYIBu7k8RylJM1h0azhNPPiLD88XZ2PXZpznSqcDw,15829
27
+ modal/client.pyi,sha256=FGi7I3pZlYeqgO-K6BAnw70jdm-Zb3cxkuMFCACHIJY,15829
28
28
  modal/cloud_bucket_mount.py,sha256=I2GRXYhOWLIz2kJZjXu75jAm9EJkBNcutGc6jR2ReUw,5928
29
29
  modal/cloud_bucket_mount.pyi,sha256=VuUOipMIHqFXMkD-3g2bsoqpSxV5qswlFHDOqPQzYAo,7405
30
30
  modal/cls.py,sha256=_Dzu8Tjjp_XukJxfBioAeHf03egKQYuKj7SrHJiK6CE,40676
@@ -80,8 +80,8 @@ modal/snapshot.pyi,sha256=0q83hlmWxAhDu8xwZyL5VmYh0i8Tigf7S60or2k30L8,1682
80
80
  modal/stream_type.py,sha256=A6320qoAAWhEfwOCZfGtymQTu5AfLfJXXgARqooTPvY,417
81
81
  modal/token_flow.py,sha256=GWpar0gANs71vm9Bd_Cj87UG1K3ljTURbkEjG3JLsrY,7616
82
82
  modal/token_flow.pyi,sha256=eirYjyqbRiT3GCKMIPHJPpkvBTu8WxDKqSHehWaJI_4,2533
83
- modal/volume.py,sha256=LNfy3GZVuPZdG9LaBjHiLtpw2OdKpuOfOvw6bK1yKT8,51798
84
- modal/volume.pyi,sha256=Fgq-QDo4iwWO-CFAgG879AbbyBuSvwOaIRbGclcTQik,51972
83
+ modal/volume.py,sha256=IctU4M2bv_uw-0KuKqaN-xXyXEeNECD2QVQ_DUqrklA,52267
84
+ modal/volume.pyi,sha256=WZGdwB51qvH5E7d6qS7sNCsscyuHsKUHFyyiZaQyvw8,53212
85
85
  modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
86
86
  modal/_runtime/asgi.py,sha256=AOcduIlijmlxhXVWo7AIUhigo-bqm6nDkHj4Q4JLy6o,22607
87
87
  modal/_runtime/container_io_manager.py,sha256=Q3JUEYrsfWsG0DuuVUiYXAUccfb8RhM8ExQEQJXR0nc,52200
@@ -127,7 +127,7 @@ modal/builder/PREVIEW.txt,sha256=XM4z76RPnrcbCx3o3sqXijWnumyOGiTCLGhKaaS4xz0,325
127
127
  modal/builder/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
128
128
  modal/builder/base-images.json,sha256=JYSDAgHTl-WrV_TZW5icY-IJEnbe2eQ4CZ_KN6EOZKU,1304
129
129
  modal/cli/__init__.py,sha256=6FRleWQxBDT19y7OayO4lBOzuL6Bs9r0rLINYYYbHwQ,769
130
- modal/cli/_download.py,sha256=tV8JFkncTtQKh85bSguQg6AW5aRRlynf-rvyN7ruigc,4337
130
+ modal/cli/_download.py,sha256=_Q_CG1Qb4cjVSAwHGGPOPoNTsmK9gHvF-HNWCNdFjaY,4900
131
131
  modal/cli/_traceback.py,sha256=IKj9xtc6LjAxyhGJWolNIXEX3MhAIulnRqywZNOFmkU,7324
132
132
  modal/cli/app.py,sha256=rbuAG92my-1eZN0olk6p2eD4oBnyBliUsrCOUW-U-9k,7832
133
133
  modal/cli/cluster.py,sha256=MMdFXqyOOeNO7P2VIUN78xKJLvesLIy_28Dt56XEXhM,3307
@@ -145,7 +145,7 @@ modal/cli/run.py,sha256=3_UF2Vdye_KEgY8JzBBomt0i944cCJx96RfNbeAEPDo,25783
145
145
  modal/cli/secret.py,sha256=-Nnk3fq1IEzMtC9VUn61AKmdvZzZ9XQyiKVgQYRpajo,8127
146
146
  modal/cli/token.py,sha256=NAmQzKBfEHkcldWKeFxAVIqQBoo1RTp7_A4yc7-8qM0,1911
147
147
  modal/cli/utils.py,sha256=aUXDU9_VgcJrGaGRy4bGf4dqwKYXHCpoO27x4m_bpuo,3293
148
- modal/cli/volume.py,sha256=QUHG28kKuL0UG6gO3wNdLQD1yVBUNVNZL70bxs0jBNo,10759
148
+ modal/cli/volume.py,sha256=wJchJyIEw9lTKiNUNTgR4I3DPYHYJBu50gU_SsecP0w,10877
149
149
  modal/cli/programs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
150
150
  modal/cli/programs/launch_instance_ssh.py,sha256=GrwK_Vy8-7B4x5a6AqFaF7lqNVgu75JYZ2BtFV0_DOw,2660
151
151
  modal/cli/programs/run_jupyter.py,sha256=IJw8nds8Cjl9j4dxBqMGxhz-bIyVX0kle7jbt8HFOhM,2688
@@ -155,7 +155,7 @@ modal/experimental/__init__.py,sha256=9gkVuDmu3m4TlKoU3MzEtTOemUSs8EEOWba40s7Aa0
155
155
  modal/experimental/flash.py,sha256=C4sef08rARYFllsgtqukFmYL18SZW0_JpMS0BejDcUs,28552
156
156
  modal/experimental/flash.pyi,sha256=vV_OQhtdrPn8SW0XrBK-aLLHHIvxAzLzwFbWrke-m74,15463
157
157
  modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
158
- modal-1.2.1.dev5.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
158
+ modal-1.2.1.dev6.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
159
159
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
160
160
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
161
161
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -183,10 +183,10 @@ modal_proto/task_command_router_pb2.py,sha256=_pD2ZpU0bNzhwBdzmLoLyLtAtftI_Agxwn
183
183
  modal_proto/task_command_router_pb2.pyi,sha256=EyDgXPLr7alqjXYERV8w_MPuO404x0uCppmSkrfE9IE,14589
184
184
  modal_proto/task_command_router_pb2_grpc.py,sha256=uEQ0HdrCp8v-9bB5yIic9muA8spCShLHY6Bz9cCgOUE,10114
185
185
  modal_proto/task_command_router_pb2_grpc.pyi,sha256=s3Yxsrawdj4nr8vqQqsAxyX6ilWaGbdECy425KKbLIA,3301
186
- modal_version/__init__.py,sha256=7UlnXu54imxCFuqMRYfAvi2QWP_63FRHLnTsGSJJpPA,120
186
+ modal_version/__init__.py,sha256=LY_wuei6F9T8FfZ_2nkSI70w3GrMT-NIf59vhLq1kV8,120
187
187
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
188
- modal-1.2.1.dev5.dist-info/METADATA,sha256=vQiClNCSghoVAGp5PJLQ9QpUrXrDgwrcNXJC9rkq5L4,2483
189
- modal-1.2.1.dev5.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
190
- modal-1.2.1.dev5.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
191
- modal-1.2.1.dev5.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
192
- modal-1.2.1.dev5.dist-info/RECORD,,
188
+ modal-1.2.1.dev6.dist-info/METADATA,sha256=c1rDfKMZkhrvkyQicFPbv9evf5wuXLUX5BzW6R_YHwc,2483
189
+ modal-1.2.1.dev6.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
190
+ modal-1.2.1.dev6.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
191
+ modal-1.2.1.dev6.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
192
+ modal-1.2.1.dev6.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.2.1.dev5"
4
+ __version__ = "1.2.1.dev6"