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