modal 1.1.2.dev27__tar.gz → 1.1.2.dev29__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.dev29}/PKG-INFO +1 -1
  2. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/blob_utils.py +83 -24
  3. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/client.pyi +2 -2
  4. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/dict.py +57 -3
  5. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/dict.pyi +107 -0
  6. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/functions.pyi +6 -6
  7. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/queue.py +57 -3
  8. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/queue.pyi +107 -0
  9. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/secret.py +60 -2
  10. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/secret.pyi +116 -0
  11. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/volume.py +62 -11
  12. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/volume.pyi +104 -0
  13. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal.egg-info/PKG-INFO +1 -1
  14. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_version/__init__.py +1 -1
  15. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/LICENSE +0 -0
  16. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/README.md +0 -0
  17. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/__init__.py +0 -0
  18. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/__main__.py +0 -0
  19. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_clustered_functions.py +0 -0
  20. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_clustered_functions.pyi +0 -0
  21. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_container_entrypoint.py +0 -0
  22. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_functions.py +0 -0
  23. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_ipython.py +0 -0
  24. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_location.py +0 -0
  25. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_object.py +0 -0
  26. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_output.py +0 -0
  27. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_partial_function.py +0 -0
  28. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_pty.py +0 -0
  29. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_resolver.py +0 -0
  30. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_resources.py +0 -0
  31. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/__init__.py +0 -0
  32. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/asgi.py +0 -0
  33. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/container_io_manager.py +0 -0
  34. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/container_io_manager.pyi +0 -0
  35. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/execution_context.py +0 -0
  36. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/execution_context.pyi +0 -0
  37. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  38. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/telemetry.py +0 -0
  39. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_runtime/user_code_imports.py +0 -0
  40. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_serialization.py +0 -0
  41. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_traceback.py +0 -0
  42. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_tunnel.py +0 -0
  43. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_tunnel.pyi +0 -0
  44. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_type_manager.py +0 -0
  45. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/__init__.py +0 -0
  46. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/app_utils.py +0 -0
  47. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/async_utils.py +0 -0
  48. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/auth_token_manager.py +0 -0
  49. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/bytes_io_segment_payload.py +0 -0
  50. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/deprecation.py +0 -0
  51. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/docker_utils.py +0 -0
  52. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/function_utils.py +0 -0
  53. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/git_utils.py +0 -0
  54. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/grpc_testing.py +0 -0
  55. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/grpc_utils.py +0 -0
  56. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/hash_utils.py +0 -0
  57. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/http_utils.py +0 -0
  58. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/jwt_utils.py +0 -0
  59. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/logger.py +0 -0
  60. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/mount_utils.py +0 -0
  61. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/name_utils.py +0 -0
  62. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/package_utils.py +0 -0
  63. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/pattern_utils.py +0 -0
  64. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/rand_pb_testing.py +0 -0
  65. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/shell_utils.py +0 -0
  66. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_utils/time_utils.py +0 -0
  67. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_vendor/__init__.py +0 -0
  68. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  69. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_vendor/cloudpickle.py +0 -0
  70. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_vendor/tblib.py +0 -0
  71. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/_watcher.py +0 -0
  72. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/app.py +0 -0
  73. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/app.pyi +0 -0
  74. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/2023.12.312.txt +0 -0
  75. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/2023.12.txt +0 -0
  76. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/2024.04.txt +0 -0
  77. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/2024.10.txt +0 -0
  78. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/2025.06.txt +0 -0
  79. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/PREVIEW.txt +0 -0
  80. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/README.md +0 -0
  81. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/builder/base-images.json +0 -0
  82. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/call_graph.py +0 -0
  83. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/__init__.py +0 -0
  84. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/_download.py +0 -0
  85. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/_traceback.py +0 -0
  86. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/app.py +0 -0
  87. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/cluster.py +0 -0
  88. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/config.py +0 -0
  89. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/container.py +0 -0
  90. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/dict.py +0 -0
  91. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/entry_point.py +0 -0
  92. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/environment.py +0 -0
  93. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/import_refs.py +0 -0
  94. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/launch.py +0 -0
  95. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/network_file_system.py +0 -0
  96. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/profile.py +0 -0
  97. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/programs/__init__.py +0 -0
  98. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/programs/launch_instance_ssh.py +0 -0
  99. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/programs/run_jupyter.py +0 -0
  100. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/programs/run_marimo.py +0 -0
  101. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/programs/vscode.py +0 -0
  102. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/queues.py +0 -0
  103. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/run.py +0 -0
  104. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/secret.py +0 -0
  105. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/token.py +0 -0
  106. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/utils.py +0 -0
  107. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cli/volume.py +0 -0
  108. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/client.py +0 -0
  109. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cloud_bucket_mount.py +0 -0
  110. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cloud_bucket_mount.pyi +0 -0
  111. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cls.py +0 -0
  112. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/cls.pyi +0 -0
  113. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/config.py +0 -0
  114. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/container_process.py +0 -0
  115. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/container_process.pyi +0 -0
  116. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/environments.py +0 -0
  117. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/environments.pyi +0 -0
  118. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/exception.py +0 -0
  119. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/experimental/__init__.py +0 -0
  120. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/experimental/flash.py +0 -0
  121. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/experimental/flash.pyi +0 -0
  122. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/experimental/ipython.py +0 -0
  123. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/file_io.py +0 -0
  124. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/file_io.pyi +0 -0
  125. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/file_pattern_matcher.py +0 -0
  126. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/functions.py +0 -0
  127. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/gpu.py +0 -0
  128. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/image.py +0 -0
  129. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/image.pyi +0 -0
  130. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/io_streams.py +0 -0
  131. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/io_streams.pyi +0 -0
  132. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/mount.py +0 -0
  133. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/mount.pyi +0 -0
  134. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/network_file_system.py +0 -0
  135. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/network_file_system.pyi +0 -0
  136. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/object.py +0 -0
  137. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/object.pyi +0 -0
  138. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/output.py +0 -0
  139. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/parallel_map.py +0 -0
  140. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/parallel_map.pyi +0 -0
  141. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/partial_function.py +0 -0
  142. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/partial_function.pyi +0 -0
  143. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/proxy.py +0 -0
  144. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/proxy.pyi +0 -0
  145. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/py.typed +0 -0
  146. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/retries.py +0 -0
  147. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/runner.py +0 -0
  148. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/runner.pyi +0 -0
  149. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/running_app.py +0 -0
  150. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/sandbox.py +0 -0
  151. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/sandbox.pyi +0 -0
  152. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/schedule.py +0 -0
  153. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/scheduler_placement.py +0 -0
  154. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/serving.py +0 -0
  155. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/serving.pyi +0 -0
  156. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/snapshot.py +0 -0
  157. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/snapshot.pyi +0 -0
  158. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/stream_type.py +0 -0
  159. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/token_flow.py +0 -0
  160. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal/token_flow.pyi +0 -0
  161. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal.egg-info/SOURCES.txt +0 -0
  162. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal.egg-info/dependency_links.txt +0 -0
  163. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal.egg-info/entry_points.txt +0 -0
  164. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal.egg-info/requires.txt +0 -0
  165. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal.egg-info/top_level.txt +0 -0
  166. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_docs/__init__.py +0 -0
  167. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_docs/gen_cli_docs.py +0 -0
  168. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_docs/gen_reference_docs.py +0 -0
  169. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_docs/mdmd/__init__.py +0 -0
  170. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_docs/mdmd/mdmd.py +0 -0
  171. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_docs/mdmd/signatures.py +0 -0
  172. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/__init__.py +0 -0
  173. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/api.proto +0 -0
  174. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/api_grpc.py +0 -0
  175. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/api_pb2.py +0 -0
  176. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/api_pb2.pyi +0 -0
  177. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/api_pb2_grpc.py +0 -0
  178. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/api_pb2_grpc.pyi +0 -0
  179. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/modal_api_grpc.py +0 -0
  180. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/modal_options_grpc.py +0 -0
  181. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/options.proto +0 -0
  182. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/options_grpc.py +0 -0
  183. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/options_pb2.py +0 -0
  184. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/options_pb2.pyi +0 -0
  185. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/options_pb2_grpc.py +0 -0
  186. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/options_pb2_grpc.pyi +0 -0
  187. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_proto/py.typed +0 -0
  188. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/modal_version/__main__.py +0 -0
  189. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/pyproject.toml +0 -0
  190. {modal-1.1.2.dev27 → modal-1.1.2.dev29}/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.dev29
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.dev29",
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.dev29",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -5,7 +5,7 @@ from datetime import datetime
5
5
  from typing import Any, Optional, Union
6
6
 
7
7
  from google.protobuf.message import Message
8
- from grpclib import GRPCError
8
+ from grpclib import GRPCError, Status
9
9
  from synchronicity import classproperty
10
10
  from synchronicity.async_wrap import asynccontextmanager
11
11
 
@@ -27,7 +27,7 @@ from ._utils.name_utils import check_object_name
27
27
  from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
28
28
  from .client import _Client
29
29
  from .config import logger
30
- from .exception import InvalidError, NotFoundError, RequestSizeError
30
+ from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError
31
31
 
32
32
 
33
33
  def _serialize_dict(data):
@@ -49,6 +49,58 @@ class DictInfo:
49
49
  class _DictManager:
50
50
  """Namespace with methods for managing named Dict objects."""
51
51
 
52
+ @staticmethod
53
+ async def create(
54
+ name: str, # Name to use for the new Dict
55
+ *,
56
+ allow_existing: bool = False, # If True, no-op when the Dict already exists
57
+ environment_name: Optional[str] = None, # Uses active environment if not specified
58
+ client: Optional[_Client] = None, # Optional client with Modal credentials
59
+ ) -> None:
60
+ """Create a new Dict object.
61
+
62
+ **Examples:**
63
+
64
+ ```python notest
65
+ modal.Dict.objects.create("my-dict")
66
+ ```
67
+
68
+ Dicts will be created in the active environment, or another one can be specified:
69
+
70
+ ```python notest
71
+ modal.Dict.objects.create("my-dict", environment_name="dev")
72
+ ```
73
+
74
+ By default, an error will be raised if the Dict already exists, but passing
75
+ `allow_existing=True` will make the creation attempt a no-op in this case.
76
+
77
+ ```python notest
78
+ modal.Dict.objects.create("my-dict", allow_existing=True)
79
+ ```
80
+
81
+ Note that this method does not return a local instance of the Dict. You can use
82
+ `modal.Dict.from_name` to perform a lookup after creation.
83
+
84
+ """
85
+ client = await _Client.from_env() if client is None else client
86
+ object_creation_type = (
87
+ api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
88
+ if allow_existing
89
+ else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
90
+ )
91
+ req = api_pb2.DictGetOrCreateRequest(
92
+ deployment_name=name,
93
+ environment_name=_get_environment_name(environment_name),
94
+ object_creation_type=object_creation_type,
95
+ )
96
+ try:
97
+ await retry_transient_errors(client.stub.DictGetOrCreate, req)
98
+ except GRPCError as exc:
99
+ if exc.status == Status.ALREADY_EXISTS and not allow_existing:
100
+ raise AlreadyExistsError(exc.message)
101
+ else:
102
+ raise
103
+
52
104
  @staticmethod
53
105
  async def list(
54
106
  *,
@@ -89,7 +141,9 @@ class _DictManager:
89
141
  async def retrieve_page(created_before: float) -> bool:
90
142
  max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
91
143
  pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
92
- req = api_pb2.DictListRequest(environment_name=environment_name, pagination=pagination)
144
+ req = api_pb2.DictListRequest(
145
+ environment_name=_get_environment_name(environment_name), pagination=pagination
146
+ )
93
147
  resp = await retry_transient_errors(client.stub.DictList, req)
94
148
  items.extend(resp.dicts)
95
149
  finished = (len(resp.dicts) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
@@ -35,6 +35,40 @@ class DictInfo:
35
35
 
36
36
  class _DictManager:
37
37
  """Namespace with methods for managing named Dict objects."""
38
+ @staticmethod
39
+ async def create(
40
+ name: str,
41
+ *,
42
+ allow_existing: bool = False,
43
+ environment_name: typing.Optional[str] = None,
44
+ client: typing.Optional[modal.client._Client] = None,
45
+ ) -> None:
46
+ """Create a new Dict object.
47
+
48
+ **Examples:**
49
+
50
+ ```python notest
51
+ modal.Dict.objects.create("my-dict")
52
+ ```
53
+
54
+ Dicts will be created in the active environment, or another one can be specified:
55
+
56
+ ```python notest
57
+ modal.Dict.objects.create("my-dict", environment_name="dev")
58
+ ```
59
+
60
+ By default, an error will be raised if the Dict already exists, but passing
61
+ `allow_existing=True` will make the creation attempt a no-op in this case.
62
+
63
+ ```python notest
64
+ modal.Dict.objects.create("my-dict", allow_existing=True)
65
+ ```
66
+
67
+ Note that this method does not return a local instance of the Dict. You can use
68
+ `modal.Dict.from_name` to perform a lookup after creation.
69
+ """
70
+ ...
71
+
38
72
  @staticmethod
39
73
  async def list(
40
74
  *,
@@ -100,6 +134,79 @@ class DictManager:
100
134
  """Initialize self. See help(type(self)) for accurate signature."""
101
135
  ...
102
136
 
137
+ class __create_spec(typing_extensions.Protocol):
138
+ def __call__(
139
+ self,
140
+ /,
141
+ name: str,
142
+ *,
143
+ allow_existing: bool = False,
144
+ environment_name: typing.Optional[str] = None,
145
+ client: typing.Optional[modal.client.Client] = None,
146
+ ) -> None:
147
+ """Create a new Dict object.
148
+
149
+ **Examples:**
150
+
151
+ ```python notest
152
+ modal.Dict.objects.create("my-dict")
153
+ ```
154
+
155
+ Dicts will be created in the active environment, or another one can be specified:
156
+
157
+ ```python notest
158
+ modal.Dict.objects.create("my-dict", environment_name="dev")
159
+ ```
160
+
161
+ By default, an error will be raised if the Dict already exists, but passing
162
+ `allow_existing=True` will make the creation attempt a no-op in this case.
163
+
164
+ ```python notest
165
+ modal.Dict.objects.create("my-dict", allow_existing=True)
166
+ ```
167
+
168
+ Note that this method does not return a local instance of the Dict. You can use
169
+ `modal.Dict.from_name` to perform a lookup after creation.
170
+ """
171
+ ...
172
+
173
+ async def aio(
174
+ self,
175
+ /,
176
+ name: str,
177
+ *,
178
+ allow_existing: bool = False,
179
+ environment_name: typing.Optional[str] = None,
180
+ client: typing.Optional[modal.client.Client] = None,
181
+ ) -> None:
182
+ """Create a new Dict object.
183
+
184
+ **Examples:**
185
+
186
+ ```python notest
187
+ modal.Dict.objects.create("my-dict")
188
+ ```
189
+
190
+ Dicts will be created in the active environment, or another one can be specified:
191
+
192
+ ```python notest
193
+ modal.Dict.objects.create("my-dict", environment_name="dev")
194
+ ```
195
+
196
+ By default, an error will be raised if the Dict already exists, but passing
197
+ `allow_existing=True` will make the creation attempt a no-op in this case.
198
+
199
+ ```python notest
200
+ modal.Dict.objects.create("my-dict", allow_existing=True)
201
+ ```
202
+
203
+ Note that this method does not return a local instance of the Dict. You can use
204
+ `modal.Dict.from_name` to perform a lookup after creation.
205
+ """
206
+ ...
207
+
208
+ create: __create_spec
209
+
103
210
  class __list_spec(typing_extensions.Protocol):
104
211
  def __call__(
105
212
  self,
@@ -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."""
@@ -29,7 +29,7 @@ from ._utils.grpc_utils import retry_transient_errors
29
29
  from ._utils.name_utils import check_object_name
30
30
  from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
31
31
  from .client import _Client
32
- from .exception import InvalidError, NotFoundError, RequestSizeError
32
+ from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError
33
33
 
34
34
 
35
35
  @dataclass
@@ -47,10 +47,62 @@ class QueueInfo:
47
47
  class _QueueManager:
48
48
  """Namespace with methods for managing named Queue objects."""
49
49
 
50
+ @staticmethod
51
+ async def create(
52
+ name: str, # Name to use for the new Queue
53
+ *,
54
+ allow_existing: bool = False, # If True, no-op when the Queue already exists
55
+ environment_name: Optional[str] = None, # Uses active environment if not specified
56
+ client: Optional[_Client] = None, # Optional client with Modal credentials
57
+ ) -> None:
58
+ """Create a new Queue object.
59
+
60
+ **Examples:**
61
+
62
+ ```python notest
63
+ modal.Queue.objects.create("my-queue")
64
+ ```
65
+
66
+ Queues will be created in the active environment, or another one can be specified:
67
+
68
+ ```python notest
69
+ modal.Queue.objects.create("my-queue", environment_name="dev")
70
+ ```
71
+
72
+ By default, an error will be raised if the Queue already exists, but passing
73
+ `allow_existing=True` will make the creation attempt a no-op in this case.
74
+
75
+ ```python notest
76
+ modal.Queue.objects.create("my-queue", allow_existing=True)
77
+ ```
78
+
79
+ Note that this method does not return a local instance of the Queue. You can use
80
+ `modal.Queue.from_name` to perform a lookup after creation.
81
+
82
+ """
83
+ client = await _Client.from_env() if client is None else client
84
+ object_creation_type = (
85
+ api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
86
+ if allow_existing
87
+ else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
88
+ )
89
+ req = api_pb2.QueueGetOrCreateRequest(
90
+ deployment_name=name,
91
+ environment_name=_get_environment_name(environment_name),
92
+ object_creation_type=object_creation_type,
93
+ )
94
+ try:
95
+ await retry_transient_errors(client.stub.QueueGetOrCreate, req)
96
+ except GRPCError as exc:
97
+ if exc.status == Status.ALREADY_EXISTS and not allow_existing:
98
+ raise AlreadyExistsError(exc.message)
99
+ else:
100
+ raise
101
+
50
102
  @staticmethod
51
103
  async def list(
52
104
  *,
53
- max_objects: Optional[int] = None, # Limit requests to this size
105
+ max_objects: Optional[int] = None, # Limit results to this size
54
106
  created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
55
107
  environment_name: str = "", # Uses active environment if not specified
56
108
  client: Optional[_Client] = None, # Optional client with Modal credentials
@@ -87,7 +139,9 @@ class _QueueManager:
87
139
  async def retrieve_page(created_before: float) -> bool:
88
140
  max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
89
141
  pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
90
- req = api_pb2.QueueListRequest(environment_name=environment_name, pagination=pagination)
142
+ req = api_pb2.QueueListRequest(
143
+ environment_name=_get_environment_name(environment_name), pagination=pagination
144
+ )
91
145
  resp = await retry_transient_errors(client.stub.QueueList, req)
92
146
  items.extend(resp.queues)
93
147
  finished = (len(resp.queues) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
@@ -33,6 +33,40 @@ class QueueInfo:
33
33
 
34
34
  class _QueueManager:
35
35
  """Namespace with methods for managing named Queue objects."""
36
+ @staticmethod
37
+ async def create(
38
+ name: str,
39
+ *,
40
+ allow_existing: bool = False,
41
+ environment_name: typing.Optional[str] = None,
42
+ client: typing.Optional[modal.client._Client] = None,
43
+ ) -> None:
44
+ """Create a new Queue object.
45
+
46
+ **Examples:**
47
+
48
+ ```python notest
49
+ modal.Queue.objects.create("my-queue")
50
+ ```
51
+
52
+ Queues will be created in the active environment, or another one can be specified:
53
+
54
+ ```python notest
55
+ modal.Queue.objects.create("my-queue", environment_name="dev")
56
+ ```
57
+
58
+ By default, an error will be raised if the Queue already exists, but passing
59
+ `allow_existing=True` will make the creation attempt a no-op in this case.
60
+
61
+ ```python notest
62
+ modal.Queue.objects.create("my-queue", allow_existing=True)
63
+ ```
64
+
65
+ Note that this method does not return a local instance of the Queue. You can use
66
+ `modal.Queue.from_name` to perform a lookup after creation.
67
+ """
68
+ ...
69
+
36
70
  @staticmethod
37
71
  async def list(
38
72
  *,
@@ -98,6 +132,79 @@ class QueueManager:
98
132
  """Initialize self. See help(type(self)) for accurate signature."""
99
133
  ...
100
134
 
135
+ class __create_spec(typing_extensions.Protocol):
136
+ def __call__(
137
+ self,
138
+ /,
139
+ name: str,
140
+ *,
141
+ allow_existing: bool = False,
142
+ environment_name: typing.Optional[str] = None,
143
+ client: typing.Optional[modal.client.Client] = None,
144
+ ) -> None:
145
+ """Create a new Queue object.
146
+
147
+ **Examples:**
148
+
149
+ ```python notest
150
+ modal.Queue.objects.create("my-queue")
151
+ ```
152
+
153
+ Queues will be created in the active environment, or another one can be specified:
154
+
155
+ ```python notest
156
+ modal.Queue.objects.create("my-queue", environment_name="dev")
157
+ ```
158
+
159
+ By default, an error will be raised if the Queue already exists, but passing
160
+ `allow_existing=True` will make the creation attempt a no-op in this case.
161
+
162
+ ```python notest
163
+ modal.Queue.objects.create("my-queue", allow_existing=True)
164
+ ```
165
+
166
+ Note that this method does not return a local instance of the Queue. You can use
167
+ `modal.Queue.from_name` to perform a lookup after creation.
168
+ """
169
+ ...
170
+
171
+ async def aio(
172
+ self,
173
+ /,
174
+ name: str,
175
+ *,
176
+ allow_existing: bool = False,
177
+ environment_name: typing.Optional[str] = None,
178
+ client: typing.Optional[modal.client.Client] = None,
179
+ ) -> None:
180
+ """Create a new Queue object.
181
+
182
+ **Examples:**
183
+
184
+ ```python notest
185
+ modal.Queue.objects.create("my-queue")
186
+ ```
187
+
188
+ Queues will be created in the active environment, or another one can be specified:
189
+
190
+ ```python notest
191
+ modal.Queue.objects.create("my-queue", environment_name="dev")
192
+ ```
193
+
194
+ By default, an error will be raised if the Queue already exists, but passing
195
+ `allow_existing=True` will make the creation attempt a no-op in this case.
196
+
197
+ ```python notest
198
+ modal.Queue.objects.create("my-queue", allow_existing=True)
199
+ ```
200
+
201
+ Note that this method does not return a local instance of the Queue. You can use
202
+ `modal.Queue.from_name` to perform a lookup after creation.
203
+ """
204
+ ...
205
+
206
+ create: __create_spec
207
+
101
208
  class __list_spec(typing_extensions.Protocol):
102
209
  def __call__(
103
210
  self,