modal 0.74.58__tar.gz → 0.74.59__tar.gz

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.
Files changed (184) hide show
  1. {modal-0.74.58 → modal-0.74.59}/PKG-INFO +1 -1
  2. {modal-0.74.58 → modal-0.74.59}/modal/cli/_download.py +17 -7
  3. {modal-0.74.58 → modal-0.74.59}/modal/client.pyi +2 -2
  4. {modal-0.74.58 → modal-0.74.59}/modal/volume.py +90 -0
  5. {modal-0.74.58 → modal-0.74.59}/modal/volume.pyi +50 -0
  6. {modal-0.74.58 → modal-0.74.59}/modal.egg-info/PKG-INFO +1 -1
  7. {modal-0.74.58 → modal-0.74.59}/modal_version/_version_generated.py +1 -1
  8. {modal-0.74.58 → modal-0.74.59}/LICENSE +0 -0
  9. {modal-0.74.58 → modal-0.74.59}/README.md +0 -0
  10. {modal-0.74.58 → modal-0.74.59}/modal/__init__.py +0 -0
  11. {modal-0.74.58 → modal-0.74.59}/modal/__main__.py +0 -0
  12. {modal-0.74.58 → modal-0.74.59}/modal/_clustered_functions.py +0 -0
  13. {modal-0.74.58 → modal-0.74.59}/modal/_clustered_functions.pyi +0 -0
  14. {modal-0.74.58 → modal-0.74.59}/modal/_container_entrypoint.py +0 -0
  15. {modal-0.74.58 → modal-0.74.59}/modal/_functions.py +0 -0
  16. {modal-0.74.58 → modal-0.74.59}/modal/_ipython.py +0 -0
  17. {modal-0.74.58 → modal-0.74.59}/modal/_location.py +0 -0
  18. {modal-0.74.58 → modal-0.74.59}/modal/_object.py +0 -0
  19. {modal-0.74.58 → modal-0.74.59}/modal/_output.py +0 -0
  20. {modal-0.74.58 → modal-0.74.59}/modal/_partial_function.py +0 -0
  21. {modal-0.74.58 → modal-0.74.59}/modal/_pty.py +0 -0
  22. {modal-0.74.58 → modal-0.74.59}/modal/_resolver.py +0 -0
  23. {modal-0.74.58 → modal-0.74.59}/modal/_resources.py +0 -0
  24. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/__init__.py +0 -0
  25. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/asgi.py +0 -0
  26. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/container_io_manager.py +0 -0
  27. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/container_io_manager.pyi +0 -0
  28. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/execution_context.py +0 -0
  29. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/execution_context.pyi +0 -0
  30. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  31. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/telemetry.py +0 -0
  32. {modal-0.74.58 → modal-0.74.59}/modal/_runtime/user_code_imports.py +0 -0
  33. {modal-0.74.58 → modal-0.74.59}/modal/_serialization.py +0 -0
  34. {modal-0.74.58 → modal-0.74.59}/modal/_traceback.py +0 -0
  35. {modal-0.74.58 → modal-0.74.59}/modal/_tunnel.py +0 -0
  36. {modal-0.74.58 → modal-0.74.59}/modal/_tunnel.pyi +0 -0
  37. {modal-0.74.58 → modal-0.74.59}/modal/_type_manager.py +0 -0
  38. {modal-0.74.58 → modal-0.74.59}/modal/_utils/__init__.py +0 -0
  39. {modal-0.74.58 → modal-0.74.59}/modal/_utils/app_utils.py +0 -0
  40. {modal-0.74.58 → modal-0.74.59}/modal/_utils/async_utils.py +0 -0
  41. {modal-0.74.58 → modal-0.74.59}/modal/_utils/blob_utils.py +0 -0
  42. {modal-0.74.58 → modal-0.74.59}/modal/_utils/bytes_io_segment_payload.py +0 -0
  43. {modal-0.74.58 → modal-0.74.59}/modal/_utils/deprecation.py +0 -0
  44. {modal-0.74.58 → modal-0.74.59}/modal/_utils/docker_utils.py +0 -0
  45. {modal-0.74.58 → modal-0.74.59}/modal/_utils/function_utils.py +0 -0
  46. {modal-0.74.58 → modal-0.74.59}/modal/_utils/git_utils.py +0 -0
  47. {modal-0.74.58 → modal-0.74.59}/modal/_utils/grpc_testing.py +0 -0
  48. {modal-0.74.58 → modal-0.74.59}/modal/_utils/grpc_utils.py +0 -0
  49. {modal-0.74.58 → modal-0.74.59}/modal/_utils/hash_utils.py +0 -0
  50. {modal-0.74.58 → modal-0.74.59}/modal/_utils/http_utils.py +0 -0
  51. {modal-0.74.58 → modal-0.74.59}/modal/_utils/jwt_utils.py +0 -0
  52. {modal-0.74.58 → modal-0.74.59}/modal/_utils/logger.py +0 -0
  53. {modal-0.74.58 → modal-0.74.59}/modal/_utils/mount_utils.py +0 -0
  54. {modal-0.74.58 → modal-0.74.59}/modal/_utils/name_utils.py +0 -0
  55. {modal-0.74.58 → modal-0.74.59}/modal/_utils/package_utils.py +0 -0
  56. {modal-0.74.58 → modal-0.74.59}/modal/_utils/pattern_utils.py +0 -0
  57. {modal-0.74.58 → modal-0.74.59}/modal/_utils/rand_pb_testing.py +0 -0
  58. {modal-0.74.58 → modal-0.74.59}/modal/_utils/shell_utils.py +0 -0
  59. {modal-0.74.58 → modal-0.74.59}/modal/_vendor/__init__.py +0 -0
  60. {modal-0.74.58 → modal-0.74.59}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  61. {modal-0.74.58 → modal-0.74.59}/modal/_vendor/cloudpickle.py +0 -0
  62. {modal-0.74.58 → modal-0.74.59}/modal/_vendor/tblib.py +0 -0
  63. {modal-0.74.58 → modal-0.74.59}/modal/_watcher.py +0 -0
  64. {modal-0.74.58 → modal-0.74.59}/modal/app.py +0 -0
  65. {modal-0.74.58 → modal-0.74.59}/modal/app.pyi +0 -0
  66. {modal-0.74.58 → modal-0.74.59}/modal/call_graph.py +0 -0
  67. {modal-0.74.58 → modal-0.74.59}/modal/cli/__init__.py +0 -0
  68. {modal-0.74.58 → modal-0.74.59}/modal/cli/_traceback.py +0 -0
  69. {modal-0.74.58 → modal-0.74.59}/modal/cli/app.py +0 -0
  70. {modal-0.74.58 → modal-0.74.59}/modal/cli/cluster.py +0 -0
  71. {modal-0.74.58 → modal-0.74.59}/modal/cli/config.py +0 -0
  72. {modal-0.74.58 → modal-0.74.59}/modal/cli/container.py +0 -0
  73. {modal-0.74.58 → modal-0.74.59}/modal/cli/dict.py +0 -0
  74. {modal-0.74.58 → modal-0.74.59}/modal/cli/entry_point.py +0 -0
  75. {modal-0.74.58 → modal-0.74.59}/modal/cli/environment.py +0 -0
  76. {modal-0.74.58 → modal-0.74.59}/modal/cli/import_refs.py +0 -0
  77. {modal-0.74.58 → modal-0.74.59}/modal/cli/launch.py +0 -0
  78. {modal-0.74.58 → modal-0.74.59}/modal/cli/network_file_system.py +0 -0
  79. {modal-0.74.58 → modal-0.74.59}/modal/cli/profile.py +0 -0
  80. {modal-0.74.58 → modal-0.74.59}/modal/cli/programs/__init__.py +0 -0
  81. {modal-0.74.58 → modal-0.74.59}/modal/cli/programs/run_jupyter.py +0 -0
  82. {modal-0.74.58 → modal-0.74.59}/modal/cli/programs/vscode.py +0 -0
  83. {modal-0.74.58 → modal-0.74.59}/modal/cli/queues.py +0 -0
  84. {modal-0.74.58 → modal-0.74.59}/modal/cli/run.py +0 -0
  85. {modal-0.74.58 → modal-0.74.59}/modal/cli/secret.py +0 -0
  86. {modal-0.74.58 → modal-0.74.59}/modal/cli/token.py +0 -0
  87. {modal-0.74.58 → modal-0.74.59}/modal/cli/utils.py +0 -0
  88. {modal-0.74.58 → modal-0.74.59}/modal/cli/volume.py +0 -0
  89. {modal-0.74.58 → modal-0.74.59}/modal/client.py +0 -0
  90. {modal-0.74.58 → modal-0.74.59}/modal/cloud_bucket_mount.py +0 -0
  91. {modal-0.74.58 → modal-0.74.59}/modal/cloud_bucket_mount.pyi +0 -0
  92. {modal-0.74.58 → modal-0.74.59}/modal/cls.py +0 -0
  93. {modal-0.74.58 → modal-0.74.59}/modal/cls.pyi +0 -0
  94. {modal-0.74.58 → modal-0.74.59}/modal/config.py +0 -0
  95. {modal-0.74.58 → modal-0.74.59}/modal/container_process.py +0 -0
  96. {modal-0.74.58 → modal-0.74.59}/modal/container_process.pyi +0 -0
  97. {modal-0.74.58 → modal-0.74.59}/modal/dict.py +0 -0
  98. {modal-0.74.58 → modal-0.74.59}/modal/dict.pyi +0 -0
  99. {modal-0.74.58 → modal-0.74.59}/modal/environments.py +0 -0
  100. {modal-0.74.58 → modal-0.74.59}/modal/environments.pyi +0 -0
  101. {modal-0.74.58 → modal-0.74.59}/modal/exception.py +0 -0
  102. {modal-0.74.58 → modal-0.74.59}/modal/experimental/__init__.py +0 -0
  103. {modal-0.74.58 → modal-0.74.59}/modal/experimental/ipython.py +0 -0
  104. {modal-0.74.58 → modal-0.74.59}/modal/file_io.py +0 -0
  105. {modal-0.74.58 → modal-0.74.59}/modal/file_io.pyi +0 -0
  106. {modal-0.74.58 → modal-0.74.59}/modal/file_pattern_matcher.py +0 -0
  107. {modal-0.74.58 → modal-0.74.59}/modal/functions.py +0 -0
  108. {modal-0.74.58 → modal-0.74.59}/modal/functions.pyi +0 -0
  109. {modal-0.74.58 → modal-0.74.59}/modal/gpu.py +0 -0
  110. {modal-0.74.58 → modal-0.74.59}/modal/image.py +0 -0
  111. {modal-0.74.58 → modal-0.74.59}/modal/image.pyi +0 -0
  112. {modal-0.74.58 → modal-0.74.59}/modal/io_streams.py +0 -0
  113. {modal-0.74.58 → modal-0.74.59}/modal/io_streams.pyi +0 -0
  114. {modal-0.74.58 → modal-0.74.59}/modal/mount.py +0 -0
  115. {modal-0.74.58 → modal-0.74.59}/modal/mount.pyi +0 -0
  116. {modal-0.74.58 → modal-0.74.59}/modal/network_file_system.py +0 -0
  117. {modal-0.74.58 → modal-0.74.59}/modal/network_file_system.pyi +0 -0
  118. {modal-0.74.58 → modal-0.74.59}/modal/object.py +0 -0
  119. {modal-0.74.58 → modal-0.74.59}/modal/object.pyi +0 -0
  120. {modal-0.74.58 → modal-0.74.59}/modal/output.py +0 -0
  121. {modal-0.74.58 → modal-0.74.59}/modal/parallel_map.py +0 -0
  122. {modal-0.74.58 → modal-0.74.59}/modal/parallel_map.pyi +0 -0
  123. {modal-0.74.58 → modal-0.74.59}/modal/partial_function.py +0 -0
  124. {modal-0.74.58 → modal-0.74.59}/modal/partial_function.pyi +0 -0
  125. {modal-0.74.58 → modal-0.74.59}/modal/proxy.py +0 -0
  126. {modal-0.74.58 → modal-0.74.59}/modal/proxy.pyi +0 -0
  127. {modal-0.74.58 → modal-0.74.59}/modal/py.typed +0 -0
  128. {modal-0.74.58 → modal-0.74.59}/modal/queue.py +0 -0
  129. {modal-0.74.58 → modal-0.74.59}/modal/queue.pyi +0 -0
  130. {modal-0.74.58 → modal-0.74.59}/modal/requirements/2023.12.312.txt +0 -0
  131. {modal-0.74.58 → modal-0.74.59}/modal/requirements/2023.12.txt +0 -0
  132. {modal-0.74.58 → modal-0.74.59}/modal/requirements/2024.04.txt +0 -0
  133. {modal-0.74.58 → modal-0.74.59}/modal/requirements/2024.10.txt +0 -0
  134. {modal-0.74.58 → modal-0.74.59}/modal/requirements/PREVIEW.txt +0 -0
  135. {modal-0.74.58 → modal-0.74.59}/modal/requirements/README.md +0 -0
  136. {modal-0.74.58 → modal-0.74.59}/modal/requirements/base-images.json +0 -0
  137. {modal-0.74.58 → modal-0.74.59}/modal/retries.py +0 -0
  138. {modal-0.74.58 → modal-0.74.59}/modal/runner.py +0 -0
  139. {modal-0.74.58 → modal-0.74.59}/modal/runner.pyi +0 -0
  140. {modal-0.74.58 → modal-0.74.59}/modal/running_app.py +0 -0
  141. {modal-0.74.58 → modal-0.74.59}/modal/sandbox.py +0 -0
  142. {modal-0.74.58 → modal-0.74.59}/modal/sandbox.pyi +0 -0
  143. {modal-0.74.58 → modal-0.74.59}/modal/schedule.py +0 -0
  144. {modal-0.74.58 → modal-0.74.59}/modal/scheduler_placement.py +0 -0
  145. {modal-0.74.58 → modal-0.74.59}/modal/secret.py +0 -0
  146. {modal-0.74.58 → modal-0.74.59}/modal/secret.pyi +0 -0
  147. {modal-0.74.58 → modal-0.74.59}/modal/serving.py +0 -0
  148. {modal-0.74.58 → modal-0.74.59}/modal/serving.pyi +0 -0
  149. {modal-0.74.58 → modal-0.74.59}/modal/snapshot.py +0 -0
  150. {modal-0.74.58 → modal-0.74.59}/modal/snapshot.pyi +0 -0
  151. {modal-0.74.58 → modal-0.74.59}/modal/stream_type.py +0 -0
  152. {modal-0.74.58 → modal-0.74.59}/modal/token_flow.py +0 -0
  153. {modal-0.74.58 → modal-0.74.59}/modal/token_flow.pyi +0 -0
  154. {modal-0.74.58 → modal-0.74.59}/modal.egg-info/SOURCES.txt +0 -0
  155. {modal-0.74.58 → modal-0.74.59}/modal.egg-info/dependency_links.txt +0 -0
  156. {modal-0.74.58 → modal-0.74.59}/modal.egg-info/entry_points.txt +0 -0
  157. {modal-0.74.58 → modal-0.74.59}/modal.egg-info/requires.txt +0 -0
  158. {modal-0.74.58 → modal-0.74.59}/modal.egg-info/top_level.txt +0 -0
  159. {modal-0.74.58 → modal-0.74.59}/modal_docs/__init__.py +0 -0
  160. {modal-0.74.58 → modal-0.74.59}/modal_docs/gen_cli_docs.py +0 -0
  161. {modal-0.74.58 → modal-0.74.59}/modal_docs/gen_reference_docs.py +0 -0
  162. {modal-0.74.58 → modal-0.74.59}/modal_docs/mdmd/__init__.py +0 -0
  163. {modal-0.74.58 → modal-0.74.59}/modal_docs/mdmd/mdmd.py +0 -0
  164. {modal-0.74.58 → modal-0.74.59}/modal_docs/mdmd/signatures.py +0 -0
  165. {modal-0.74.58 → modal-0.74.59}/modal_proto/__init__.py +0 -0
  166. {modal-0.74.58 → modal-0.74.59}/modal_proto/api.proto +0 -0
  167. {modal-0.74.58 → modal-0.74.59}/modal_proto/api_grpc.py +0 -0
  168. {modal-0.74.58 → modal-0.74.59}/modal_proto/api_pb2.py +0 -0
  169. {modal-0.74.58 → modal-0.74.59}/modal_proto/api_pb2.pyi +0 -0
  170. {modal-0.74.58 → modal-0.74.59}/modal_proto/api_pb2_grpc.py +0 -0
  171. {modal-0.74.58 → modal-0.74.59}/modal_proto/api_pb2_grpc.pyi +0 -0
  172. {modal-0.74.58 → modal-0.74.59}/modal_proto/modal_api_grpc.py +0 -0
  173. {modal-0.74.58 → modal-0.74.59}/modal_proto/modal_options_grpc.py +0 -0
  174. {modal-0.74.58 → modal-0.74.59}/modal_proto/options.proto +0 -0
  175. {modal-0.74.58 → modal-0.74.59}/modal_proto/options_grpc.py +0 -0
  176. {modal-0.74.58 → modal-0.74.59}/modal_proto/options_pb2.py +0 -0
  177. {modal-0.74.58 → modal-0.74.59}/modal_proto/options_pb2.pyi +0 -0
  178. {modal-0.74.58 → modal-0.74.59}/modal_proto/options_pb2_grpc.py +0 -0
  179. {modal-0.74.58 → modal-0.74.59}/modal_proto/options_pb2_grpc.pyi +0 -0
  180. {modal-0.74.58 → modal-0.74.59}/modal_proto/py.typed +0 -0
  181. {modal-0.74.58 → modal-0.74.59}/modal_version/__init__.py +0 -0
  182. {modal-0.74.58 → modal-0.74.59}/modal_version/__main__.py +0 -0
  183. {modal-0.74.58 → modal-0.74.59}/pyproject.toml +0 -0
  184. {modal-0.74.58 → modal-0.74.59}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.58
3
+ Version: 0.74.59
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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
- progress_cb(task_id=progress_task_id, advance=len(chunk))
76
- progress_cb(task_id=progress_task_id, complete=True)
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
- b = 0
83
- async for chunk in volume.read_file(entry.path):
84
- b += fp.write(chunk)
85
- progress_cb(task_id=progress_task_id, advance=len(chunk))
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
- progress_cb(task_id=progress_task_id, complete=True)
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:
@@ -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.58"
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.58"
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
@@ -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."""
@@ -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: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 0.74.58
3
+ Version: 0.74.59
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -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 = 58 # git: ea8f826
4
+ build_number = 59 # git: 57679cb
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes