modal 1.1.2.dev26__tar.gz → 1.1.2.dev28__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 (190) hide show
  1. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/PKG-INFO +1 -1
  2. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/blob_utils.py +83 -24
  3. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/client.pyi +2 -2
  4. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/functions.pyi +6 -6
  5. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/volume.py +7 -9
  6. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal.egg-info/PKG-INFO +1 -1
  7. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/api.proto +18 -0
  8. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/api_grpc.py +32 -0
  9. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/api_pb2.py +627 -597
  10. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/api_pb2.pyi +53 -0
  11. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/api_pb2_grpc.py +67 -0
  12. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/api_pb2_grpc.pyi +22 -0
  13. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/modal_api_grpc.py +2 -0
  14. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_version/__init__.py +1 -1
  15. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/LICENSE +0 -0
  16. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/README.md +0 -0
  17. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/__init__.py +0 -0
  18. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/__main__.py +0 -0
  19. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_clustered_functions.py +0 -0
  20. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_clustered_functions.pyi +0 -0
  21. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_container_entrypoint.py +0 -0
  22. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_functions.py +0 -0
  23. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_ipython.py +0 -0
  24. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_location.py +0 -0
  25. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_object.py +0 -0
  26. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_output.py +0 -0
  27. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_partial_function.py +0 -0
  28. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_pty.py +0 -0
  29. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_resolver.py +0 -0
  30. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_resources.py +0 -0
  31. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/__init__.py +0 -0
  32. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/asgi.py +0 -0
  33. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/container_io_manager.py +0 -0
  34. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/container_io_manager.pyi +0 -0
  35. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/execution_context.py +0 -0
  36. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/execution_context.pyi +0 -0
  37. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  38. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/telemetry.py +0 -0
  39. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_runtime/user_code_imports.py +0 -0
  40. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_serialization.py +0 -0
  41. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_traceback.py +0 -0
  42. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_tunnel.py +0 -0
  43. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_tunnel.pyi +0 -0
  44. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_type_manager.py +0 -0
  45. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/__init__.py +0 -0
  46. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/app_utils.py +0 -0
  47. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/async_utils.py +0 -0
  48. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/auth_token_manager.py +0 -0
  49. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/bytes_io_segment_payload.py +0 -0
  50. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/deprecation.py +0 -0
  51. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/docker_utils.py +0 -0
  52. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/function_utils.py +0 -0
  53. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/git_utils.py +0 -0
  54. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/grpc_testing.py +0 -0
  55. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/grpc_utils.py +0 -0
  56. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/hash_utils.py +0 -0
  57. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/http_utils.py +0 -0
  58. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/jwt_utils.py +0 -0
  59. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/logger.py +0 -0
  60. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/mount_utils.py +0 -0
  61. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/name_utils.py +0 -0
  62. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/package_utils.py +0 -0
  63. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/pattern_utils.py +0 -0
  64. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/rand_pb_testing.py +0 -0
  65. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/shell_utils.py +0 -0
  66. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_utils/time_utils.py +0 -0
  67. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_vendor/__init__.py +0 -0
  68. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  69. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_vendor/cloudpickle.py +0 -0
  70. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_vendor/tblib.py +0 -0
  71. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/_watcher.py +0 -0
  72. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/app.py +0 -0
  73. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/app.pyi +0 -0
  74. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/2023.12.312.txt +0 -0
  75. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/2023.12.txt +0 -0
  76. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/2024.04.txt +0 -0
  77. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/2024.10.txt +0 -0
  78. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/2025.06.txt +0 -0
  79. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/PREVIEW.txt +0 -0
  80. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/README.md +0 -0
  81. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/builder/base-images.json +0 -0
  82. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/call_graph.py +0 -0
  83. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/__init__.py +0 -0
  84. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/_download.py +0 -0
  85. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/_traceback.py +0 -0
  86. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/app.py +0 -0
  87. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/cluster.py +0 -0
  88. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/config.py +0 -0
  89. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/container.py +0 -0
  90. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/dict.py +0 -0
  91. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/entry_point.py +0 -0
  92. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/environment.py +0 -0
  93. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/import_refs.py +0 -0
  94. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/launch.py +0 -0
  95. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/network_file_system.py +0 -0
  96. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/profile.py +0 -0
  97. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/programs/__init__.py +0 -0
  98. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/programs/launch_instance_ssh.py +0 -0
  99. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/programs/run_jupyter.py +0 -0
  100. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/programs/run_marimo.py +0 -0
  101. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/programs/vscode.py +0 -0
  102. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/queues.py +0 -0
  103. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/run.py +0 -0
  104. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/secret.py +0 -0
  105. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/token.py +0 -0
  106. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/utils.py +0 -0
  107. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cli/volume.py +0 -0
  108. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/client.py +0 -0
  109. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cloud_bucket_mount.py +0 -0
  110. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cloud_bucket_mount.pyi +0 -0
  111. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cls.py +0 -0
  112. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/cls.pyi +0 -0
  113. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/config.py +0 -0
  114. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/container_process.py +0 -0
  115. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/container_process.pyi +0 -0
  116. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/dict.py +0 -0
  117. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/dict.pyi +0 -0
  118. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/environments.py +0 -0
  119. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/environments.pyi +0 -0
  120. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/exception.py +0 -0
  121. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/experimental/__init__.py +0 -0
  122. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/experimental/flash.py +0 -0
  123. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/experimental/flash.pyi +0 -0
  124. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/experimental/ipython.py +0 -0
  125. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/file_io.py +0 -0
  126. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/file_io.pyi +0 -0
  127. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/file_pattern_matcher.py +0 -0
  128. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/functions.py +0 -0
  129. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/gpu.py +0 -0
  130. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/image.py +0 -0
  131. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/image.pyi +0 -0
  132. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/io_streams.py +0 -0
  133. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/io_streams.pyi +0 -0
  134. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/mount.py +0 -0
  135. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/mount.pyi +0 -0
  136. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/network_file_system.py +0 -0
  137. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/network_file_system.pyi +0 -0
  138. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/object.py +0 -0
  139. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/object.pyi +0 -0
  140. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/output.py +0 -0
  141. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/parallel_map.py +0 -0
  142. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/parallel_map.pyi +0 -0
  143. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/partial_function.py +0 -0
  144. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/partial_function.pyi +0 -0
  145. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/proxy.py +0 -0
  146. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/proxy.pyi +0 -0
  147. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/py.typed +0 -0
  148. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/queue.py +0 -0
  149. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/queue.pyi +0 -0
  150. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/retries.py +0 -0
  151. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/runner.py +0 -0
  152. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/runner.pyi +0 -0
  153. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/running_app.py +0 -0
  154. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/sandbox.py +0 -0
  155. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/sandbox.pyi +0 -0
  156. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/schedule.py +0 -0
  157. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/scheduler_placement.py +0 -0
  158. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/secret.py +0 -0
  159. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/secret.pyi +0 -0
  160. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/serving.py +0 -0
  161. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/serving.pyi +0 -0
  162. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/snapshot.py +0 -0
  163. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/snapshot.pyi +0 -0
  164. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/stream_type.py +0 -0
  165. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/token_flow.py +0 -0
  166. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/token_flow.pyi +0 -0
  167. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal/volume.pyi +0 -0
  168. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal.egg-info/SOURCES.txt +0 -0
  169. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal.egg-info/dependency_links.txt +0 -0
  170. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal.egg-info/entry_points.txt +0 -0
  171. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal.egg-info/requires.txt +0 -0
  172. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal.egg-info/top_level.txt +0 -0
  173. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_docs/__init__.py +0 -0
  174. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_docs/gen_cli_docs.py +0 -0
  175. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_docs/gen_reference_docs.py +0 -0
  176. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_docs/mdmd/__init__.py +0 -0
  177. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_docs/mdmd/mdmd.py +0 -0
  178. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_docs/mdmd/signatures.py +0 -0
  179. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/__init__.py +0 -0
  180. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/modal_options_grpc.py +0 -0
  181. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/options.proto +0 -0
  182. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/options_grpc.py +0 -0
  183. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/options_pb2.py +0 -0
  184. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/options_pb2.pyi +0 -0
  185. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/options_pb2_grpc.py +0 -0
  186. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/options_pb2_grpc.pyi +0 -0
  187. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_proto/py.typed +0 -0
  188. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/modal_version/__main__.py +0 -0
  189. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/pyproject.toml +0 -0
  190. {modal-1.1.2.dev26 → modal-1.1.2.dev28}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.2.dev26
3
+ Version: 1.1.2.dev28
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -444,14 +444,24 @@ def get_file_upload_spec_from_fileobj(fp: BinaryIO, mount_filename: PurePosixPat
444
444
  _FileUploadSource2 = Callable[[], ContextManager[BinaryIO]]
445
445
 
446
446
 
447
+ @dataclasses.dataclass
448
+ class FileUploadBlock:
449
+ # The start (byte offset, inclusive) of the block within the file
450
+ start: int
451
+ # The end (byte offset, exclusive) of the block, after having removed any trailing zeroes
452
+ end: int
453
+ # Raw (unencoded 32 byte) SHA256 sum of the block, not including trailing zeroes
454
+ contents_sha256: bytes
455
+
456
+
447
457
  @dataclasses.dataclass
448
458
  class FileUploadSpec2:
449
459
  source: _FileUploadSource2
450
460
  source_description: Union[str, Path]
451
461
 
452
462
  path: str
453
- # Raw (unencoded 32 byte) SHA256 sum per 8MiB file block
454
- blocks_sha256: list[bytes]
463
+ # 8MiB file blocks
464
+ blocks: list[FileUploadBlock]
455
465
  mode: int # file permission bits (last 12 bits of st_mode)
456
466
  size: int
457
467
 
@@ -522,53 +532,102 @@ class FileUploadSpec2:
522
532
  source_fp.seek(0, os.SEEK_END)
523
533
  size = source_fp.tell()
524
534
 
525
- blocks_sha256 = await hash_blocks_sha256(source, size, hash_semaphore)
535
+ blocks = await _gather_blocks(source, size, hash_semaphore)
526
536
 
527
537
  return FileUploadSpec2(
528
538
  source=source,
529
539
  source_description=source_description,
530
540
  path=mount_filename.as_posix(),
531
- blocks_sha256=blocks_sha256,
541
+ blocks=blocks,
532
542
  mode=mode & 0o7777,
533
543
  size=size,
534
544
  )
535
545
 
536
546
 
537
- async def hash_blocks_sha256(
547
+ async def _gather_blocks(
538
548
  source: _FileUploadSource2,
539
549
  size: int,
540
550
  hash_semaphore: asyncio.Semaphore,
541
- ) -> list[bytes]:
551
+ ) -> list[FileUploadBlock]:
542
552
  def ceildiv(a: int, b: int) -> int:
543
553
  return -(a // -b)
544
554
 
545
555
  num_blocks = ceildiv(size, BLOCK_SIZE)
546
556
 
547
- def blocking_hash_block_sha256(block_idx: int) -> bytes:
548
- sha256_hash = hashlib.sha256()
549
- block_start = block_idx * BLOCK_SIZE
557
+ async def gather_block(block_idx: int) -> FileUploadBlock:
558
+ async with hash_semaphore:
559
+ return await asyncio.to_thread(_gather_block, source, block_idx)
550
560
 
551
- with source() as block_fp:
552
- block_fp.seek(block_start)
561
+ tasks = (gather_block(idx) for idx in range(num_blocks))
562
+ return await asyncio.gather(*tasks)
553
563
 
554
- num_bytes_read = 0
555
- while num_bytes_read < BLOCK_SIZE:
556
- chunk = block_fp.read(BLOCK_SIZE - num_bytes_read)
557
564
 
558
- if not chunk:
559
- break
565
+ def _gather_block(source: _FileUploadSource2, block_idx: int) -> FileUploadBlock:
566
+ start = block_idx * BLOCK_SIZE
567
+ end = _find_end_of_block(source, start, start + BLOCK_SIZE)
568
+ contents_sha256 = _hash_range_sha256(source, start, end)
569
+ return FileUploadBlock(start=start, end=end, contents_sha256=contents_sha256)
560
570
 
561
- num_bytes_read += len(chunk)
562
- sha256_hash.update(chunk)
563
571
 
564
- return sha256_hash.digest()
572
+ def _hash_range_sha256(source: _FileUploadSource2, start, end):
573
+ sha256_hash = hashlib.sha256()
574
+ range_size = end - start
565
575
 
566
- async def hash_block_sha256(block_idx: int) -> bytes:
567
- async with hash_semaphore:
568
- return await asyncio.to_thread(blocking_hash_block_sha256, block_idx)
576
+ with source() as fp:
577
+ fp.seek(start)
578
+
579
+ num_bytes_read = 0
580
+ while num_bytes_read < range_size:
581
+ chunk = fp.read(range_size - num_bytes_read)
582
+
583
+ if not chunk:
584
+ break
585
+
586
+ num_bytes_read += len(chunk)
587
+ sha256_hash.update(chunk)
588
+
589
+ return sha256_hash.digest()
590
+
591
+
592
+ def _find_end_of_block(source: _FileUploadSource2, start: int, end: int) -> Optional[int]:
593
+ """Finds the appropriate end of a block, which is the index of the byte just past the last non-zero byte in the
594
+ block.
595
+
596
+ >>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0"), 0, 1024)
597
+ 6
598
+ >>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0"), 3, 1024)
599
+ 6
600
+ >>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0"), 0, 3)
601
+ 4
602
+ >>> _find_end_of_block(lambda: BytesIO(b"abc123\0\0\0a"), 0, 9)
603
+ 6
604
+ >>> _find_end_of_block(lambda: BytesIO(b"\0\0\0"), 0, 3)
605
+ 0
606
+ >>> _find_end_of_block(lambda: BytesIO(b"\0\0\0\0\0\0"), 3, 6)
607
+ 3
608
+ >>> _find_end_of_block(lambda: BytesIO(b""), 0, 1024)
609
+ 0
610
+ """
611
+ size = end - start
612
+ new_end = start
569
613
 
570
- tasks = (hash_block_sha256(idx) for idx in range(num_blocks))
571
- return await asyncio.gather(*tasks)
614
+ with source() as block_fp:
615
+ block_fp.seek(start)
616
+
617
+ num_bytes_read = 0
618
+ while num_bytes_read < size:
619
+ chunk = block_fp.read(size - num_bytes_read)
620
+
621
+ if not chunk:
622
+ break
623
+
624
+ stripped_chunk = chunk.rstrip(b"\0")
625
+ if stripped_chunk:
626
+ new_end = start + num_bytes_read + len(stripped_chunk)
627
+
628
+ num_bytes_read += len(chunk)
629
+
630
+ return new_end
572
631
 
573
632
 
574
633
  def use_md5(url: str) -> bool:
@@ -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.1.2.dev26",
36
+ version: str = "1.1.2.dev28",
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.1.2.dev26",
167
+ version: str = "1.1.2.dev28",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -433,7 +433,7 @@ class Function(
433
433
 
434
434
  _call_generator: ___call_generator_spec[typing_extensions.Self]
435
435
 
436
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
436
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
437
437
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
438
438
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
439
439
  ...
@@ -442,7 +442,7 @@ class Function(
442
442
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
443
443
  ...
444
444
 
445
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
445
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
446
446
 
447
447
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
448
448
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -469,7 +469,7 @@ class Function(
469
469
  """
470
470
  ...
471
471
 
472
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
472
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
473
473
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
474
474
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
475
475
 
@@ -493,7 +493,7 @@ class Function(
493
493
  ...
494
494
 
495
495
  _experimental_spawn: ___experimental_spawn_spec[
496
- modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
496
+ modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
497
497
  ]
498
498
 
499
499
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -502,7 +502,7 @@ class Function(
502
502
 
503
503
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
504
504
 
505
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
505
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
506
506
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
507
507
  """Calls the function with the given arguments, without waiting for the results.
508
508
 
@@ -523,7 +523,7 @@ class Function(
523
523
  """
524
524
  ...
525
525
 
526
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
526
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
527
527
 
528
528
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
529
529
  """Return the inner Python object wrapped by this Modal Function."""
@@ -1122,9 +1122,9 @@ class _VolumeUploadContextManager2(_AbstractVolumeUploadContextManager):
1122
1122
  for file_spec in file_specs:
1123
1123
  blocks = [
1124
1124
  api_pb2.VolumePutFiles2Request.Block(
1125
- contents_sha256=block_sha256, put_response=put_responses.get(block_sha256)
1125
+ contents_sha256=block.contents_sha256, put_response=put_responses.get(block.contents_sha256)
1126
1126
  )
1127
- for block_sha256 in file_spec.blocks_sha256
1127
+ for block in file_spec.blocks
1128
1128
  ]
1129
1129
  files.append(
1130
1130
  api_pb2.VolumePutFiles2Request.File(
@@ -1181,7 +1181,7 @@ async def _put_missing_blocks(
1181
1181
  # TODO(dflemstr): Type is `api_pb2.VolumePutFiles2Response.MissingBlock` but synchronicity gets confused
1182
1182
  # by the nested class (?)
1183
1183
  missing_block,
1184
- ) -> (bytes, bytes):
1184
+ ) -> tuple[bytes, bytes]:
1185
1185
  # Lazily import to keep the eager loading time of this module down
1186
1186
  from ._utils.bytes_io_segment_payload import BytesIOSegmentPayload
1187
1187
 
@@ -1190,9 +1190,7 @@ async def _put_missing_blocks(
1190
1190
  file_spec = file_specs[missing_block.file_index]
1191
1191
  # TODO(dflemstr): What if the underlying file has changed here in the meantime; should we check the
1192
1192
  # hash again just to be sure?
1193
- block_sha256 = file_spec.blocks_sha256[missing_block.block_index]
1194
- block_start = missing_block.block_index * BLOCK_SIZE
1195
- block_length = min(BLOCK_SIZE, file_spec.size - block_start)
1193
+ block = file_spec.blocks[missing_block.block_index]
1196
1194
 
1197
1195
  if file_spec.path not in file_progresses:
1198
1196
  file_task_id = progress_cb(name=file_spec.path, size=file_spec.size)
@@ -1216,8 +1214,8 @@ async def _put_missing_blocks(
1216
1214
  with file_spec.source() as source_fp:
1217
1215
  payload = BytesIOSegmentPayload(
1218
1216
  source_fp,
1219
- block_start,
1220
- block_length,
1217
+ block.start,
1218
+ block.end - block.start,
1221
1219
  # limit chunk size somewhat to not keep event loop busy for too long
1222
1220
  chunk_size=256 * 1024,
1223
1221
  progress_report_cb=task_progress_cb,
@@ -1229,7 +1227,7 @@ async def _put_missing_blocks(
1229
1227
  if len(file_progress.pending_blocks) == 0:
1230
1228
  task_progress_cb(complete=True)
1231
1229
 
1232
- return block_sha256, resp_data
1230
+ return block.contents_sha256, resp_data
1233
1231
 
1234
1232
  tasks = [asyncio.create_task(put_missing_block(missing_block)) for missing_block in missing_blocks]
1235
1233
  for task_result in asyncio.as_completed(tasks):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.2.dev26
3
+ Version: 1.1.2.dev28
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1510,6 +1510,16 @@ message FunctionCallCancelRequest {
1510
1510
  optional string function_id = 3; // Only provided for sync input cancellation on the input plane. Async input cancellation does not provide this field this.
1511
1511
  }
1512
1512
 
1513
+ message FunctionCallFromIdRequest {
1514
+ string function_call_id = 1;
1515
+ }
1516
+
1517
+ // Everything you need to build a FunctionCallHandler.
1518
+ message FunctionCallFromIdResponse {
1519
+ string function_call_id = 1;
1520
+ int32 num_inputs = 2;
1521
+ }
1522
+
1513
1523
  message FunctionCallGetDataRequest {
1514
1524
  oneof call_info {
1515
1525
  string function_call_id = 1;
@@ -1648,6 +1658,12 @@ message FunctionExtended {
1648
1658
  }
1649
1659
  }
1650
1660
 
1661
+ message FunctionFinishInputsRequest {
1662
+ string function_id = 1;
1663
+ string function_call_id = 2;
1664
+ uint32 num_inputs = 3;
1665
+ }
1666
+
1651
1667
 
1652
1668
  message FunctionGetCallGraphRequest {
1653
1669
  // TODO: use input_id once we switch client submit API to return those.
@@ -3494,11 +3510,13 @@ service ModalClient {
3494
3510
  rpc FunctionAsyncInvoke(FunctionAsyncInvokeRequest) returns (FunctionAsyncInvokeResponse);
3495
3511
  rpc FunctionBindParams(FunctionBindParamsRequest) returns (FunctionBindParamsResponse);
3496
3512
  rpc FunctionCallCancel(FunctionCallCancelRequest) returns (google.protobuf.Empty);
3513
+ rpc FunctionCallFromId(FunctionCallFromIdRequest) returns (FunctionCallFromIdResponse);
3497
3514
  rpc FunctionCallGetDataIn(FunctionCallGetDataRequest) returns (stream DataChunk);
3498
3515
  rpc FunctionCallGetDataOut(FunctionCallGetDataRequest) returns (stream DataChunk);
3499
3516
  rpc FunctionCallList(FunctionCallListRequest) returns (FunctionCallListResponse);
3500
3517
  rpc FunctionCallPutDataOut(FunctionCallPutDataRequest) returns (google.protobuf.Empty);
3501
3518
  rpc FunctionCreate(FunctionCreateRequest) returns (FunctionCreateResponse);
3519
+ rpc FunctionFinishInputs(FunctionFinishInputsRequest) returns (google.protobuf.Empty); // For map RPCs, to signal that all inputs have been sent
3502
3520
  rpc FunctionGet(FunctionGetRequest) returns (FunctionGetResponse);
3503
3521
  rpc FunctionGetCallGraph(FunctionGetCallGraphRequest) returns (FunctionGetCallGraphResponse);
3504
3522
  rpc FunctionGetCurrentStats(FunctionGetCurrentStatsRequest) returns (FunctionStats);
@@ -274,6 +274,10 @@ class ModalClientBase(abc.ABC):
274
274
  async def FunctionCallCancel(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.FunctionCallCancelRequest, google.protobuf.empty_pb2.Empty]') -> None:
275
275
  pass
276
276
 
277
+ @abc.abstractmethod
278
+ async def FunctionCallFromId(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.FunctionCallFromIdRequest, modal_proto.api_pb2.FunctionCallFromIdResponse]') -> None:
279
+ pass
280
+
277
281
  @abc.abstractmethod
278
282
  async def FunctionCallGetDataIn(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.FunctionCallGetDataRequest, modal_proto.api_pb2.DataChunk]') -> None:
279
283
  pass
@@ -294,6 +298,10 @@ class ModalClientBase(abc.ABC):
294
298
  async def FunctionCreate(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.FunctionCreateRequest, modal_proto.api_pb2.FunctionCreateResponse]') -> None:
295
299
  pass
296
300
 
301
+ @abc.abstractmethod
302
+ async def FunctionFinishInputs(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.FunctionFinishInputsRequest, google.protobuf.empty_pb2.Empty]') -> None:
303
+ pass
304
+
297
305
  @abc.abstractmethod
298
306
  async def FunctionGet(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.FunctionGetRequest, modal_proto.api_pb2.FunctionGetResponse]') -> None:
299
307
  pass
@@ -1060,6 +1068,12 @@ class ModalClientBase(abc.ABC):
1060
1068
  modal_proto.api_pb2.FunctionCallCancelRequest,
1061
1069
  google.protobuf.empty_pb2.Empty,
1062
1070
  ),
1071
+ '/modal.client.ModalClient/FunctionCallFromId': grpclib.const.Handler(
1072
+ self.FunctionCallFromId,
1073
+ grpclib.const.Cardinality.UNARY_UNARY,
1074
+ modal_proto.api_pb2.FunctionCallFromIdRequest,
1075
+ modal_proto.api_pb2.FunctionCallFromIdResponse,
1076
+ ),
1063
1077
  '/modal.client.ModalClient/FunctionCallGetDataIn': grpclib.const.Handler(
1064
1078
  self.FunctionCallGetDataIn,
1065
1079
  grpclib.const.Cardinality.UNARY_STREAM,
@@ -1090,6 +1104,12 @@ class ModalClientBase(abc.ABC):
1090
1104
  modal_proto.api_pb2.FunctionCreateRequest,
1091
1105
  modal_proto.api_pb2.FunctionCreateResponse,
1092
1106
  ),
1107
+ '/modal.client.ModalClient/FunctionFinishInputs': grpclib.const.Handler(
1108
+ self.FunctionFinishInputs,
1109
+ grpclib.const.Cardinality.UNARY_UNARY,
1110
+ modal_proto.api_pb2.FunctionFinishInputsRequest,
1111
+ google.protobuf.empty_pb2.Empty,
1112
+ ),
1093
1113
  '/modal.client.ModalClient/FunctionGet': grpclib.const.Handler(
1094
1114
  self.FunctionGet,
1095
1115
  grpclib.const.Cardinality.UNARY_UNARY,
@@ -2050,6 +2070,12 @@ class ModalClientStub:
2050
2070
  modal_proto.api_pb2.FunctionCallCancelRequest,
2051
2071
  google.protobuf.empty_pb2.Empty,
2052
2072
  )
2073
+ self.FunctionCallFromId = grpclib.client.UnaryUnaryMethod(
2074
+ channel,
2075
+ '/modal.client.ModalClient/FunctionCallFromId',
2076
+ modal_proto.api_pb2.FunctionCallFromIdRequest,
2077
+ modal_proto.api_pb2.FunctionCallFromIdResponse,
2078
+ )
2053
2079
  self.FunctionCallGetDataIn = grpclib.client.UnaryStreamMethod(
2054
2080
  channel,
2055
2081
  '/modal.client.ModalClient/FunctionCallGetDataIn',
@@ -2080,6 +2106,12 @@ class ModalClientStub:
2080
2106
  modal_proto.api_pb2.FunctionCreateRequest,
2081
2107
  modal_proto.api_pb2.FunctionCreateResponse,
2082
2108
  )
2109
+ self.FunctionFinishInputs = grpclib.client.UnaryUnaryMethod(
2110
+ channel,
2111
+ '/modal.client.ModalClient/FunctionFinishInputs',
2112
+ modal_proto.api_pb2.FunctionFinishInputsRequest,
2113
+ google.protobuf.empty_pb2.Empty,
2114
+ )
2083
2115
  self.FunctionGet = grpclib.client.UnaryUnaryMethod(
2084
2116
  channel,
2085
2117
  '/modal.client.ModalClient/FunctionGet',