modal 1.2.5.dev26__tar.gz → 1.2.5.dev27__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 (196) hide show
  1. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/PKG-INFO +1 -1
  2. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/blob_utils.py +18 -5
  3. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/client.pyi +2 -2
  4. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/mount.py +14 -8
  5. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/volume.py +5 -1
  6. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal.egg-info/PKG-INFO +1 -1
  7. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_version/__init__.py +1 -1
  8. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/LICENSE +0 -0
  9. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/README.md +0 -0
  10. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/__init__.py +0 -0
  11. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/__main__.py +0 -0
  12. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_billing.py +0 -0
  13. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_clustered_functions.py +0 -0
  14. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_clustered_functions.pyi +0 -0
  15. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_container_entrypoint.py +0 -0
  16. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_functions.py +0 -0
  17. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_grpc_client.py +0 -0
  18. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_ipython.py +0 -0
  19. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_load_context.py +0 -0
  20. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_location.py +0 -0
  21. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_object.py +0 -0
  22. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_output.py +0 -0
  23. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_partial_function.py +0 -0
  24. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_pty.py +0 -0
  25. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_resolver.py +0 -0
  26. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_resources.py +0 -0
  27. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/__init__.py +0 -0
  28. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/asgi.py +0 -0
  29. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/container_io_manager.py +0 -0
  30. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/container_io_manager.pyi +0 -0
  31. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/execution_context.py +0 -0
  32. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/execution_context.pyi +0 -0
  33. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  34. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/telemetry.py +0 -0
  35. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/user_code_event_loop.py +0 -0
  36. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_runtime/user_code_imports.py +0 -0
  37. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_serialization.py +0 -0
  38. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_traceback.py +0 -0
  39. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_tunnel.py +0 -0
  40. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_tunnel.pyi +0 -0
  41. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_type_manager.py +0 -0
  42. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/__init__.py +0 -0
  43. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/app_utils.py +0 -0
  44. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/async_utils.py +0 -0
  45. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/auth_token_manager.py +0 -0
  46. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/bytes_io_segment_payload.py +0 -0
  47. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/deprecation.py +0 -0
  48. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/docker_utils.py +0 -0
  49. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/function_utils.py +0 -0
  50. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/git_utils.py +0 -0
  51. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/grpc_testing.py +0 -0
  52. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/grpc_utils.py +0 -0
  53. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/hash_utils.py +0 -0
  54. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/http_utils.py +0 -0
  55. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/jwt_utils.py +0 -0
  56. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/logger.py +0 -0
  57. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/mount_utils.py +0 -0
  58. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/name_utils.py +0 -0
  59. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/package_utils.py +0 -0
  60. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/pattern_utils.py +0 -0
  61. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/rand_pb_testing.py +0 -0
  62. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/shell_utils.py +0 -0
  63. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/task_command_router_client.py +0 -0
  64. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_utils/time_utils.py +0 -0
  65. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_vendor/__init__.py +0 -0
  66. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  67. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_vendor/cloudpickle.py +0 -0
  68. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_vendor/tblib.py +0 -0
  69. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/_watcher.py +0 -0
  70. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/app.py +0 -0
  71. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/app.pyi +0 -0
  72. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/billing.py +0 -0
  73. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/2023.12.312.txt +0 -0
  74. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/2023.12.txt +0 -0
  75. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/2024.04.txt +0 -0
  76. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/2024.10.txt +0 -0
  77. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/2025.06.txt +0 -0
  78. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/PREVIEW.txt +0 -0
  79. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/README.md +0 -0
  80. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/builder/base-images.json +0 -0
  81. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/call_graph.py +0 -0
  82. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/__init__.py +0 -0
  83. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/_download.py +0 -0
  84. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/_traceback.py +0 -0
  85. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/app.py +0 -0
  86. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/cluster.py +0 -0
  87. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/config.py +0 -0
  88. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/container.py +0 -0
  89. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/dict.py +0 -0
  90. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/entry_point.py +0 -0
  91. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/environment.py +0 -0
  92. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/import_refs.py +0 -0
  93. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/launch.py +0 -0
  94. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/network_file_system.py +0 -0
  95. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/profile.py +0 -0
  96. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/programs/__init__.py +0 -0
  97. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/programs/launch_instance_ssh.py +0 -0
  98. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/programs/run_jupyter.py +0 -0
  99. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/programs/run_marimo.py +0 -0
  100. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/programs/vscode.py +0 -0
  101. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/queues.py +0 -0
  102. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/run.py +0 -0
  103. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/secret.py +0 -0
  104. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/shell.py +0 -0
  105. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/token.py +0 -0
  106. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/utils.py +0 -0
  107. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cli/volume.py +0 -0
  108. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/client.py +0 -0
  109. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cloud_bucket_mount.py +0 -0
  110. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cloud_bucket_mount.pyi +0 -0
  111. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cls.py +0 -0
  112. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/cls.pyi +0 -0
  113. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/config.py +0 -0
  114. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/container_process.py +0 -0
  115. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/container_process.pyi +0 -0
  116. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/dict.py +0 -0
  117. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/dict.pyi +0 -0
  118. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/environments.py +0 -0
  119. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/environments.pyi +0 -0
  120. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/exception.py +0 -0
  121. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/experimental/__init__.py +0 -0
  122. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/experimental/flash.py +0 -0
  123. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/experimental/flash.pyi +0 -0
  124. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/experimental/ipython.py +0 -0
  125. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/file_io.py +0 -0
  126. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/file_io.pyi +0 -0
  127. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/file_pattern_matcher.py +0 -0
  128. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/functions.py +0 -0
  129. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/functions.pyi +0 -0
  130. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/gpu.py +0 -0
  131. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/image.py +0 -0
  132. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/image.pyi +0 -0
  133. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/io_streams.py +0 -0
  134. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/io_streams.pyi +0 -0
  135. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/mount.pyi +0 -0
  136. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/network_file_system.py +0 -0
  137. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/network_file_system.pyi +0 -0
  138. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/object.py +0 -0
  139. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/object.pyi +0 -0
  140. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/output.py +0 -0
  141. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/parallel_map.py +0 -0
  142. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/parallel_map.pyi +0 -0
  143. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/partial_function.py +0 -0
  144. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/partial_function.pyi +0 -0
  145. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/proxy.py +0 -0
  146. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/proxy.pyi +0 -0
  147. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/py.typed +0 -0
  148. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/queue.py +0 -0
  149. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/queue.pyi +0 -0
  150. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/retries.py +0 -0
  151. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/runner.py +0 -0
  152. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/runner.pyi +0 -0
  153. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/running_app.py +0 -0
  154. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/sandbox.py +0 -0
  155. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/sandbox.pyi +0 -0
  156. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/schedule.py +0 -0
  157. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/scheduler_placement.py +0 -0
  158. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/secret.py +0 -0
  159. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/secret.pyi +0 -0
  160. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/serving.py +0 -0
  161. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/serving.pyi +0 -0
  162. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/snapshot.py +0 -0
  163. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/snapshot.pyi +0 -0
  164. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/stream_type.py +0 -0
  165. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/token_flow.py +0 -0
  166. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/token_flow.pyi +0 -0
  167. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal/volume.pyi +0 -0
  168. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal.egg-info/SOURCES.txt +0 -0
  169. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal.egg-info/dependency_links.txt +0 -0
  170. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal.egg-info/entry_points.txt +0 -0
  171. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal.egg-info/requires.txt +0 -0
  172. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal.egg-info/top_level.txt +0 -0
  173. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_docs/__init__.py +0 -0
  174. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_docs/gen_cli_docs.py +0 -0
  175. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_docs/gen_reference_docs.py +0 -0
  176. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_docs/mdmd/__init__.py +0 -0
  177. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_docs/mdmd/mdmd.py +0 -0
  178. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_docs/mdmd/signatures.py +0 -0
  179. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/__init__.py +0 -0
  180. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/api.proto +0 -0
  181. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/api_grpc.py +0 -0
  182. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/api_pb2.py +0 -0
  183. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/api_pb2.pyi +0 -0
  184. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/api_pb2_grpc.py +0 -0
  185. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/api_pb2_grpc.pyi +0 -0
  186. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/modal_api_grpc.py +0 -0
  187. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/py.typed +0 -0
  188. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/task_command_router.proto +0 -0
  189. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/task_command_router_grpc.py +0 -0
  190. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/task_command_router_pb2.py +0 -0
  191. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/task_command_router_pb2.pyi +0 -0
  192. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  193. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  194. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/modal_version/__main__.py +0 -0
  195. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/pyproject.toml +0 -0
  196. {modal-1.2.5.dev26 → modal-1.2.5.dev27}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.5.dev26
3
+ Version: 1.2.5.dev27
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -371,11 +371,17 @@ class FileUploadSpec:
371
371
  mount_filename: str
372
372
 
373
373
  use_blob: bool
374
- content: Optional[bytes] # typically None if using blob, required otherwise
375
374
  sha256_hex: str
376
375
  md5_hex: str
377
376
  mode: int # file permission bits (last 12 bits of st_mode)
378
377
  size: int
378
+ content: Optional[bytes] = None # Set for very small files to avoid double-read
379
+
380
+ def read_content(self) -> bytes:
381
+ """Read content from source."""
382
+ with self.source() as fp:
383
+ fp.seek(0)
384
+ return fp.read()
379
385
 
380
386
 
381
387
  def _get_file_upload_spec(
@@ -384,6 +390,7 @@ def _get_file_upload_spec(
384
390
  mount_filename: PurePosixPath,
385
391
  mode: int,
386
392
  ) -> FileUploadSpec:
393
+ content = None
387
394
  with source() as fp:
388
395
  # Current position is ignored - we always upload from position 0
389
396
  fp.seek(0, os.SEEK_END)
@@ -394,12 +401,18 @@ def _get_file_upload_spec(
394
401
  # TODO(dano): remove the placeholder md5 once we stop requiring md5 for blobs
395
402
  md5_hex = "baadbaadbaadbaadbaadbaadbaadbaad" if size > MULTIPART_UPLOAD_THRESHOLD else None
396
403
  use_blob = True
397
- content = None
398
404
  hashes = get_upload_hashes(fp, md5_hex=md5_hex)
399
405
  else:
400
406
  use_blob = False
401
- content = fp.read()
402
- hashes = get_upload_hashes(content)
407
+ # For very small files (< 256 KiB), read content once and cache it
408
+ # This avoids double-read penalty while limiting memory usage
409
+ if size < 256 * 1024: # 256 KiB threshold
410
+ fp.seek(0)
411
+ content = fp.read()
412
+ hashes = get_upload_hashes(content)
413
+ else:
414
+ # For medium files (256 KiB - 4 MiB), compute hashes without caching content
415
+ hashes = get_upload_hashes(fp)
403
416
 
404
417
  return FileUploadSpec(
405
418
  source=source,
@@ -407,11 +420,11 @@ def _get_file_upload_spec(
407
420
  source_is_path=isinstance(source_description, Path),
408
421
  mount_filename=mount_filename.as_posix(),
409
422
  use_blob=use_blob,
410
- content=content,
411
423
  sha256_hex=hashes.sha256_hex(),
412
424
  md5_hex=hashes.md5_hex(),
413
425
  mode=mode & 0o7777,
414
426
  size=size,
427
+ content=content,
415
428
  )
416
429
 
417
430
 
@@ -32,7 +32,7 @@ class _Client:
32
32
  server_url: str,
33
33
  client_type: int,
34
34
  credentials: typing.Optional[tuple[str, str]],
35
- version: str = "1.2.5.dev26",
35
+ version: str = "1.2.5.dev27",
36
36
  ):
37
37
  """mdmd:hidden
38
38
  The Modal client object is not intended to be instantiated directly by users.
@@ -163,7 +163,7 @@ class Client:
163
163
  server_url: str,
164
164
  client_type: int,
165
165
  credentials: typing.Optional[tuple[str, str]],
166
- version: str = "1.2.5.dev26",
166
+ version: str = "1.2.5.dev27",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -466,16 +466,18 @@ class _Mount(_Object, type_prefix="mo"):
466
466
  loop = asyncio.get_event_loop()
467
467
  with concurrent.futures.ThreadPoolExecutor() as exe:
468
468
  all_files = await loop.run_in_executor(exe, _select_files, entries)
469
+ logger.debug(f"Computing checksums for {len(all_files)} files using {exe._max_workers} worker threads")
469
470
 
470
- futs = []
471
+ # Yield FileUploadSpec objects lazily as they're consumed by async_map downstream.
472
+ # async_map's concurrency limit provides natural backpressure, so we don't need
473
+ # a separate semaphore here. This keeps memory bounded without creating all tasks upfront.
471
474
  for local_filename, remote_filename in all_files:
472
- logger.debug(f"Mounting {local_filename} as {remote_filename}")
473
- futs.append(loop.run_in_executor(exe, get_file_upload_spec_from_path, local_filename, remote_filename))
474
-
475
- logger.debug(f"Computing checksums for {len(futs)} files using {exe._max_workers} worker threads")
476
- for fut in asyncio.as_completed(futs):
477
475
  try:
478
- yield await fut
476
+ logger.debug(f"Mounting {local_filename} as {remote_filename}")
477
+ file_spec = await loop.run_in_executor(
478
+ exe, get_file_upload_spec_from_path, local_filename, remote_filename
479
+ )
480
+ yield file_spec
479
481
  except FileNotFoundError as exc:
480
482
  # Can happen with temporary files (e.g. emacs will write temp files and delete them quickly)
481
483
  logger.info(f"Ignoring file not found: {exc}")
@@ -547,7 +549,11 @@ class _Mount(_Object, type_prefix="mo"):
547
549
  logger.debug(
548
550
  f"Uploading file {file_spec.source_description} to {remote_filename} ({file_spec.size} bytes)"
549
551
  )
550
- request2 = api_pb2.MountPutFileRequest(data=file_spec.content, sha256_hex=file_spec.sha256_hex)
552
+ if file_spec.content is None:
553
+ content = await asyncio.to_thread(file_spec.read_content)
554
+ else:
555
+ content = file_spec.content
556
+ request2 = api_pb2.MountPutFileRequest(data=content, sha256_hex=file_spec.sha256_hex)
551
557
 
552
558
  start_time = time.monotonic()
553
559
  while time.monotonic() - start_time < MOUNT_PUT_FILE_CLIENT_TIMEOUT:
@@ -1056,7 +1056,11 @@ class _VolumeUploadContextManager(_AbstractVolumeUploadContextManager):
1056
1056
  logger.debug(
1057
1057
  f"Uploading file {file_spec.source_description} to {remote_filename} ({file_spec.size} bytes)"
1058
1058
  )
1059
- request2 = api_pb2.MountPutFileRequest(data=file_spec.content, sha256_hex=file_spec.sha256_hex)
1059
+ if file_spec.content is None:
1060
+ content = await asyncio.to_thread(file_spec.read_content)
1061
+ else:
1062
+ content = file_spec.content
1063
+ request2 = api_pb2.MountPutFileRequest(data=content, sha256_hex=file_spec.sha256_hex)
1060
1064
  self._progress_cb(task_id=progress_task_id, complete=True)
1061
1065
 
1062
1066
  while (time.monotonic() - start_time) < VOLUME_PUT_FILE_CLIENT_TIMEOUT:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.5.dev26
3
+ Version: 1.2.5.dev27
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.2.5.dev26"
4
+ __version__ = "1.2.5.dev27"
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