modal 1.0.3.dev14__tar.gz → 1.0.3.dev21__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/PKG-INFO +1 -1
  2. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/run.py +12 -0
  3. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/client.pyi +2 -2
  4. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/mount.py +124 -4
  5. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/mount.pyi +16 -0
  6. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/runner.py +2 -7
  7. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/volume.py +7 -48
  8. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/volume.pyi +0 -40
  9. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal.egg-info/PKG-INFO +1 -1
  10. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/api.proto +4 -4
  11. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/api_pb2.pyi +4 -4
  12. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_version/__init__.py +1 -1
  13. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/LICENSE +0 -0
  14. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/README.md +0 -0
  15. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/__init__.py +0 -0
  16. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/__main__.py +0 -0
  17. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_clustered_functions.py +0 -0
  18. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_clustered_functions.pyi +0 -0
  19. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_container_entrypoint.py +0 -0
  20. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_functions.py +0 -0
  21. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_ipython.py +0 -0
  22. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_location.py +0 -0
  23. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_object.py +0 -0
  24. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_output.py +0 -0
  25. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_partial_function.py +0 -0
  26. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_pty.py +0 -0
  27. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_resolver.py +0 -0
  28. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_resources.py +0 -0
  29. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/__init__.py +0 -0
  30. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/asgi.py +0 -0
  31. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/container_io_manager.py +0 -0
  32. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/container_io_manager.pyi +0 -0
  33. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/execution_context.py +0 -0
  34. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/execution_context.pyi +0 -0
  35. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  36. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/telemetry.py +0 -0
  37. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_runtime/user_code_imports.py +0 -0
  38. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_serialization.py +0 -0
  39. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_traceback.py +0 -0
  40. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_tunnel.py +0 -0
  41. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_tunnel.pyi +0 -0
  42. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_type_manager.py +0 -0
  43. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/__init__.py +0 -0
  44. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/app_utils.py +0 -0
  45. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/async_utils.py +0 -0
  46. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/blob_utils.py +0 -0
  47. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/bytes_io_segment_payload.py +0 -0
  48. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/deprecation.py +0 -0
  49. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/docker_utils.py +0 -0
  50. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/function_utils.py +0 -0
  51. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/git_utils.py +0 -0
  52. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/grpc_testing.py +0 -0
  53. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/grpc_utils.py +0 -0
  54. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/hash_utils.py +0 -0
  55. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/http_utils.py +0 -0
  56. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/jwt_utils.py +0 -0
  57. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/logger.py +0 -0
  58. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/mount_utils.py +0 -0
  59. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/name_utils.py +0 -0
  60. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/package_utils.py +0 -0
  61. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/pattern_utils.py +0 -0
  62. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/rand_pb_testing.py +0 -0
  63. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/shell_utils.py +0 -0
  64. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_utils/time_utils.py +0 -0
  65. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_vendor/__init__.py +0 -0
  66. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  67. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_vendor/cloudpickle.py +0 -0
  68. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_vendor/tblib.py +0 -0
  69. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/_watcher.py +0 -0
  70. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/app.py +0 -0
  71. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/app.pyi +0 -0
  72. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/call_graph.py +0 -0
  73. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/__init__.py +0 -0
  74. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/_download.py +0 -0
  75. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/_traceback.py +0 -0
  76. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/app.py +0 -0
  77. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/cluster.py +0 -0
  78. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/config.py +0 -0
  79. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/container.py +0 -0
  80. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/dict.py +0 -0
  81. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/entry_point.py +0 -0
  82. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/environment.py +0 -0
  83. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/import_refs.py +0 -0
  84. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/launch.py +0 -0
  85. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/network_file_system.py +0 -0
  86. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/profile.py +0 -0
  87. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/programs/__init__.py +0 -0
  88. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/programs/run_jupyter.py +0 -0
  89. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/programs/vscode.py +0 -0
  90. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/queues.py +0 -0
  91. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/secret.py +0 -0
  92. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/token.py +0 -0
  93. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/utils.py +0 -0
  94. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cli/volume.py +0 -0
  95. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/client.py +0 -0
  96. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cloud_bucket_mount.py +0 -0
  97. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cloud_bucket_mount.pyi +0 -0
  98. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cls.py +0 -0
  99. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/cls.pyi +0 -0
  100. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/config.py +0 -0
  101. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/container_process.py +0 -0
  102. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/container_process.pyi +0 -0
  103. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/dict.py +0 -0
  104. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/dict.pyi +0 -0
  105. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/environments.py +0 -0
  106. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/environments.pyi +0 -0
  107. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/exception.py +0 -0
  108. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/experimental/__init__.py +0 -0
  109. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/experimental/ipython.py +0 -0
  110. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/file_io.py +0 -0
  111. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/file_io.pyi +0 -0
  112. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/file_pattern_matcher.py +0 -0
  113. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/functions.py +0 -0
  114. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/functions.pyi +0 -0
  115. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/gpu.py +0 -0
  116. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/image.py +0 -0
  117. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/image.pyi +0 -0
  118. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/io_streams.py +0 -0
  119. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/io_streams.pyi +0 -0
  120. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/network_file_system.py +0 -0
  121. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/network_file_system.pyi +0 -0
  122. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/object.py +0 -0
  123. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/object.pyi +0 -0
  124. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/output.py +0 -0
  125. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/parallel_map.py +0 -0
  126. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/parallel_map.pyi +0 -0
  127. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/partial_function.py +0 -0
  128. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/partial_function.pyi +0 -0
  129. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/proxy.py +0 -0
  130. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/proxy.pyi +0 -0
  131. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/py.typed +0 -0
  132. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/queue.py +0 -0
  133. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/queue.pyi +0 -0
  134. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/2023.12.312.txt +0 -0
  135. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/2023.12.txt +0 -0
  136. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/2024.04.txt +0 -0
  137. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/2024.10.txt +0 -0
  138. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/PREVIEW.txt +0 -0
  139. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/README.md +0 -0
  140. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/requirements/base-images.json +0 -0
  141. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/retries.py +0 -0
  142. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/runner.pyi +0 -0
  143. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/running_app.py +0 -0
  144. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/sandbox.py +0 -0
  145. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/sandbox.pyi +0 -0
  146. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/schedule.py +0 -0
  147. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/scheduler_placement.py +0 -0
  148. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/secret.py +0 -0
  149. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/secret.pyi +0 -0
  150. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/serving.py +0 -0
  151. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/serving.pyi +0 -0
  152. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/snapshot.py +0 -0
  153. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/snapshot.pyi +0 -0
  154. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/stream_type.py +0 -0
  155. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/token_flow.py +0 -0
  156. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal/token_flow.pyi +0 -0
  157. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal.egg-info/SOURCES.txt +0 -0
  158. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal.egg-info/dependency_links.txt +0 -0
  159. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal.egg-info/entry_points.txt +0 -0
  160. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal.egg-info/requires.txt +0 -0
  161. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal.egg-info/top_level.txt +0 -0
  162. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_docs/__init__.py +0 -0
  163. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_docs/gen_cli_docs.py +0 -0
  164. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_docs/gen_reference_docs.py +0 -0
  165. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_docs/mdmd/__init__.py +0 -0
  166. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_docs/mdmd/mdmd.py +0 -0
  167. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_docs/mdmd/signatures.py +0 -0
  168. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/__init__.py +0 -0
  169. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/api_grpc.py +0 -0
  170. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/api_pb2.py +0 -0
  171. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/api_pb2_grpc.py +0 -0
  172. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/api_pb2_grpc.pyi +0 -0
  173. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/modal_api_grpc.py +0 -0
  174. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/modal_options_grpc.py +0 -0
  175. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/options.proto +0 -0
  176. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/options_grpc.py +0 -0
  177. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/options_pb2.py +0 -0
  178. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/options_pb2.pyi +0 -0
  179. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/options_pb2_grpc.py +0 -0
  180. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/options_pb2_grpc.pyi +0 -0
  181. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_proto/py.typed +0 -0
  182. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/modal_version/__main__.py +0 -0
  183. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/pyproject.toml +0 -0
  184. {modal-1.0.3.dev14 → modal-1.0.3.dev21}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.3.dev14
3
+ Version: 1.0.3.dev21
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -171,6 +171,14 @@ def _write_local_result(result_path: str, res: Any):
171
171
  fid.write(res)
172
172
 
173
173
 
174
+ def _validate_interactive_quiet_params(ctx):
175
+ interactive = ctx.obj["interactive"]
176
+ show_progress = ctx.obj["show_progress"]
177
+
178
+ if not show_progress and interactive:
179
+ raise InvalidError("To use interactive mode, remove the --quiet flag")
180
+
181
+
174
182
  def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
175
183
  @click.pass_context
176
184
  def f(ctx, **kwargs):
@@ -180,6 +188,8 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
180
188
  else:
181
189
  args = ()
182
190
 
191
+ _validate_interactive_quiet_params(ctx)
192
+
183
193
  show_progress: bool = ctx.obj["show_progress"]
184
194
  with enable_output(show_progress):
185
195
  with run_app(
@@ -291,6 +301,8 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
291
301
  assert len(args) == 0 and len(kwargs) == 0
292
302
  args = ctx.args
293
303
 
304
+ _validate_interactive_quiet_params(ctx)
305
+
294
306
  show_progress: bool = ctx.obj["show_progress"]
295
307
  with enable_output(show_progress):
296
308
  with run_app(
@@ -31,7 +31,7 @@ class _Client:
31
31
  server_url: str,
32
32
  client_type: int,
33
33
  credentials: typing.Optional[tuple[str, str]],
34
- version: str = "1.0.3.dev14",
34
+ version: str = "1.0.3.dev21",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -94,7 +94,7 @@ class Client:
94
94
  server_url: str,
95
95
  client_type: int,
96
96
  credentials: typing.Optional[tuple[str, str]],
97
- version: str = "1.0.3.dev14",
97
+ version: str = "1.0.3.dev21",
98
98
  ): ...
99
99
  def is_closed(self) -> bool: ...
100
100
  @property
@@ -21,7 +21,7 @@ from modal_version import __version__
21
21
 
22
22
  from ._object import _get_environment_name, _Object
23
23
  from ._resolver import Resolver
24
- from ._utils.async_utils import aclosing, async_map, synchronize_api
24
+ from ._utils.async_utils import TaskContext, aclosing, async_map, synchronize_api
25
25
  from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
26
26
  from ._utils.deprecation import deprecation_warning
27
27
  from ._utils.grpc_utils import retry_transient_errors
@@ -115,7 +115,8 @@ class _MountFile(_MountEntry):
115
115
  def get_files_to_upload(self):
116
116
  local_file = self.local_file.resolve()
117
117
  if not local_file.exists():
118
- raise FileNotFoundError(local_file)
118
+ msg = f"local file {local_file} does not exist"
119
+ raise FileNotFoundError(msg)
119
120
 
120
121
  rel_filename = self.remote_path
121
122
  yield local_file, rel_filename
@@ -144,10 +145,12 @@ class _MountDir(_MountEntry):
144
145
  local_dir = self.local_dir.expanduser().absolute()
145
146
 
146
147
  if not local_dir.exists():
147
- raise FileNotFoundError(local_dir)
148
+ msg = f"local dir {local_dir} does not exist"
149
+ raise FileNotFoundError(msg)
148
150
 
149
151
  if not local_dir.is_dir():
150
- raise NotADirectoryError(local_dir)
152
+ msg = f"local dir {local_dir} is not a directory"
153
+ raise NotADirectoryError(msg)
151
154
 
152
155
  if self.recursive:
153
156
  gen = (os.path.join(root, name) for root, dirs, files in os.walk(local_dir) for name in files)
@@ -810,3 +813,120 @@ def _is_modal_path(remote_path: PurePosixPath):
810
813
  if is_modal_path:
811
814
  return True
812
815
  return False
816
+
817
+
818
+ REMOTE_PACKAGES_PATH = "/__modal/deps"
819
+ REMOTE_SITECUSTOMIZE_PATH = "/pkg/sitecustomize.py"
820
+
821
+ SITECUSTOMIZE_CONTENT = f"""
822
+ # This file is automatically generated by Modal.
823
+ # It ensures that Modal's python dependencies are available in the Python PATH,
824
+ # while prioritizing user-installed packages.
825
+ import sys; sys.path.append('{REMOTE_PACKAGES_PATH}')
826
+ """.strip()
827
+
828
+
829
+ async def _create_single_mount(
830
+ client: _Client,
831
+ builder_version: str,
832
+ python_version: str,
833
+ platform: str,
834
+ arch: str,
835
+ uv_python_platform: str = None,
836
+ check_if_exists: bool = True,
837
+ ):
838
+ import subprocess
839
+ import tempfile
840
+
841
+ profile_environment = config.get("environment")
842
+ abi_tag = "cp" + python_version.replace(".", "")
843
+ mount_name = f"{builder_version}-{abi_tag}-{platform}-{arch}"
844
+ uv_python_platform = uv_python_platform or f"{arch}-{platform}"
845
+
846
+ if check_if_exists:
847
+ try:
848
+ await Mount.from_name(mount_name, namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL).hydrate.aio(client)
849
+ print(f"✅ Found existing mount {mount_name} in global namespace.")
850
+ return
851
+ except modal.exception.NotFoundError:
852
+ pass
853
+
854
+ with tempfile.TemporaryDirectory() as tmpd:
855
+ print(f"📦 Building {mount_name}.")
856
+ requirements = os.path.join(os.path.dirname(__file__), f"requirements/{builder_version}.txt")
857
+ subprocess.run(
858
+ [
859
+ "uv",
860
+ "pip",
861
+ "install",
862
+ "--strict",
863
+ "--no-deps",
864
+ "--no-cache",
865
+ "-r",
866
+ requirements,
867
+ "--compile-bytecode",
868
+ "--target",
869
+ tmpd,
870
+ "--python-platform",
871
+ uv_python_platform,
872
+ "--python-version",
873
+ python_version,
874
+ ],
875
+ check=True,
876
+ capture_output=True,
877
+ )
878
+
879
+ print(f"🌐 Downloaded and unpacked packages to {tmpd}.")
880
+
881
+ python_mount = Mount._from_local_dir(tmpd, remote_path=REMOTE_PACKAGES_PATH)
882
+
883
+ with tempfile.NamedTemporaryFile() as sitecustomize:
884
+ sitecustomize.write(
885
+ SITECUSTOMIZE_CONTENT.encode("utf-8"),
886
+ )
887
+ sitecustomize.flush()
888
+
889
+ python_mount = python_mount.add_local_file(
890
+ sitecustomize.name,
891
+ remote_path=REMOTE_SITECUSTOMIZE_PATH,
892
+ )
893
+
894
+ await python_mount._deploy.aio(
895
+ mount_name,
896
+ api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
897
+ environment_name=profile_environment,
898
+ client=client,
899
+ )
900
+ print(f"✅ Deployed mount {mount_name} to global namespace.")
901
+
902
+
903
+ async def _create_client_dependency_mounts(client=None, check_if_exists=True):
904
+ coros = []
905
+ for python_version in PYTHON_STANDALONE_VERSIONS:
906
+ # glibc >= 2.17
907
+ coros.append(
908
+ _create_single_mount(
909
+ client,
910
+ "PREVIEW",
911
+ python_version,
912
+ "manylinux_2_17",
913
+ "x86_64",
914
+ check_if_exists=check_if_exists,
915
+ )
916
+ )
917
+ # musl >= 1.2
918
+ coros.append(
919
+ _create_single_mount(
920
+ client,
921
+ "PREVIEW",
922
+ python_version,
923
+ "musllinux_1_2",
924
+ "x86_64",
925
+ uv_python_platform="x86_64-unknown-linux-musl",
926
+ check_if_exists=check_if_exists,
927
+ )
928
+ )
929
+ await TaskContext.gather(*coros)
930
+
931
+
932
+ create_client_dependency_mounts = synchronize_api(_create_client_dependency_mounts)
@@ -308,6 +308,22 @@ def _create_client_mount(): ...
308
308
  def create_client_mount(): ...
309
309
  def _get_client_mount(): ...
310
310
  def _is_modal_path(remote_path: pathlib.PurePosixPath): ...
311
+ async def _create_single_mount(
312
+ client: modal.client._Client,
313
+ builder_version: str,
314
+ python_version: str,
315
+ platform: str,
316
+ arch: str,
317
+ uv_python_platform: str = None,
318
+ check_if_exists: bool = True,
319
+ ): ...
320
+ async def _create_client_dependency_mounts(client=None, check_if_exists=True): ...
321
+
322
+ class __create_client_dependency_mounts_spec(typing_extensions.Protocol):
323
+ def __call__(self, /, client=None, check_if_exists=True): ...
324
+ async def aio(self, /, client=None, check_if_exists=True): ...
325
+
326
+ create_client_dependency_mounts: __create_client_dependency_mounts_spec
311
327
 
312
328
  ROOT_DIR: pathlib.PurePosixPath
313
329
 
@@ -9,7 +9,6 @@ import dataclasses
9
9
  import os
10
10
  import time
11
11
  import typing
12
- import warnings
13
12
  from collections.abc import AsyncGenerator
14
13
  from multiprocessing.synchronize import Event
15
14
  from typing import TYPE_CHECKING, Any, Optional, TypeVar
@@ -296,12 +295,8 @@ async def _run_app(
296
295
 
297
296
  output_mgr = _get_output_manager()
298
297
  if interactive and output_mgr is None:
299
- warnings.warn(
300
- "Interactive mode is disabled because no output manager is active. "
301
- "Use 'with modal.enable_output():' to enable interactive mode and see logs.",
302
- stacklevel=2,
303
- )
304
- interactive = False
298
+ msg = "Interactive mode requires output to be enabled. (Use the the `modal.enable_output()` context manager.)"
299
+ raise InvalidError(msg)
305
300
 
306
301
  running_app: RunningApp = await _init_local_app_new(
307
302
  client,
@@ -45,7 +45,6 @@ from ._utils.blob_utils import (
45
45
  BLOCK_SIZE,
46
46
  FileUploadSpec,
47
47
  FileUploadSpec2,
48
- blob_iter,
49
48
  blob_upload_file,
50
49
  get_file_upload_spec_from_fileobj,
51
50
  get_file_upload_spec_from_path,
@@ -400,7 +399,7 @@ class _Volume(_Object, type_prefix="vo"):
400
399
  return [entry async for entry in self.iterdir(path, recursive=recursive)]
401
400
 
402
401
  @live_method_gen
403
- def read_file(self, path: str) -> AsyncIterator[bytes]:
402
+ async def read_file(self, path: str) -> AsyncIterator[bytes]:
404
403
  """
405
404
  Read a file from the modal.Volume.
406
405
 
@@ -414,23 +413,6 @@ class _Volume(_Object, type_prefix="vo"):
414
413
  print(len(data)) # == 1024 * 1024
415
414
  ```
416
415
  """
417
- return self._read_file1(path) if self._is_v1 else self._read_file2(path)
418
-
419
- async def _read_file1(self, path: str) -> AsyncIterator[bytes]:
420
- req = api_pb2.VolumeGetFileRequest(volume_id=self.object_id, path=path)
421
- try:
422
- response = await retry_transient_errors(self._client.stub.VolumeGetFile, req)
423
- except GRPCError as exc:
424
- raise FileNotFoundError(exc.message) if exc.status == Status.NOT_FOUND else exc
425
- # TODO(Jonathon): use ranged requests.
426
- if response.WhichOneof("data_oneof") == "data":
427
- yield response.data
428
- return
429
- else:
430
- async for data in blob_iter(response.data_blob_id, self._client.stub):
431
- yield data
432
-
433
- async def _read_file2(self, path: str) -> AsyncIterator[bytes]:
434
416
  req = api_pb2.VolumeGetFile2Request(volume_id=self.object_id, path=path)
435
417
 
436
418
  try:
@@ -461,36 +443,9 @@ class _Volume(_Object, type_prefix="vo"):
461
443
  Read volume file into file-like IO object.
462
444
  """
463
445
  if progress_cb is None:
464
-
465
446
  def progress_cb(*_, **__):
466
447
  pass
467
448
 
468
- if self._is_v1:
469
- return await self._read_file_into_fileobj1(path, fileobj, progress_cb)
470
- else:
471
- return await self._read_file_into_fileobj2(path, fileobj, progress_cb)
472
-
473
- async def _read_file_into_fileobj1(
474
- self, path: str, fileobj: typing.IO[bytes], progress_cb: Callable[..., Any]
475
- ) -> int:
476
- num_bytes_written = 0
477
-
478
- async for chunk in self._read_file1(path):
479
- num_chunk_bytes_written = 0
480
-
481
- while num_chunk_bytes_written < len(chunk):
482
- # TODO(dflemstr): this is a small write, but nonetheless might block the event loop for some time:
483
- n = fileobj.write(chunk)
484
- num_chunk_bytes_written += n
485
- progress_cb(advance=n)
486
-
487
- num_bytes_written += len(chunk)
488
-
489
- return num_bytes_written
490
-
491
- async def _read_file_into_fileobj2(
492
- self, path: str, fileobj: typing.IO[bytes], progress_cb: Callable[..., Any]
493
- ) -> int:
494
449
  req = api_pb2.VolumeGetFile2Request(volume_id=self.object_id, path=path)
495
450
 
496
451
  try:
@@ -567,8 +522,12 @@ class _Volume(_Object, type_prefix="vo"):
567
522
  like `os.rename()` and then `commit()` the volume. The `copy_files()` method is useful when you don't have
568
523
  the volume mounted as a filesystem, e.g. when running a script on your local computer.
569
524
  """
570
- request = api_pb2.VolumeCopyFilesRequest(volume_id=self.object_id, src_paths=src_paths, dst_path=dst_path)
571
- await retry_transient_errors(self._client.stub.VolumeCopyFiles, request, base_delay=1)
525
+ if self._is_v1:
526
+ request = api_pb2.VolumeCopyFilesRequest(volume_id=self.object_id, src_paths=src_paths, dst_path=dst_path)
527
+ await retry_transient_errors(self._client.stub.VolumeCopyFiles, request, base_delay=1)
528
+ else:
529
+ request = api_pb2.VolumeCopyFiles2Request(volume_id=self.object_id, src_paths=src_paths, dst_path=dst_path)
530
+ await retry_transient_errors(self._client.stub.VolumeCopyFiles2, request, base_delay=1)
572
531
 
573
532
  @live_method
574
533
  async def batch_upload(self, force: bool = False) -> "_AbstractVolumeUploadContextManager":
@@ -87,20 +87,12 @@ class _Volume(modal._object._Object):
87
87
  def iterdir(self, path: str, *, recursive: bool = True) -> collections.abc.AsyncIterator[FileEntry]: ...
88
88
  async def listdir(self, path: str, *, recursive: bool = False) -> list[FileEntry]: ...
89
89
  def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
90
- def _read_file1(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
91
- def _read_file2(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
92
90
  async def read_file_into_fileobj(
93
91
  self,
94
92
  path: str,
95
93
  fileobj: typing.IO[bytes],
96
94
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
97
95
  ) -> int: ...
98
- async def _read_file_into_fileobj1(
99
- self, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
100
- ) -> int: ...
101
- async def _read_file_into_fileobj2(
102
- self, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
103
- ) -> int: ...
104
96
  async def remove_file(self, path: str, recursive: bool = False) -> None: ...
105
97
  async def copy_files(self, src_paths: collections.abc.Sequence[str], dst_path: str) -> None: ...
106
98
  async def batch_upload(self, force: bool = False) -> _AbstractVolumeUploadContextManager: ...
@@ -236,18 +228,6 @@ class Volume(modal.object.Object):
236
228
 
237
229
  read_file: __read_file_spec[typing_extensions.Self]
238
230
 
239
- class ___read_file1_spec(typing_extensions.Protocol[SUPERSELF]):
240
- def __call__(self, /, path: str) -> typing.Iterator[bytes]: ...
241
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]: ...
242
-
243
- _read_file1: ___read_file1_spec[typing_extensions.Self]
244
-
245
- class ___read_file2_spec(typing_extensions.Protocol[SUPERSELF]):
246
- def __call__(self, /, path: str) -> typing.Iterator[bytes]: ...
247
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]: ...
248
-
249
- _read_file2: ___read_file2_spec[typing_extensions.Self]
250
-
251
231
  class __read_file_into_fileobj_spec(typing_extensions.Protocol[SUPERSELF]):
252
232
  def __call__(
253
233
  self,
@@ -266,26 +246,6 @@ class Volume(modal.object.Object):
266
246
 
267
247
  read_file_into_fileobj: __read_file_into_fileobj_spec[typing_extensions.Self]
268
248
 
269
- class ___read_file_into_fileobj1_spec(typing_extensions.Protocol[SUPERSELF]):
270
- def __call__(
271
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
272
- ) -> int: ...
273
- async def aio(
274
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
275
- ) -> int: ...
276
-
277
- _read_file_into_fileobj1: ___read_file_into_fileobj1_spec[typing_extensions.Self]
278
-
279
- class ___read_file_into_fileobj2_spec(typing_extensions.Protocol[SUPERSELF]):
280
- def __call__(
281
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
282
- ) -> int: ...
283
- async def aio(
284
- self, /, path: str, fileobj: typing.IO[bytes], progress_cb: collections.abc.Callable[..., typing.Any]
285
- ) -> int: ...
286
-
287
- _read_file_into_fileobj2: ___read_file_into_fileobj2_spec[typing_extensions.Self]
288
-
289
249
  class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
290
250
  def __call__(self, /, path: str, recursive: bool = False) -> None: ...
291
251
  async def aio(self, /, path: str, recursive: bool = False) -> None: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.3.dev14
3
+ Version: 1.0.3.dev21
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1957,11 +1957,11 @@ message ImageMetadata {
1957
1957
  // package name -> version. Empty if missing
1958
1958
  map<string, string> python_packages = 2;
1959
1959
 
1960
- // The working directory of the image, defaulting to "/". Not set if missing.
1960
+ // The working directory of the image, as an absolute file path.
1961
1961
  //
1962
- // Note that this is NOT the actual working directory of the image, especially
1963
- // for runner tasks. Please see `ImageManifest.workdir` instead. This field
1964
- // should not be used in any future code.
1962
+ // For most images, this is not set, which means to use the default workdir:
1963
+ // - On function runners, the default is `/root` (home directory).
1964
+ // - For image builds and sandbox environments, it is `/`.
1965
1965
  optional string workdir = 3;
1966
1966
 
1967
1967
  // The version of glibc in this image, if any.
@@ -6313,11 +6313,11 @@ class ImageMetadata(google.protobuf.message.Message):
6313
6313
  package name -> version. Empty if missing
6314
6314
  """
6315
6315
  workdir: builtins.str
6316
- """The working directory of the image, defaulting to "/". Not set if missing.
6316
+ """The working directory of the image, as an absolute file path.
6317
6317
 
6318
- Note that this is NOT the actual working directory of the image, especially
6319
- for runner tasks. Please see `ImageManifest.workdir` instead. This field
6320
- should not be used in any future code.
6318
+ For most images, this is not set, which means to use the default workdir:
6319
+ - On function runners, the default is `/root` (home directory).
6320
+ - For image builds and sandbox environments, it is `/`.
6321
6321
  """
6322
6322
  libc_version_info: builtins.str
6323
6323
  """The version of glibc in this image, if any."""
@@ -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.0.3.dev14"
4
+ __version__ = "1.0.3.dev21"
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