modal 1.4.2.dev14__tar.gz → 1.4.3.dev0__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 (211) hide show
  1. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/PKG-INFO +1 -1
  2. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/sandbox_fs_utils.py +37 -0
  3. modal-1.4.3.dev0/modal/cli/bootstrap.py +136 -0
  4. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/entry_point.py +8 -24
  5. modal-1.4.3.dev0/modal/cli/logo.py +70 -0
  6. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/selector.py +12 -4
  7. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/client.pyi +2 -2
  8. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/exception.py +6 -0
  9. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/file_io.py +2 -2
  10. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/functions.pyi +6 -6
  11. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox.py +20 -2
  12. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox.pyi +30 -6
  13. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox_fs.py +38 -1
  14. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/sandbox_fs.pyi +84 -3
  15. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/PKG-INFO +1 -1
  16. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/SOURCES.txt +2 -0
  17. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_version/__init__.py +1 -1
  18. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/LICENSE +0 -0
  19. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/README.md +0 -0
  20. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/__init__.py +0 -0
  21. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/__main__.py +0 -0
  22. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_billing.py +0 -0
  23. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_clustered_functions.py +0 -0
  24. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_clustered_functions.pyi +0 -0
  25. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_container_entrypoint.py +0 -0
  26. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_functions.py +0 -0
  27. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_grpc_client.py +0 -0
  28. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_ipython.py +0 -0
  29. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_load_context.py +0 -0
  30. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_location.py +0 -0
  31. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_logs.py +0 -0
  32. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_object.py +0 -0
  33. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/__init__.py +0 -0
  34. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/manager.py +0 -0
  35. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/pty.py +0 -0
  36. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/rich.py +0 -0
  37. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_output/status.py +0 -0
  38. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_partial_function.py +0 -0
  39. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_resolver.py +0 -0
  40. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_resources.py +0 -0
  41. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/__init__.py +0 -0
  42. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/asgi.py +0 -0
  43. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/container_io_manager.py +0 -0
  44. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  45. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/execution_context.py +0 -0
  46. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/execution_context.pyi +0 -0
  47. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  48. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/telemetry.py +0 -0
  49. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  50. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_runtime/user_code_imports.py +0 -0
  51. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_serialization.py +0 -0
  52. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_server.py +0 -0
  53. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_traceback.py +0 -0
  54. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_tunnel.py +0 -0
  55. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_tunnel.pyi +0 -0
  56. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_type_manager.py +0 -0
  57. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/__init__.py +0 -0
  58. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/app_utils.py +0 -0
  59. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/async_utils.py +0 -0
  60. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/auth_token_manager.py +0 -0
  61. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/blob_utils.py +0 -0
  62. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/browser_utils.py +0 -0
  63. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  64. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/deprecation.py +0 -0
  65. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/docker_utils.py +0 -0
  66. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/function_utils.py +0 -0
  67. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/git_utils.py +0 -0
  68. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/grpc_testing.py +0 -0
  69. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/grpc_utils.py +0 -0
  70. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/hash_utils.py +0 -0
  71. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/http_utils.py +0 -0
  72. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/jwt_utils.py +0 -0
  73. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/logger.py +0 -0
  74. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/mount_utils.py +0 -0
  75. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/name_utils.py +0 -0
  76. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/package_utils.py +0 -0
  77. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/pattern_utils.py +0 -0
  78. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  79. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/shell_utils.py +0 -0
  80. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/task_command_router_client.py +0 -0
  81. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_utils/time_utils.py +0 -0
  82. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/__init__.py +0 -0
  83. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  84. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/cloudpickle.py +0 -0
  85. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/tblib.py +0 -0
  86. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_vendor/version.py +0 -0
  87. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/_watcher.py +0 -0
  88. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/app.py +0 -0
  89. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/app.pyi +0 -0
  90. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/billing.py +0 -0
  91. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2023.12.312.txt +0 -0
  92. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2023.12.txt +0 -0
  93. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2024.04.txt +0 -0
  94. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2024.10.txt +0 -0
  95. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/2025.06.txt +0 -0
  96. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/PREVIEW.txt +0 -0
  97. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/README.md +0 -0
  98. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/builder/base-images.json +0 -0
  99. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/call_graph.py +0 -0
  100. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/__init__.py +0 -0
  101. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/_download.py +0 -0
  102. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/_traceback.py +0 -0
  103. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/app.py +0 -0
  104. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/billing.py +0 -0
  105. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/changelog.py +0 -0
  106. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/cluster.py +0 -0
  107. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/config.py +0 -0
  108. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/container.py +0 -0
  109. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/dashboard.py +0 -0
  110. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/dict.py +0 -0
  111. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/environment.py +0 -0
  112. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/import_refs.py +0 -0
  113. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/launch.py +0 -0
  114. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/network_file_system.py +0 -0
  115. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/profile.py +0 -0
  116. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/programs/__init__.py +0 -0
  117. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  118. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/programs/vscode.py +0 -0
  119. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/queues.py +0 -0
  120. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/run.py +0 -0
  121. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/secret.py +0 -0
  122. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/shell.py +0 -0
  123. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/token.py +0 -0
  124. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/utils.py +0 -0
  125. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cli/volume.py +0 -0
  126. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/client.py +0 -0
  127. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cloud_bucket_mount.py +0 -0
  128. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  129. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cls.py +0 -0
  130. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/cls.pyi +0 -0
  131. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/config.py +0 -0
  132. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/container_process.py +0 -0
  133. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/container_process.pyi +0 -0
  134. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/dict.py +0 -0
  135. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/dict.pyi +0 -0
  136. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/environments.py +0 -0
  137. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/environments.pyi +0 -0
  138. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/__init__.py +0 -0
  139. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/flash.py +0 -0
  140. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/flash.pyi +0 -0
  141. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/experimental/ipython.py +0 -0
  142. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/file_io.pyi +0 -0
  143. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/file_pattern_matcher.py +0 -0
  144. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/functions.py +0 -0
  145. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/image.py +0 -0
  146. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/image.pyi +0 -0
  147. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/io_streams.py +0 -0
  148. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/io_streams.pyi +0 -0
  149. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/mount.py +0 -0
  150. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/mount.pyi +0 -0
  151. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/network_file_system.py +0 -0
  152. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/network_file_system.pyi +0 -0
  153. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/object.py +0 -0
  154. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/object.pyi +0 -0
  155. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/output.py +0 -0
  156. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/parallel_map.py +0 -0
  157. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/parallel_map.pyi +0 -0
  158. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/partial_function.py +0 -0
  159. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/partial_function.pyi +0 -0
  160. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/proxy.py +0 -0
  161. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/proxy.pyi +0 -0
  162. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/py.typed +0 -0
  163. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/queue.py +0 -0
  164. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/queue.pyi +0 -0
  165. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/retries.py +0 -0
  166. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/runner.py +0 -0
  167. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/runner.pyi +0 -0
  168. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/running_app.py +0 -0
  169. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/schedule.py +0 -0
  170. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/scheduler_placement.py +0 -0
  171. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/secret.py +0 -0
  172. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/secret.pyi +0 -0
  173. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/server.py +0 -0
  174. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/server.pyi +0 -0
  175. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/serving.py +0 -0
  176. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/serving.pyi +0 -0
  177. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/snapshot.py +0 -0
  178. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/snapshot.pyi +0 -0
  179. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/stream_type.py +0 -0
  180. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/token_flow.py +0 -0
  181. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/token_flow.pyi +0 -0
  182. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/volume.py +0 -0
  183. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal/volume.pyi +0 -0
  184. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/dependency_links.txt +0 -0
  185. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/entry_points.txt +0 -0
  186. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/requires.txt +0 -0
  187. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal.egg-info/top_level.txt +0 -0
  188. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/__init__.py +0 -0
  189. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_cli_docs.py +0 -0
  190. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  191. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_reference_docs.py +0 -0
  192. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  193. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/mdmd/__init__.py +0 -0
  194. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  195. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_docs/mdmd/signatures.py +0 -0
  196. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/__init__.py +0 -0
  197. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_grpc.py +0 -0
  198. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2.py +0 -0
  199. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2.pyi +0 -0
  200. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2_grpc.py +0 -0
  201. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
  202. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/modal_api_grpc.py +0 -0
  203. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/py.typed +0 -0
  204. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  205. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  206. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  207. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  208. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  209. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/modal_version/__main__.py +0 -0
  210. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/pyproject.toml +0 -0
  211. {modal-1.4.2.dev14 → modal-1.4.3.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.2.dev14
3
+ Version: 1.4.3.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -18,6 +18,7 @@ from ..exception import (
18
18
  SandboxFilesystemIsADirectoryError,
19
19
  SandboxFilesystemNotADirectoryError,
20
20
  SandboxFilesystemNotFoundError,
21
+ SandboxFilesystemPathAlreadyExistsError,
21
22
  SandboxFilesystemPermissionError,
22
23
  ServiceError,
23
24
  )
@@ -188,6 +189,42 @@ def raise_remove_error(returncode: int, stderr: Union[str, bytes], remote_path:
188
189
  raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
189
190
 
190
191
 
192
+ def make_make_directory_command(remote_path: str, create_parents: bool) -> str:
193
+ """Build the JSON command string for a MakeDirectory operation.
194
+
195
+ The returned JSON must match the `Command` enum in the modal-sandbox-fs-tools
196
+ Rust crate (crates/modal-sandbox-fs-tools/src/lib.rs). Treat changes to
197
+ this schema like protobuf changes: fields must not be removed or renamed,
198
+ only added with backwards-compatible defaults.
199
+ """
200
+ return json.dumps({"MakeDirectory": {"path": remote_path, "parents": create_parents}})
201
+
202
+
203
+ def raise_make_directory_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
204
+ if payload := try_parse_error_payload(stderr):
205
+ logger.debug(
206
+ f"sandbox-fs-tools make_directory error: path={remote_path}, "
207
+ f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
208
+ )
209
+ if payload.error_kind == "NotFound":
210
+ raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
211
+ if payload.error_kind == "PathAlreadyExists":
212
+ raise SandboxFilesystemPathAlreadyExistsError(f"{payload.message}: {remote_path}")
213
+ if payload.error_kind == "NotDirectory":
214
+ raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
215
+ if payload.error_kind == "PermissionDenied":
216
+ raise SandboxFilesystemPermissionError(f"{payload.message}: {remote_path}")
217
+ if payload.error_kind == "NotSupported":
218
+ raise InvalidError(
219
+ f"{payload.message}: {remote_path} - this operation is not supported for CloudBucketMounts"
220
+ )
221
+ raise SandboxFilesystemError(payload.message)
222
+
223
+ if stderr_text := _stderr_to_text(stderr):
224
+ logger.debug(f"Unstructured modal-sandbox-fs-tools stderr: {stderr_text}")
225
+ raise SandboxFilesystemError(f"Operation on '{remote_path}' failed with exit code {returncode}")
226
+
227
+
191
228
  def validate_absolute_remote_path(remote_path: str, operation: str) -> None:
192
229
  if not PurePosixPath(remote_path).is_absolute():
193
230
  raise InvalidError(f"Sandbox.filesystem.{operation}() currently only supports absolute remote_path values")
@@ -0,0 +1,136 @@
1
+ # Copyright Modal Labs 2026
2
+ import io
3
+ import shutil
4
+ import zipfile
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ import typer
10
+
11
+ from modal._utils.async_utils import synchronizer
12
+ from modal._utils.http_utils import _http_client_with_tls
13
+ from modal.client import _Client
14
+ from modal.config import logger
15
+ from modal.output import OutputManager
16
+ from modal_proto import api_pb2
17
+
18
+ from .logo import GREEN, print_logo
19
+ from .selector import Selector
20
+
21
+
22
+ @synchronizer.create_blocking
23
+ async def bootstrap(
24
+ name: Optional[str] = typer.Argument(None, help="The name of the template to load."),
25
+ output: str = typer.Option(".", "-o", "--output", help="Location for storing the template."),
26
+ force: bool = typer.Option(False, "--force", help="Overwrite the output directory if it already exists."),
27
+ ) -> None:
28
+ """Initialize a sample Modal App."""
29
+ client = await _Client.from_env()
30
+ resp = await client.stub.TemplateList(api_pb2.TemplateListRequest())
31
+ names = sorted([item.name for item in resp.items])
32
+
33
+ output_manager = OutputManager.get()
34
+ print_logo()
35
+
36
+ if name is None:
37
+ if not names:
38
+ output_manager.print("No templates available. Try updating modal.")
39
+ raise typer.Exit(1)
40
+ try:
41
+ selector = Selector(names, title="Select a template", highlight_style=f"bold {GREEN}")
42
+ name = selector.run()
43
+ except Exception:
44
+ output_manager.print("Available templates:")
45
+ for n in names:
46
+ output_manager.print(f" - {n}")
47
+ output_manager.print("\nRun `modal bootstrap <name>` to select a template.")
48
+ raise typer.Exit(0)
49
+
50
+ item = next((item for item in resp.items if item.name == name), None)
51
+ if item is None:
52
+ output_manager.print(f"Unknown template: {name}")
53
+ output_manager.print(f"Available templates: {', '.join(names)}")
54
+ raise typer.Exit(1)
55
+
56
+ template_root = Path(output)
57
+ template_root.mkdir(parents=True, exist_ok=True)
58
+ template_written_to_cwd = template_root.resolve() == Path.cwd().resolve()
59
+
60
+ dest = template_root / name
61
+ if dest.exists() and not force:
62
+ raise click.UsageError(f"Output path '{dest}' already exists. Use --force to overwrite.")
63
+
64
+ # Download the repo archive
65
+ ref = item.ref or "main"
66
+ archive_url = item.repo.rstrip("/") + f"/archive/refs/heads/{ref}.zip"
67
+
68
+ with output_manager.status(f"Downloading template '{name}'..."):
69
+ try:
70
+ session = _http_client_with_tls(timeout=30)
71
+ try:
72
+ async with session.get(archive_url) as resp_http:
73
+ resp_http.raise_for_status()
74
+ data = await resp_http.read()
75
+ finally:
76
+ await session.close()
77
+ except Exception as e:
78
+ output_manager.print(f"Failed to download template from {archive_url}: {e}")
79
+ raise typer.Exit(1)
80
+
81
+ try:
82
+ zf = zipfile.ZipFile(io.BytesIO(data))
83
+ all_names = zf.namelist()
84
+ root_prefix = all_names[0].split("/")[0] + "/"
85
+ except Exception as e:
86
+ logger.debug(f"Failed to parse template archive: {e}")
87
+ output_manager.print(f"Unable to fetch template '{name}'.")
88
+ raise typer.Exit(1)
89
+
90
+ with zf:
91
+ template_prefix = root_prefix + name + "/"
92
+ logger.debug(f"Zip root: {root_prefix!r}, template: {template_prefix!r}, entries: {len(all_names)}")
93
+ for entry in all_names[:20]:
94
+ logger.debug(f" {entry}")
95
+
96
+ members = [m for m in all_names if m.startswith(template_prefix) and m != template_prefix]
97
+ logger.debug(f"Matched {len(members)} members under {template_prefix!r}")
98
+
99
+ if not members:
100
+ output_manager.print(f"Template '{name}' not found in repository.")
101
+ raise typer.Exit(1)
102
+
103
+ if dest.exists() and force:
104
+ shutil.rmtree(dest)
105
+
106
+ dest.mkdir(parents=True, exist_ok=True)
107
+
108
+ resolved_dest = dest.resolve()
109
+ for member in members:
110
+ rel_path = member[len(template_prefix) :]
111
+ out_path = (dest / rel_path).resolve()
112
+ if not out_path.is_relative_to(resolved_dest):
113
+ logger.debug(f"Zip entry escapes destination: {member!r}")
114
+ output_manager.print(f"Unable to fetch template '{name}'.")
115
+ raise typer.Exit(1)
116
+ if member.endswith("/"):
117
+ out_path.mkdir(parents=True, exist_ok=True)
118
+ else:
119
+ out_path.parent.mkdir(parents=True, exist_ok=True)
120
+ out_path.write_bytes(zf.read(member))
121
+
122
+ output_manager.print(f"[{GREEN}]✓[/{GREEN}] Template '{name}' written to {dest}", highlight=False)
123
+ output_manager.print(f"[{GREEN}]→[/{GREEN}] To see it in action, run the following command:")
124
+ amp = " [dim white]&&[/dim white] "
125
+ if template_written_to_cwd:
126
+ steps = []
127
+ else:
128
+ steps = [f"[#ff8de6]cd {template_root}[/#ff8de6]"]
129
+ steps.extend(
130
+ [
131
+ f"[{GREEN}]modal deploy -m {name}.app[/{GREEN}]",
132
+ f"[#91c8ef]python -m {name}.try[/#91c8ef]",
133
+ ]
134
+ )
135
+ command = amp.join(steps)
136
+ output_manager.print(command)
@@ -11,6 +11,7 @@ from modal.output import OutputManager
11
11
  from . import run, shell as shell_module
12
12
  from .app import app_cli
13
13
  from .billing import billing_cli
14
+ from .bootstrap import bootstrap
14
15
  from .changelog import changelog
15
16
  from .cluster import cluster_cli
16
17
  from .config import config_cli
@@ -19,6 +20,7 @@ from .dashboard import dashboard
19
20
  from .dict import dict_cli
20
21
  from .environment import environment_cli
21
22
  from .launch import launch_cli
23
+ from .logo import print_logo
22
24
  from .network_file_system import nfs_cli
23
25
  from .profile import profile_cli
24
26
  from .queues import queue_cli
@@ -88,28 +90,7 @@ def check_path():
88
90
  @synchronizer.create_blocking
89
91
  async def setup(profile: Optional[str] = None):
90
92
  check_path()
91
-
92
- art = """
93
- ############# #############
94
- #### ## #### ##
95
- ## ## ## ## ## ##
96
- ## ## ## ## ## ##
97
- ## ## #### ## ##
98
- ## ############# ## ##
99
- ## ## #### ## ##
100
- ## ## ## ## ## ##
101
- ## ## ## ## ## ##
102
- ## ## ## ## ## ##
103
- ## ## ## ## ## ##
104
- ## ## ## ## #############
105
- ## ## ## ## ## ##
106
- ## ## ## ## ## ##
107
- ## ## ## ## ## ##
108
- #### ## #### ##
109
- ############# #############
110
- """
111
-
112
- OutputManager.get().print(art, highlight=False, style="green")
93
+ print_logo()
113
94
 
114
95
  # Fetch a new token (same as `modal token new` but redirect to /home once finishes)
115
96
  await _new_token(profile=profile, next_url="/home")
@@ -145,8 +126,11 @@ entrypoint_cli_typer.add_typer(billing_cli, rich_help_panel="Observability")
145
126
  entrypoint_cli_typer.command("changelog", rich_help_panel="Observability")(changelog)
146
127
  entrypoint_cli_typer.command("dashboard", rich_help_panel="Observability")(dashboard)
147
128
 
148
- # Hide setup from help as it's redundant with modal token new, but nicer for onboarding
149
- entrypoint_cli_typer.command("setup", help="Bootstrap Modal's configuration.", rich_help_panel="Onboarding")(setup)
129
+ # Onboarding
130
+ entrypoint_cli_typer.command("setup", help="Get started using Modal.", rich_help_panel="Onboarding")(setup)
131
+ entrypoint_cli_typer.command("bootstrap", help="Initialize a sample Modal App.", rich_help_panel="Onboarding")(
132
+ bootstrap
133
+ )
150
134
 
151
135
  # Special handling for modal run, which is more complicated
152
136
  entrypoint_cli = typer.main.get_command(entrypoint_cli_typer)
@@ -0,0 +1,70 @@
1
+ # Copyright Modal Labs 2026
2
+ import sys
3
+
4
+ from modal.output import OutputManager
5
+
6
+ GREEN = "#7FEE64"
7
+
8
+ ASCII_LOGO = """
9
+ ############# #############
10
+ #### ## #### ##
11
+ ## ## ## ## ## ##
12
+ ## ## ## ## ## ##
13
+ ## ## #### ## ##
14
+ ## ############# ## ##
15
+ ## ## #### ## ##
16
+ ## ## ## ## ## ##
17
+ ## ## ## ## ## ##
18
+ ## ## ## ## ## ##
19
+ ## ## ## ## ## ##
20
+ ## ## ## ## #############
21
+ ## ## ## ## ## ##
22
+ ## ## ## ## ## ##
23
+ ## ## ## ## ## ##
24
+ #### ## #### ##
25
+ ############# #############
26
+ """
27
+
28
+ UNICODE_LOGO_LARGE = """
29
+ █████████████ █████████████
30
+ ████ ██ ████ ██
31
+ ██ ██ ██ ██ ██ ██
32
+ ██ ██ ██ ██ ██ ██
33
+ ██ ██ ████ ██ ██
34
+ ██ █████████████ ██ ██
35
+ ██ ██ ████ ██ ██
36
+ ██ ██ ██ ██ ██ ██
37
+ ██ ██ ██ ██ ██ ██
38
+ ██ ██ ██ ██ ██ ██
39
+ ██ ██ ██ ██ ██ ██
40
+ ██ ██ ██ ██ █████████████
41
+ ██ ██ ██ ██ ██ ██
42
+ ██ ██ ██ ██ ██ ██
43
+ ██ ██ ██ ██ ██ ██
44
+ ████ ██ ████ ██
45
+ █████████████ █████████████
46
+ """
47
+
48
+ UNICODE_LOGO_SMALL = """
49
+ ▟█▀▀▀▀▜▖ ▗█▛▀▀▀▀▙
50
+ ▟▘▝▙ ▜▖▗▛ ▜▖ ▝▙
51
+ ▟▘ ▝▙▄▄▄▄█▛ ▜▖ ▝▙
52
+ ▟▘ ▟▘ ▗▛▜▖ ▜▖ ▝▙
53
+ ▟▘ ▟▘ ▗▛ ▜▖ ▜▖ ▝▙
54
+ ▟▘ ▟▘ ▗▛ ▜▖ ▜▄▄▄▄▟▙
55
+ ▝▙ ▟▘ ▗▛ ▜▖ ▗▛ ▟▘
56
+ ▝▙▟▘ ▗▛ ▜▄▛ ▟▘
57
+ ▝▀▀▀▀▀▀ ▀▀▀▀▀▀▘
58
+ """
59
+
60
+
61
+ def get_logo():
62
+ if sys.stdout.encoding and sys.stdout.encoding.lower().startswith("utf"):
63
+ return UNICODE_LOGO_SMALL
64
+ else:
65
+ return ASCII_LOGO
66
+
67
+
68
+ def print_logo():
69
+ if sys.stdout.isatty():
70
+ OutputManager.get().print(get_logo(), highlight=False, style=GREEN)
@@ -19,6 +19,13 @@ def _cbreak_terminal():
19
19
  old_settings = termios.tcgetattr(fd)
20
20
  try:
21
21
  tty.setcbreak(fd, termios.TCSADRAIN)
22
+ # Disable ISIG so Ctrl-C is delivered as \x03 to os.read() rather than
23
+ # generating an asynchronous SIGINT. This lets _input_loop raise
24
+ # KeyboardInterrupt synchronously, ensuring context-manager cleanup
25
+ # (cursor visibility, terminal echo) completes without interruption.
26
+ attrs = termios.tcgetattr(fd)
27
+ attrs[3] &= ~termios.ISIG
28
+ termios.tcsetattr(fd, termios.TCSADRAIN, attrs)
22
29
  yield
23
30
  finally:
24
31
  termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
@@ -44,10 +51,10 @@ class Selector:
44
51
  exiting.
45
52
  """
46
53
 
47
- def __init__(self, options: list[str], title: str = "Select an option"):
54
+ def __init__(self, options: list[str], title: str = "Select an option", highlight_style: str = "bold green"):
48
55
  if not options:
49
56
  raise ValueError("options must not be empty")
50
- if not sys.stdin.isatty() or not sys.stdout.isatty():
57
+ if not sys.stdin.isatty() or not sys.stdout.isatty() or Console().is_dumb_terminal:
51
58
  raise RuntimeError("Interactive selection requires a TTY")
52
59
  # Fail fast on platforms without termios (e.g. Windows).
53
60
  import termios # noqa: F401
@@ -55,6 +62,7 @@ class Selector:
55
62
  self.options = options
56
63
  self.title = title
57
64
  self.selected = 0
65
+ self.highlight_style = highlight_style
58
66
 
59
67
  # -- state manipulation ---------------------------------------------------
60
68
 
@@ -75,8 +83,8 @@ class Selector:
75
83
  text.append(f"{self.title}\n\n", style="bold")
76
84
  for i, opt in enumerate(self.options):
77
85
  if i == self.selected:
78
- text.append(" > ", style="bold green")
79
- text.append(f"{opt}\n", style="bold green")
86
+ text.append(" > ", style=self.highlight_style)
87
+ text.append(f"{opt}\n", style=self.highlight_style)
80
88
  else:
81
89
  text.append(f" {opt}\n")
82
90
  return text
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.4.2.dev14",
38
+ version: str = "1.4.3.dev0",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -175,7 +175,7 @@ class Client:
175
175
  server_url: str,
176
176
  client_type: int,
177
177
  credentials: typing.Optional[tuple[str, str]],
178
- version: str = "1.4.2.dev14",
178
+ version: str = "1.4.3.dev0",
179
179
  ):
180
180
  """mdmd:hidden
181
181
  The Modal client object is not intended to be instantiated directly by users.
@@ -379,3 +379,9 @@ class SandboxFilesystemFileTooLargeError(SandboxFilesystemError):
379
379
  """Raised when a file exceeds the maximum allowed size for a read operation in the sandbox."""
380
380
 
381
381
  pass
382
+
383
+
384
+ class SandboxFilesystemPathAlreadyExistsError(SandboxFilesystemError):
385
+ """Raised when a path already exists and the operation requires it to be absent."""
386
+
387
+ pass
@@ -406,7 +406,7 @@ class _FileIO(Generic[T]):
406
406
  """Create a new directory."""
407
407
  deprecation_warning(
408
408
  (2026, 3, 9),
409
- "`FileIO.mkdir()` is deprecated. Use `Sandbox.mkdir()` instead.",
409
+ "`FileIO.mkdir()` is deprecated. Use `Sandbox.filesystem.make_directory()` instead.",
410
410
  pending=True,
411
411
  )
412
412
  await mkdir(path, client, task_id, parents)
@@ -416,7 +416,7 @@ class _FileIO(Generic[T]):
416
416
  """Remove a file or directory in the Sandbox."""
417
417
  deprecation_warning(
418
418
  (2026, 3, 9),
419
- "`FileIO.rm()` is deprecated. Use `Sandbox.rm()` instead.",
419
+ "`FileIO.rm()` is deprecated. Use `Sandbox.filesystem.remove()` instead.",
420
420
  pending=True,
421
421
  )
422
422
  await rm(path, client, task_id, recursive)
@@ -347,7 +347,7 @@ class Function(
347
347
 
348
348
  _call_generator: ___call_generator_spec
349
349
 
350
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
350
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
351
351
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
352
352
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
353
353
  ...
@@ -356,7 +356,7 @@ class Function(
356
356
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
357
357
  ...
358
358
 
359
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
359
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
360
360
 
361
361
  class __remote_gen_spec(typing_extensions.Protocol):
362
362
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -383,7 +383,7 @@ class Function(
383
383
  """
384
384
  ...
385
385
 
386
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
386
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
387
387
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
388
388
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
389
389
 
@@ -406,7 +406,7 @@ class Function(
406
406
  """
407
407
  ...
408
408
 
409
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
409
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
410
410
 
411
411
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
412
412
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -414,7 +414,7 @@ class Function(
414
414
 
415
415
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
416
416
 
417
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
417
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
418
418
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
419
419
  """Calls the function with the given arguments, without waiting for the results.
420
420
 
@@ -435,7 +435,7 @@ class Function(
435
435
  """
436
436
  ...
437
437
 
438
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
438
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
439
439
 
440
440
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
441
441
  """Return the inner Python object wrapped by this Modal Function."""
@@ -1647,14 +1647,32 @@ class _Sandbox(_Object, type_prefix="sb"):
1647
1647
  return await ls(path, self._client, task_id)
1648
1648
 
1649
1649
  async def mkdir(self, path: str, parents: bool = False) -> None:
1650
- """[Alpha] Create a new directory in the Sandbox."""
1650
+ """[Alpha] Create a new directory in the Sandbox.
1651
+
1652
+ .. deprecated:: 2026-04-15
1653
+ Use `Sandbox.filesystem.make_directory()` instead.
1654
+ """
1651
1655
  self._ensure_v1("mkdir")
1656
+ deprecation_warning(
1657
+ (2026, 4, 15),
1658
+ "`Sandbox.mkdir()` is deprecated. Use `Sandbox.filesystem.make_directory()` instead.",
1659
+ pending=True,
1660
+ )
1652
1661
  task_id = await self._get_task_id()
1653
1662
  return await mkdir(path, self._client, task_id, parents)
1654
1663
 
1655
1664
  async def rm(self, path: str, recursive: bool = False) -> None:
1656
- """[Alpha] Remove a file or directory in the Sandbox."""
1665
+ """[Alpha] Remove a file or directory in the Sandbox.
1666
+
1667
+ .. deprecated:: 2026-04-15
1668
+ Use `Sandbox.filesystem.remove()` instead.
1669
+ """
1657
1670
  self._ensure_v1("rm")
1671
+ deprecation_warning(
1672
+ (2026, 4, 15),
1673
+ "`Sandbox.rm()` is deprecated. Use `Sandbox.filesystem.remove()` instead.",
1674
+ pending=True,
1675
+ )
1658
1676
  task_id = await self._get_task_id()
1659
1677
  return await rm(path, self._client, task_id, recursive)
1660
1678
 
@@ -617,11 +617,19 @@ class _Sandbox(modal._object._Object):
617
617
  ...
618
618
 
619
619
  async def mkdir(self, path: str, parents: bool = False) -> None:
620
- """[Alpha] Create a new directory in the Sandbox."""
620
+ """[Alpha] Create a new directory in the Sandbox.
621
+
622
+ .. deprecated:: 2026-04-15
623
+ Use `Sandbox.filesystem.make_directory()` instead.
624
+ """
621
625
  ...
622
626
 
623
627
  async def rm(self, path: str, recursive: bool = False) -> None:
624
- """[Alpha] Remove a file or directory in the Sandbox."""
628
+ """[Alpha] Remove a file or directory in the Sandbox.
629
+
630
+ .. deprecated:: 2026-04-15
631
+ Use `Sandbox.filesystem.remove()` instead.
632
+ """
625
633
  ...
626
634
 
627
635
  def watch(
@@ -1911,22 +1919,38 @@ class Sandbox(modal.object.Object):
1911
1919
 
1912
1920
  class __mkdir_spec(typing_extensions.Protocol):
1913
1921
  def __call__(self, /, path: str, parents: bool = False) -> None:
1914
- """[Alpha] Create a new directory in the Sandbox."""
1922
+ """[Alpha] Create a new directory in the Sandbox.
1923
+
1924
+ .. deprecated:: 2026-04-15
1925
+ Use `Sandbox.filesystem.make_directory()` instead.
1926
+ """
1915
1927
  ...
1916
1928
 
1917
1929
  async def aio(self, /, path: str, parents: bool = False) -> None:
1918
- """[Alpha] Create a new directory in the Sandbox."""
1930
+ """[Alpha] Create a new directory in the Sandbox.
1931
+
1932
+ .. deprecated:: 2026-04-15
1933
+ Use `Sandbox.filesystem.make_directory()` instead.
1934
+ """
1919
1935
  ...
1920
1936
 
1921
1937
  mkdir: __mkdir_spec
1922
1938
 
1923
1939
  class __rm_spec(typing_extensions.Protocol):
1924
1940
  def __call__(self, /, path: str, recursive: bool = False) -> None:
1925
- """[Alpha] Remove a file or directory in the Sandbox."""
1941
+ """[Alpha] Remove a file or directory in the Sandbox.
1942
+
1943
+ .. deprecated:: 2026-04-15
1944
+ Use `Sandbox.filesystem.remove()` instead.
1945
+ """
1926
1946
  ...
1927
1947
 
1928
1948
  async def aio(self, /, path: str, recursive: bool = False) -> None:
1929
- """[Alpha] Remove a file or directory in the Sandbox."""
1949
+ """[Alpha] Remove a file or directory in the Sandbox.
1950
+
1951
+ .. deprecated:: 2026-04-15
1952
+ Use `Sandbox.filesystem.remove()` instead.
1953
+ """
1930
1954
  ...
1931
1955
 
1932
1956
  rm: __rm_spec
@@ -11,9 +11,11 @@ from typing import Union, cast
11
11
  from ._utils.async_utils import synchronize_api
12
12
  from ._utils.logger import logger
13
13
  from ._utils.sandbox_fs_utils import (
14
+ make_make_directory_command,
14
15
  make_read_file_command,
15
16
  make_remove_command,
16
17
  make_write_file_command,
18
+ raise_make_directory_error,
17
19
  raise_read_file_error,
18
20
  raise_remove_error,
19
21
  raise_write_file_error,
@@ -338,7 +340,7 @@ class _SandboxFilesystem:
338
340
  To remove a directory and all its contents:
339
341
 
340
342
  ```python fixture:sandbox
341
- sandbox.exec("mkdir", "-p", "/tmp/mydir/subdir").wait()
343
+ sandbox.filesystem.make_directory("/tmp/mydir/subdir")
342
344
  sandbox.filesystem.remove("/tmp/mydir", recursive=True)
343
345
  ```
344
346
  """
@@ -351,5 +353,40 @@ class _SandboxFilesystem:
351
353
  if returncode != 0:
352
354
  raise_remove_error(returncode, stderr, remote_path)
353
355
 
356
+ async def make_directory(self, remote_path: str, *, create_parents: bool = True) -> None:
357
+ """Create a new directory in the Sandbox.
358
+
359
+ `remote_path` must be an absolute path in the Sandbox.
360
+
361
+ When `create_parents` is `True` (the default), any missing parent directories are created and the call is
362
+ idempotent (succeeds silently if the directory already exists). When `create_parents` is `False`, the
363
+ immediate parent directory must already exist and the path must not already exist.
364
+
365
+ **Raises**
366
+
367
+ - `SandboxFilesystemNotFoundError`: the parent directory does not exist and `create_parents` is `False`.
368
+ - `SandboxFilesystemPathAlreadyExistsError`: the path already exists.
369
+ - `SandboxFilesystemNotADirectoryError`: a path component is not a directory.
370
+ - `SandboxFilesystemPermissionError`: creation is not permitted.
371
+ - `InvalidError`: the operation is not supported by the mount.
372
+ - `SandboxFilesystemError`: the command fails for any other reason.
373
+
374
+ **Usage**
375
+
376
+ ```python fixture:sandbox
377
+ sandbox.filesystem.make_directory("/tmp/a/b/c")
378
+ ```
379
+ """
380
+ validate_absolute_remote_path(remote_path, "make_directory")
381
+
382
+ with translate_exec_errors("make_directory", remote_path):
383
+ process = await self._sandbox.exec(
384
+ _SANDBOX_FS_TOOLS_PATH, make_make_directory_command(remote_path, create_parents)
385
+ )
386
+ stderr, returncode = await asyncio.gather(process.stderr.read(), process.wait())
387
+
388
+ if returncode != 0:
389
+ raise_make_directory_error(returncode, stderr, remote_path)
390
+
354
391
 
355
392
  SandboxFilesystem = synchronize_api(_SandboxFilesystem)