modal 0.74.58__tar.gz → 0.74.60__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.60}/PKG-INFO +2 -2
  2. {modal-0.74.58 → modal-0.74.60}/modal/cli/_download.py +17 -7
  3. {modal-0.74.58 → modal-0.74.60}/modal/client.pyi +2 -2
  4. {modal-0.74.58 → modal-0.74.60}/modal/dict.py +10 -3
  5. {modal-0.74.58 → modal-0.74.60}/modal/dict.pyi +3 -3
  6. {modal-0.74.58 → modal-0.74.60}/modal/functions.pyi +6 -6
  7. {modal-0.74.58 → modal-0.74.60}/modal/volume.py +90 -0
  8. {modal-0.74.58 → modal-0.74.60}/modal/volume.pyi +50 -0
  9. {modal-0.74.58 → modal-0.74.60}/modal.egg-info/PKG-INFO +2 -2
  10. {modal-0.74.58 → modal-0.74.60}/modal.egg-info/requires.txt +1 -1
  11. {modal-0.74.58 → modal-0.74.60}/modal_version/_version_generated.py +1 -1
  12. {modal-0.74.58 → modal-0.74.60}/pyproject.toml +1 -1
  13. {modal-0.74.58 → modal-0.74.60}/LICENSE +0 -0
  14. {modal-0.74.58 → modal-0.74.60}/README.md +0 -0
  15. {modal-0.74.58 → modal-0.74.60}/modal/__init__.py +0 -0
  16. {modal-0.74.58 → modal-0.74.60}/modal/__main__.py +0 -0
  17. {modal-0.74.58 → modal-0.74.60}/modal/_clustered_functions.py +0 -0
  18. {modal-0.74.58 → modal-0.74.60}/modal/_clustered_functions.pyi +0 -0
  19. {modal-0.74.58 → modal-0.74.60}/modal/_container_entrypoint.py +0 -0
  20. {modal-0.74.58 → modal-0.74.60}/modal/_functions.py +0 -0
  21. {modal-0.74.58 → modal-0.74.60}/modal/_ipython.py +0 -0
  22. {modal-0.74.58 → modal-0.74.60}/modal/_location.py +0 -0
  23. {modal-0.74.58 → modal-0.74.60}/modal/_object.py +0 -0
  24. {modal-0.74.58 → modal-0.74.60}/modal/_output.py +0 -0
  25. {modal-0.74.58 → modal-0.74.60}/modal/_partial_function.py +0 -0
  26. {modal-0.74.58 → modal-0.74.60}/modal/_pty.py +0 -0
  27. {modal-0.74.58 → modal-0.74.60}/modal/_resolver.py +0 -0
  28. {modal-0.74.58 → modal-0.74.60}/modal/_resources.py +0 -0
  29. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/__init__.py +0 -0
  30. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/asgi.py +0 -0
  31. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/container_io_manager.py +0 -0
  32. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/container_io_manager.pyi +0 -0
  33. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/execution_context.py +0 -0
  34. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/execution_context.pyi +0 -0
  35. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  36. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/telemetry.py +0 -0
  37. {modal-0.74.58 → modal-0.74.60}/modal/_runtime/user_code_imports.py +0 -0
  38. {modal-0.74.58 → modal-0.74.60}/modal/_serialization.py +0 -0
  39. {modal-0.74.58 → modal-0.74.60}/modal/_traceback.py +0 -0
  40. {modal-0.74.58 → modal-0.74.60}/modal/_tunnel.py +0 -0
  41. {modal-0.74.58 → modal-0.74.60}/modal/_tunnel.pyi +0 -0
  42. {modal-0.74.58 → modal-0.74.60}/modal/_type_manager.py +0 -0
  43. {modal-0.74.58 → modal-0.74.60}/modal/_utils/__init__.py +0 -0
  44. {modal-0.74.58 → modal-0.74.60}/modal/_utils/app_utils.py +0 -0
  45. {modal-0.74.58 → modal-0.74.60}/modal/_utils/async_utils.py +0 -0
  46. {modal-0.74.58 → modal-0.74.60}/modal/_utils/blob_utils.py +0 -0
  47. {modal-0.74.58 → modal-0.74.60}/modal/_utils/bytes_io_segment_payload.py +0 -0
  48. {modal-0.74.58 → modal-0.74.60}/modal/_utils/deprecation.py +0 -0
  49. {modal-0.74.58 → modal-0.74.60}/modal/_utils/docker_utils.py +0 -0
  50. {modal-0.74.58 → modal-0.74.60}/modal/_utils/function_utils.py +0 -0
  51. {modal-0.74.58 → modal-0.74.60}/modal/_utils/git_utils.py +0 -0
  52. {modal-0.74.58 → modal-0.74.60}/modal/_utils/grpc_testing.py +0 -0
  53. {modal-0.74.58 → modal-0.74.60}/modal/_utils/grpc_utils.py +0 -0
  54. {modal-0.74.58 → modal-0.74.60}/modal/_utils/hash_utils.py +0 -0
  55. {modal-0.74.58 → modal-0.74.60}/modal/_utils/http_utils.py +0 -0
  56. {modal-0.74.58 → modal-0.74.60}/modal/_utils/jwt_utils.py +0 -0
  57. {modal-0.74.58 → modal-0.74.60}/modal/_utils/logger.py +0 -0
  58. {modal-0.74.58 → modal-0.74.60}/modal/_utils/mount_utils.py +0 -0
  59. {modal-0.74.58 → modal-0.74.60}/modal/_utils/name_utils.py +0 -0
  60. {modal-0.74.58 → modal-0.74.60}/modal/_utils/package_utils.py +0 -0
  61. {modal-0.74.58 → modal-0.74.60}/modal/_utils/pattern_utils.py +0 -0
  62. {modal-0.74.58 → modal-0.74.60}/modal/_utils/rand_pb_testing.py +0 -0
  63. {modal-0.74.58 → modal-0.74.60}/modal/_utils/shell_utils.py +0 -0
  64. {modal-0.74.58 → modal-0.74.60}/modal/_vendor/__init__.py +0 -0
  65. {modal-0.74.58 → modal-0.74.60}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  66. {modal-0.74.58 → modal-0.74.60}/modal/_vendor/cloudpickle.py +0 -0
  67. {modal-0.74.58 → modal-0.74.60}/modal/_vendor/tblib.py +0 -0
  68. {modal-0.74.58 → modal-0.74.60}/modal/_watcher.py +0 -0
  69. {modal-0.74.58 → modal-0.74.60}/modal/app.py +0 -0
  70. {modal-0.74.58 → modal-0.74.60}/modal/app.pyi +0 -0
  71. {modal-0.74.58 → modal-0.74.60}/modal/call_graph.py +0 -0
  72. {modal-0.74.58 → modal-0.74.60}/modal/cli/__init__.py +0 -0
  73. {modal-0.74.58 → modal-0.74.60}/modal/cli/_traceback.py +0 -0
  74. {modal-0.74.58 → modal-0.74.60}/modal/cli/app.py +0 -0
  75. {modal-0.74.58 → modal-0.74.60}/modal/cli/cluster.py +0 -0
  76. {modal-0.74.58 → modal-0.74.60}/modal/cli/config.py +0 -0
  77. {modal-0.74.58 → modal-0.74.60}/modal/cli/container.py +0 -0
  78. {modal-0.74.58 → modal-0.74.60}/modal/cli/dict.py +0 -0
  79. {modal-0.74.58 → modal-0.74.60}/modal/cli/entry_point.py +0 -0
  80. {modal-0.74.58 → modal-0.74.60}/modal/cli/environment.py +0 -0
  81. {modal-0.74.58 → modal-0.74.60}/modal/cli/import_refs.py +0 -0
  82. {modal-0.74.58 → modal-0.74.60}/modal/cli/launch.py +0 -0
  83. {modal-0.74.58 → modal-0.74.60}/modal/cli/network_file_system.py +0 -0
  84. {modal-0.74.58 → modal-0.74.60}/modal/cli/profile.py +0 -0
  85. {modal-0.74.58 → modal-0.74.60}/modal/cli/programs/__init__.py +0 -0
  86. {modal-0.74.58 → modal-0.74.60}/modal/cli/programs/run_jupyter.py +0 -0
  87. {modal-0.74.58 → modal-0.74.60}/modal/cli/programs/vscode.py +0 -0
  88. {modal-0.74.58 → modal-0.74.60}/modal/cli/queues.py +0 -0
  89. {modal-0.74.58 → modal-0.74.60}/modal/cli/run.py +0 -0
  90. {modal-0.74.58 → modal-0.74.60}/modal/cli/secret.py +0 -0
  91. {modal-0.74.58 → modal-0.74.60}/modal/cli/token.py +0 -0
  92. {modal-0.74.58 → modal-0.74.60}/modal/cli/utils.py +0 -0
  93. {modal-0.74.58 → modal-0.74.60}/modal/cli/volume.py +0 -0
  94. {modal-0.74.58 → modal-0.74.60}/modal/client.py +0 -0
  95. {modal-0.74.58 → modal-0.74.60}/modal/cloud_bucket_mount.py +0 -0
  96. {modal-0.74.58 → modal-0.74.60}/modal/cloud_bucket_mount.pyi +0 -0
  97. {modal-0.74.58 → modal-0.74.60}/modal/cls.py +0 -0
  98. {modal-0.74.58 → modal-0.74.60}/modal/cls.pyi +0 -0
  99. {modal-0.74.58 → modal-0.74.60}/modal/config.py +0 -0
  100. {modal-0.74.58 → modal-0.74.60}/modal/container_process.py +0 -0
  101. {modal-0.74.58 → modal-0.74.60}/modal/container_process.pyi +0 -0
  102. {modal-0.74.58 → modal-0.74.60}/modal/environments.py +0 -0
  103. {modal-0.74.58 → modal-0.74.60}/modal/environments.pyi +0 -0
  104. {modal-0.74.58 → modal-0.74.60}/modal/exception.py +0 -0
  105. {modal-0.74.58 → modal-0.74.60}/modal/experimental/__init__.py +0 -0
  106. {modal-0.74.58 → modal-0.74.60}/modal/experimental/ipython.py +0 -0
  107. {modal-0.74.58 → modal-0.74.60}/modal/file_io.py +0 -0
  108. {modal-0.74.58 → modal-0.74.60}/modal/file_io.pyi +0 -0
  109. {modal-0.74.58 → modal-0.74.60}/modal/file_pattern_matcher.py +0 -0
  110. {modal-0.74.58 → modal-0.74.60}/modal/functions.py +0 -0
  111. {modal-0.74.58 → modal-0.74.60}/modal/gpu.py +0 -0
  112. {modal-0.74.58 → modal-0.74.60}/modal/image.py +0 -0
  113. {modal-0.74.58 → modal-0.74.60}/modal/image.pyi +0 -0
  114. {modal-0.74.58 → modal-0.74.60}/modal/io_streams.py +0 -0
  115. {modal-0.74.58 → modal-0.74.60}/modal/io_streams.pyi +0 -0
  116. {modal-0.74.58 → modal-0.74.60}/modal/mount.py +0 -0
  117. {modal-0.74.58 → modal-0.74.60}/modal/mount.pyi +0 -0
  118. {modal-0.74.58 → modal-0.74.60}/modal/network_file_system.py +0 -0
  119. {modal-0.74.58 → modal-0.74.60}/modal/network_file_system.pyi +0 -0
  120. {modal-0.74.58 → modal-0.74.60}/modal/object.py +0 -0
  121. {modal-0.74.58 → modal-0.74.60}/modal/object.pyi +0 -0
  122. {modal-0.74.58 → modal-0.74.60}/modal/output.py +0 -0
  123. {modal-0.74.58 → modal-0.74.60}/modal/parallel_map.py +0 -0
  124. {modal-0.74.58 → modal-0.74.60}/modal/parallel_map.pyi +0 -0
  125. {modal-0.74.58 → modal-0.74.60}/modal/partial_function.py +0 -0
  126. {modal-0.74.58 → modal-0.74.60}/modal/partial_function.pyi +0 -0
  127. {modal-0.74.58 → modal-0.74.60}/modal/proxy.py +0 -0
  128. {modal-0.74.58 → modal-0.74.60}/modal/proxy.pyi +0 -0
  129. {modal-0.74.58 → modal-0.74.60}/modal/py.typed +0 -0
  130. {modal-0.74.58 → modal-0.74.60}/modal/queue.py +0 -0
  131. {modal-0.74.58 → modal-0.74.60}/modal/queue.pyi +0 -0
  132. {modal-0.74.58 → modal-0.74.60}/modal/requirements/2023.12.312.txt +0 -0
  133. {modal-0.74.58 → modal-0.74.60}/modal/requirements/2023.12.txt +0 -0
  134. {modal-0.74.58 → modal-0.74.60}/modal/requirements/2024.04.txt +0 -0
  135. {modal-0.74.58 → modal-0.74.60}/modal/requirements/2024.10.txt +0 -0
  136. {modal-0.74.58 → modal-0.74.60}/modal/requirements/PREVIEW.txt +0 -0
  137. {modal-0.74.58 → modal-0.74.60}/modal/requirements/README.md +0 -0
  138. {modal-0.74.58 → modal-0.74.60}/modal/requirements/base-images.json +0 -0
  139. {modal-0.74.58 → modal-0.74.60}/modal/retries.py +0 -0
  140. {modal-0.74.58 → modal-0.74.60}/modal/runner.py +0 -0
  141. {modal-0.74.58 → modal-0.74.60}/modal/runner.pyi +0 -0
  142. {modal-0.74.58 → modal-0.74.60}/modal/running_app.py +0 -0
  143. {modal-0.74.58 → modal-0.74.60}/modal/sandbox.py +0 -0
  144. {modal-0.74.58 → modal-0.74.60}/modal/sandbox.pyi +0 -0
  145. {modal-0.74.58 → modal-0.74.60}/modal/schedule.py +0 -0
  146. {modal-0.74.58 → modal-0.74.60}/modal/scheduler_placement.py +0 -0
  147. {modal-0.74.58 → modal-0.74.60}/modal/secret.py +0 -0
  148. {modal-0.74.58 → modal-0.74.60}/modal/secret.pyi +0 -0
  149. {modal-0.74.58 → modal-0.74.60}/modal/serving.py +0 -0
  150. {modal-0.74.58 → modal-0.74.60}/modal/serving.pyi +0 -0
  151. {modal-0.74.58 → modal-0.74.60}/modal/snapshot.py +0 -0
  152. {modal-0.74.58 → modal-0.74.60}/modal/snapshot.pyi +0 -0
  153. {modal-0.74.58 → modal-0.74.60}/modal/stream_type.py +0 -0
  154. {modal-0.74.58 → modal-0.74.60}/modal/token_flow.py +0 -0
  155. {modal-0.74.58 → modal-0.74.60}/modal/token_flow.pyi +0 -0
  156. {modal-0.74.58 → modal-0.74.60}/modal.egg-info/SOURCES.txt +0 -0
  157. {modal-0.74.58 → modal-0.74.60}/modal.egg-info/dependency_links.txt +0 -0
  158. {modal-0.74.58 → modal-0.74.60}/modal.egg-info/entry_points.txt +0 -0
  159. {modal-0.74.58 → modal-0.74.60}/modal.egg-info/top_level.txt +0 -0
  160. {modal-0.74.58 → modal-0.74.60}/modal_docs/__init__.py +0 -0
  161. {modal-0.74.58 → modal-0.74.60}/modal_docs/gen_cli_docs.py +0 -0
  162. {modal-0.74.58 → modal-0.74.60}/modal_docs/gen_reference_docs.py +0 -0
  163. {modal-0.74.58 → modal-0.74.60}/modal_docs/mdmd/__init__.py +0 -0
  164. {modal-0.74.58 → modal-0.74.60}/modal_docs/mdmd/mdmd.py +0 -0
  165. {modal-0.74.58 → modal-0.74.60}/modal_docs/mdmd/signatures.py +0 -0
  166. {modal-0.74.58 → modal-0.74.60}/modal_proto/__init__.py +0 -0
  167. {modal-0.74.58 → modal-0.74.60}/modal_proto/api.proto +0 -0
  168. {modal-0.74.58 → modal-0.74.60}/modal_proto/api_grpc.py +0 -0
  169. {modal-0.74.58 → modal-0.74.60}/modal_proto/api_pb2.py +0 -0
  170. {modal-0.74.58 → modal-0.74.60}/modal_proto/api_pb2.pyi +0 -0
  171. {modal-0.74.58 → modal-0.74.60}/modal_proto/api_pb2_grpc.py +0 -0
  172. {modal-0.74.58 → modal-0.74.60}/modal_proto/api_pb2_grpc.pyi +0 -0
  173. {modal-0.74.58 → modal-0.74.60}/modal_proto/modal_api_grpc.py +0 -0
  174. {modal-0.74.58 → modal-0.74.60}/modal_proto/modal_options_grpc.py +0 -0
  175. {modal-0.74.58 → modal-0.74.60}/modal_proto/options.proto +0 -0
  176. {modal-0.74.58 → modal-0.74.60}/modal_proto/options_grpc.py +0 -0
  177. {modal-0.74.58 → modal-0.74.60}/modal_proto/options_pb2.py +0 -0
  178. {modal-0.74.58 → modal-0.74.60}/modal_proto/options_pb2.pyi +0 -0
  179. {modal-0.74.58 → modal-0.74.60}/modal_proto/options_pb2_grpc.py +0 -0
  180. {modal-0.74.58 → modal-0.74.60}/modal_proto/options_pb2_grpc.pyi +0 -0
  181. {modal-0.74.58 → modal-0.74.60}/modal_proto/py.typed +0 -0
  182. {modal-0.74.58 → modal-0.74.60}/modal_version/__init__.py +0 -0
  183. {modal-0.74.58 → modal-0.74.60}/modal_version/__main__.py +0 -0
  184. {modal-0.74.58 → modal-0.74.60}/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.60
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ Requires-Dist: click>=8.1.0
22
22
  Requires-Dist: grpclib==0.4.7
23
23
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
24
24
  Requires-Dist: rich>=12.0.0
25
- Requires-Dist: synchronicity~=0.9.10
25
+ Requires-Dist: synchronicity~=0.9.12
26
26
  Requires-Dist: toml
27
27
  Requires-Dist: typer>=0.9
28
28
  Requires-Dist: types-certifi
@@ -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.60"
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.60"
90
90
  ): ...
91
91
  def is_closed(self) -> bool: ...
92
92
  @property
@@ -1,5 +1,5 @@
1
1
  # Copyright Modal Labs 2022
2
- from collections.abc import AsyncIterator
2
+ from collections.abc import AsyncIterator, Mapping
3
3
  from typing import Any, Optional
4
4
 
5
5
  from grpclib import GRPCError
@@ -244,9 +244,16 @@ class _Dict(_Object, type_prefix="di"):
244
244
  return value
245
245
 
246
246
  @live_method
247
- async def update(self, **kwargs) -> None:
247
+ async def update(self, other: Optional[Mapping] = None, /, **kwargs) -> None:
248
248
  """Update the dictionary with additional items."""
249
- serialized = _serialize_dict(kwargs)
249
+ # Support the Python dict.update API
250
+ # https://docs.python.org/3/library/stdtypes.html#dict.update
251
+ contents = {}
252
+ if other:
253
+ contents.update({k: other[k] for k in other.keys()})
254
+ if kwargs:
255
+ contents.update(kwargs)
256
+ serialized = _serialize_dict(contents)
250
257
  req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized)
251
258
  try:
252
259
  await retry_transient_errors(self._client.stub.DictUpdate, req)
@@ -48,7 +48,7 @@ class _Dict(modal._object._Object):
48
48
  async def contains(self, key: typing.Any) -> bool: ...
49
49
  async def len(self) -> int: ...
50
50
  async def __getitem__(self, key: typing.Any) -> typing.Any: ...
51
- async def update(self, **kwargs) -> None: ...
51
+ async def update(self, other: typing.Optional[collections.abc.Mapping] = None, /, **kwargs) -> None: ...
52
52
  async def put(self, key: typing.Any, value: typing.Any) -> None: ...
53
53
  async def __setitem__(self, key: typing.Any, value: typing.Any) -> None: ...
54
54
  async def pop(self, key: typing.Any) -> typing.Any: ...
@@ -155,8 +155,8 @@ class Dict(modal.object.Object):
155
155
  __getitem__: ____getitem___spec[typing_extensions.Self]
156
156
 
157
157
  class __update_spec(typing_extensions.Protocol[SUPERSELF]):
158
- def __call__(self, /, **kwargs) -> None: ...
159
- async def aio(self, /, **kwargs) -> None: ...
158
+ def __call__(self, other: typing.Optional[collections.abc.Mapping] = None, /, **kwargs) -> None: ...
159
+ async def aio(self, other: typing.Optional[collections.abc.Mapping] = None, /, **kwargs) -> None: ...
160
160
 
161
161
  update: __update_spec[typing_extensions.Self]
162
162
 
@@ -227,11 +227,11 @@ class Function(
227
227
 
228
228
  _call_generator_nowait: ___call_generator_nowait_spec[typing_extensions.Self]
229
229
 
230
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
230
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
231
231
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
232
232
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
233
233
 
234
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
234
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
235
235
 
236
236
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
237
237
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -246,12 +246,12 @@ class Function(
246
246
  self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
247
247
  ) -> modal._functions.OriginalReturnType: ...
248
248
 
249
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
249
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
250
250
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
251
251
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
252
252
 
253
253
  _experimental_spawn: ___experimental_spawn_spec[
254
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
254
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
255
255
  ]
256
256
 
257
257
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -260,11 +260,11 @@ class Function(
260
260
 
261
261
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
262
262
 
263
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
263
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
264
264
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
265
265
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
266
266
 
267
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
267
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
268
268
 
269
269
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
270
270
 
@@ -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.60
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ Requires-Dist: click>=8.1.0
22
22
  Requires-Dist: grpclib==0.4.7
23
23
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
24
24
  Requires-Dist: rich>=12.0.0
25
- Requires-Dist: synchronicity~=0.9.10
25
+ Requires-Dist: synchronicity~=0.9.12
26
26
  Requires-Dist: toml
27
27
  Requires-Dist: typer>=0.9
28
28
  Requires-Dist: types-certifi
@@ -4,7 +4,7 @@ click>=8.1.0
4
4
  grpclib==0.4.7
5
5
  protobuf!=4.24.0,<7.0,>=3.19
6
6
  rich>=12.0.0
7
- synchronicity~=0.9.10
7
+ synchronicity~=0.9.12
8
8
  toml
9
9
  typer>=0.9
10
10
  types-certifi
@@ -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 = 60 # git: 7229b14
@@ -20,7 +20,7 @@ dependencies = [
20
20
  "grpclib==0.4.7",
21
21
  "protobuf>=3.19,<7.0,!=4.24.0",
22
22
  "rich>=12.0.0",
23
- "synchronicity~=0.9.10",
23
+ "synchronicity~=0.9.12",
24
24
  "toml",
25
25
  "typer>=0.9",
26
26
  "types-certifi",
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