modal 1.4.1.dev1__tar.gz → 1.4.1.dev3__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 (209) hide show
  1. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/PKG-INFO +1 -1
  2. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/__init__.py +2 -1
  3. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/sandbox_fs_utils.py +13 -1
  4. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/run.py +4 -2
  5. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/client.py +4 -1
  6. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/client.pyi +2 -2
  7. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/config.py +30 -0
  8. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/functions.pyi +6 -6
  9. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox.py +108 -0
  10. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox.pyi +127 -0
  11. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/PKG-INFO +1 -1
  12. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_version/__init__.py +1 -1
  13. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/LICENSE +0 -0
  14. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/README.md +0 -0
  15. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/__main__.py +0 -0
  16. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_billing.py +0 -0
  17. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_clustered_functions.py +0 -0
  18. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_clustered_functions.pyi +0 -0
  19. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_container_entrypoint.py +0 -0
  20. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_functions.py +0 -0
  21. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_grpc_client.py +0 -0
  22. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_ipython.py +0 -0
  23. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_load_context.py +0 -0
  24. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_location.py +0 -0
  25. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_logs.py +0 -0
  26. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_object.py +0 -0
  27. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/__init__.py +0 -0
  28. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/manager.py +0 -0
  29. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/pty.py +0 -0
  30. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/rich.py +0 -0
  31. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/status.py +0 -0
  32. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_partial_function.py +0 -0
  33. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_resolver.py +0 -0
  34. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_resources.py +0 -0
  35. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/__init__.py +0 -0
  36. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/asgi.py +0 -0
  37. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/container_io_manager.py +0 -0
  38. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/container_io_manager.pyi +0 -0
  39. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/execution_context.py +0 -0
  40. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/execution_context.pyi +0 -0
  41. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  42. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/telemetry.py +0 -0
  43. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/user_code_event_loop.py +0 -0
  44. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/user_code_imports.py +0 -0
  45. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_serialization.py +0 -0
  46. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_server.py +0 -0
  47. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_traceback.py +0 -0
  48. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_tunnel.py +0 -0
  49. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_tunnel.pyi +0 -0
  50. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_type_manager.py +0 -0
  51. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/__init__.py +0 -0
  52. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/app_utils.py +0 -0
  53. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/async_utils.py +0 -0
  54. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/auth_token_manager.py +0 -0
  55. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/blob_utils.py +0 -0
  56. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/browser_utils.py +0 -0
  57. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/bytes_io_segment_payload.py +0 -0
  58. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/deprecation.py +0 -0
  59. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/docker_utils.py +0 -0
  60. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/function_utils.py +0 -0
  61. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/git_utils.py +0 -0
  62. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/grpc_testing.py +0 -0
  63. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/grpc_utils.py +0 -0
  64. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/hash_utils.py +0 -0
  65. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/http_utils.py +0 -0
  66. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/jwt_utils.py +0 -0
  67. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/logger.py +0 -0
  68. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/mount_utils.py +0 -0
  69. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/name_utils.py +0 -0
  70. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/package_utils.py +0 -0
  71. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/pattern_utils.py +0 -0
  72. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/rand_pb_testing.py +0 -0
  73. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/shell_utils.py +0 -0
  74. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/task_command_router_client.py +0 -0
  75. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/time_utils.py +0 -0
  76. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/__init__.py +0 -0
  77. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  78. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/cloudpickle.py +0 -0
  79. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/tblib.py +0 -0
  80. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/version.py +0 -0
  81. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_watcher.py +0 -0
  82. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/app.py +0 -0
  83. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/app.pyi +0 -0
  84. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/billing.py +0 -0
  85. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2023.12.312.txt +0 -0
  86. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2023.12.txt +0 -0
  87. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2024.04.txt +0 -0
  88. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2024.10.txt +0 -0
  89. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2025.06.txt +0 -0
  90. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/PREVIEW.txt +0 -0
  91. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/README.md +0 -0
  92. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/base-images.json +0 -0
  93. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/call_graph.py +0 -0
  94. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/__init__.py +0 -0
  95. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/_download.py +0 -0
  96. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/_traceback.py +0 -0
  97. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/app.py +0 -0
  98. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/billing.py +0 -0
  99. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/changelog.py +0 -0
  100. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/cluster.py +0 -0
  101. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/config.py +0 -0
  102. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/container.py +0 -0
  103. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/dashboard.py +0 -0
  104. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/dict.py +0 -0
  105. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/entry_point.py +0 -0
  106. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/environment.py +0 -0
  107. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/import_refs.py +0 -0
  108. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/launch.py +0 -0
  109. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/network_file_system.py +0 -0
  110. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/profile.py +0 -0
  111. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/programs/__init__.py +0 -0
  112. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/programs/run_jupyter.py +0 -0
  113. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/programs/vscode.py +0 -0
  114. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/queues.py +0 -0
  115. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/secret.py +0 -0
  116. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/selector.py +0 -0
  117. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/shell.py +0 -0
  118. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/token.py +0 -0
  119. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/utils.py +0 -0
  120. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/volume.py +0 -0
  121. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cloud_bucket_mount.py +0 -0
  122. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cloud_bucket_mount.pyi +0 -0
  123. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cls.py +0 -0
  124. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cls.pyi +0 -0
  125. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/container_process.py +0 -0
  126. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/container_process.pyi +0 -0
  127. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/dict.py +0 -0
  128. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/dict.pyi +0 -0
  129. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/environments.py +0 -0
  130. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/environments.pyi +0 -0
  131. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/exception.py +0 -0
  132. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/__init__.py +0 -0
  133. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/flash.py +0 -0
  134. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/flash.pyi +0 -0
  135. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/ipython.py +0 -0
  136. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/file_io.py +0 -0
  137. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/file_io.pyi +0 -0
  138. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/file_pattern_matcher.py +0 -0
  139. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/functions.py +0 -0
  140. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/image.py +0 -0
  141. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/image.pyi +0 -0
  142. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/io_streams.py +0 -0
  143. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/io_streams.pyi +0 -0
  144. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/mount.py +0 -0
  145. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/mount.pyi +0 -0
  146. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/network_file_system.py +0 -0
  147. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/network_file_system.pyi +0 -0
  148. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/object.py +0 -0
  149. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/object.pyi +0 -0
  150. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/output.py +0 -0
  151. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/parallel_map.py +0 -0
  152. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/parallel_map.pyi +0 -0
  153. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/partial_function.py +0 -0
  154. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/partial_function.pyi +0 -0
  155. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/proxy.py +0 -0
  156. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/proxy.pyi +0 -0
  157. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/py.typed +0 -0
  158. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/queue.py +0 -0
  159. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/queue.pyi +0 -0
  160. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/retries.py +0 -0
  161. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/runner.py +0 -0
  162. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/runner.pyi +0 -0
  163. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/running_app.py +0 -0
  164. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox_fs.py +0 -0
  165. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox_fs.pyi +0 -0
  166. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/schedule.py +0 -0
  167. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/scheduler_placement.py +0 -0
  168. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/secret.py +0 -0
  169. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/secret.pyi +0 -0
  170. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/server.py +0 -0
  171. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/server.pyi +0 -0
  172. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/serving.py +0 -0
  173. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/serving.pyi +0 -0
  174. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/snapshot.py +0 -0
  175. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/snapshot.pyi +0 -0
  176. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/stream_type.py +0 -0
  177. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/token_flow.py +0 -0
  178. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/token_flow.pyi +0 -0
  179. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/volume.py +0 -0
  180. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/volume.pyi +0 -0
  181. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/SOURCES.txt +0 -0
  182. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/dependency_links.txt +0 -0
  183. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/entry_points.txt +0 -0
  184. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/requires.txt +0 -0
  185. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/top_level.txt +0 -0
  186. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/__init__.py +0 -0
  187. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_cli_docs.py +0 -0
  188. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_cli_docs_main.py +0 -0
  189. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_reference_docs.py +0 -0
  190. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_reference_docs_main.py +0 -0
  191. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/mdmd/__init__.py +0 -0
  192. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/mdmd/mdmd.py +0 -0
  193. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/mdmd/signatures.py +0 -0
  194. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/__init__.py +0 -0
  195. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_grpc.py +0 -0
  196. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2.py +0 -0
  197. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2.pyi +0 -0
  198. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2_grpc.py +0 -0
  199. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2_grpc.pyi +0 -0
  200. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/modal_api_grpc.py +0 -0
  201. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/py.typed +0 -0
  202. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_grpc.py +0 -0
  203. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2.py +0 -0
  204. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2.pyi +0 -0
  205. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  206. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  207. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_version/__main__.py +0 -0
  208. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/pyproject.toml +0 -0
  209. {modal-1.4.1.dev1 → modal-1.4.1.dev3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.1.dev1
3
+ Version: 1.4.1.dev3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -38,7 +38,7 @@ try:
38
38
  from .proxy import Proxy
39
39
  from .queue import Queue
40
40
  from .retries import Retries
41
- from .sandbox import Sandbox
41
+ from .sandbox import Probe, Sandbox
42
42
  from .schedule import Cron, Period
43
43
  from .scheduler_placement import SchedulerPlacement
44
44
  from .secret import Secret
@@ -68,6 +68,7 @@ __all__ = [
68
68
  "Image",
69
69
  "NetworkFileSystem",
70
70
  "Period",
71
+ "Probe",
71
72
  "Proxy",
72
73
  "Queue",
73
74
  "Retries",
@@ -28,6 +28,7 @@ _EXEC_SANDBOX_UNAVAILABLE_ERROR_TYPES = (NotFoundError, ServiceError, Connection
28
28
  class ErrorPayload:
29
29
  error_kind: str
30
30
  message: str
31
+ detail: str = ""
31
32
 
32
33
 
33
34
  def _stderr_to_text(stderr: Union[str, bytes]) -> str:
@@ -51,11 +52,18 @@ def try_parse_error_payload(stderr: Union[str, bytes]) -> Optional[ErrorPayload]
51
52
  return None
52
53
  if not isinstance(message, str) or not message.strip():
53
54
  return None
54
- return ErrorPayload(error_kind=error_kind, message=message)
55
+ detail = payload.get("detail", "")
56
+ if not isinstance(detail, str):
57
+ detail = ""
58
+ return ErrorPayload(error_kind=error_kind, message=message, detail=detail)
55
59
 
56
60
 
57
61
  def raise_read_file_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
58
62
  if payload := try_parse_error_payload(stderr):
63
+ logger.debug(
64
+ f"sandbox-fs-tools read error: path={remote_path}, "
65
+ f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
66
+ )
59
67
  if payload.error_kind == "NotFound":
60
68
  raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
61
69
  if payload.error_kind == "IsDirectory":
@@ -106,6 +114,10 @@ def translate_exec_unexpected_error(operation: str, path: str, exc: Exception) -
106
114
 
107
115
  def raise_write_file_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
108
116
  if payload := try_parse_error_payload(stderr):
117
+ logger.debug(
118
+ f"sandbox-fs-tools write error: path={remote_path}, "
119
+ f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
120
+ )
109
121
  if payload.error_kind == "NotDirectory" or payload.error_kind == "AlreadyExists":
110
122
  raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
111
123
  if payload.error_kind == "IsDirectory":
@@ -7,7 +7,7 @@ import sys
7
7
  import time
8
8
  import typing
9
9
  from dataclasses import dataclass
10
- from typing import Any, Callable, Optional
10
+ from typing import Any, Callable, Optional, get_args
11
11
 
12
12
  import click
13
13
  import typer
@@ -504,7 +504,9 @@ def deploy(
504
504
  False, "-m", help="Interpret argument as a Python module path instead of a file/script path"
505
505
  ),
506
506
  timestamps: bool = typer.Option(False, "--timestamps", help="Show timestamps for each log line."),
507
- strategy: DEPLOYMENT_STRATEGY_TYPE = typer.Option("rolling", help="Deployment strategy"),
507
+ strategy: str = typer.Option(
508
+ "rolling", help="Deployment strategy", click_type=click.Choice(get_args(DEPLOYMENT_STRATEGY_TYPE))
509
+ ),
508
510
  ):
509
511
  """Deploy a Modal application.
510
512
 
@@ -22,7 +22,7 @@ from ._utils import async_utils
22
22
  from ._utils.async_utils import TaskContext, synchronize_api
23
23
  from ._utils.auth_token_manager import _AuthTokenManager
24
24
  from ._utils.grpc_utils import ConnectionManager
25
- from .config import _check_config, _is_remote, config, logger
25
+ from .config import _agent_environment, _check_config, _is_remote, config, logger
26
26
  from .exception import AuthError, ClientClosed
27
27
 
28
28
  HEARTBEAT_INTERVAL: float = config.get("heartbeat_interval")
@@ -56,6 +56,9 @@ def _get_metadata(client_type: int, credentials: Optional[tuple[str, str]], vers
56
56
  "x-modal-token-secret": token_secret,
57
57
  }
58
58
  )
59
+ agent_env = _agent_environment()
60
+ if agent_env:
61
+ metadata["x-modal-agent-harness"] = urllib.parse.quote(agent_env)
59
62
  return metadata
60
63
 
61
64
 
@@ -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.1.dev1",
38
+ version: str = "1.4.1.dev3",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -171,7 +171,7 @@ class Client:
171
171
  server_url: str,
172
172
  client_type: int,
173
173
  credentials: typing.Optional[tuple[str, str]],
174
- version: str = "1.4.1.dev1",
174
+ version: str = "1.4.1.dev3",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -121,6 +121,36 @@ def _is_remote() -> bool:
121
121
  return os.environ.get("MODAL_IS_REMOTE") == "1"
122
122
 
123
123
 
124
+ def _agent_environment() -> Optional[str]:
125
+ """Detect if the current process is running inside an AI agent harness.
126
+
127
+ Returns the agent name if detected, or None otherwise.
128
+
129
+ Checks the emerging `AGENT` standard variable first, then falls back to
130
+ tool-specific environment variables used by known agent harnesses.
131
+ """
132
+ # Emerging standard: AGENT=<name>
133
+ if agent := os.environ.get("AGENT"):
134
+ return agent
135
+
136
+ # Tool-specific environment variables
137
+ _TOOL_SPECIFIC_ENV_VARS: list[tuple[str, str]] = [
138
+ ("CLAUDECODE", "claude-code"),
139
+ ("GEMINI_CLI", "gemini-cli"),
140
+ ("CURSOR_AGENT", "cursor"),
141
+ ("CLINE_ACTIVE", "cline"),
142
+ ("AUGMENT_AGENT", "augment"),
143
+ ("OPENCODE_CLIENT", "opencode"),
144
+ ("GOOSE_TERMINAL", "goose"),
145
+ ("TRAE_AI_SHELL_ID", "trae"),
146
+ ]
147
+ for env_var, agent_name in _TOOL_SPECIFIC_ENV_VARS:
148
+ if os.environ.get(env_var):
149
+ return agent_name
150
+
151
+ return None
152
+
153
+
124
154
  def _read_user_config():
125
155
  config_data = {}
126
156
  if not _is_remote() and os.path.exists(user_config_path):
@@ -348,7 +348,7 @@ class Function(
348
348
 
349
349
  _call_generator: ___call_generator_spec
350
350
 
351
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
351
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
352
352
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
353
353
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
354
354
  ...
@@ -357,7 +357,7 @@ class Function(
357
357
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
358
358
  ...
359
359
 
360
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
360
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
361
361
 
362
362
  class __remote_gen_spec(typing_extensions.Protocol):
363
363
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -384,7 +384,7 @@ class Function(
384
384
  """
385
385
  ...
386
386
 
387
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
387
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
388
388
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
389
389
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
390
390
 
@@ -407,7 +407,7 @@ class Function(
407
407
  """
408
408
  ...
409
409
 
410
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
410
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
411
411
 
412
412
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
413
413
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -415,7 +415,7 @@ class Function(
415
415
 
416
416
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
417
417
 
418
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
418
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
419
419
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
420
420
  """Calls the function with the given arguments, without waiting for the results.
421
421
 
@@ -436,7 +436,7 @@ class Function(
436
436
  """
437
437
  ...
438
438
 
439
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
439
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
440
440
 
441
441
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
442
442
  """Return the inner Python object wrapped by this Modal Function."""
@@ -42,6 +42,7 @@ from .exception import (
42
42
  InvalidError,
43
43
  SandboxTerminatedError,
44
44
  SandboxTimeoutError,
45
+ TimeoutError,
45
46
  )
46
47
  from .file_io import FileWatchEvent, FileWatchEventType, _FileIO, ls, mkdir, rm, watch
47
48
  from .image import _Image
@@ -118,6 +119,73 @@ class SandboxConnectCredentials:
118
119
  token: str
119
120
 
120
121
 
122
+ @dataclass(frozen=True)
123
+ class Probe:
124
+ """Probe configuration for the Sandbox Readiness Probe.
125
+
126
+ Usage:
127
+ ```py notest
128
+ # Wait until a file exists.
129
+ readiness_probe = modal.Probe.with_exec(
130
+ "sh", "-c", "test -f /tmp/ready",
131
+ )
132
+
133
+ # Wait until a TCP port is accepting connections.
134
+ readiness_probe = modal.Probe.with_tcp(8080)
135
+
136
+ app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
137
+ sandbox = modal.Sandbox.create(
138
+ "python3", "-m", "http.server", "8080",
139
+ readiness_probe=readiness_probe,
140
+ app=app,
141
+ )
142
+ sandbox.wait_until_ready()
143
+ ```
144
+ """
145
+
146
+ tcp_port: Optional[int] = None
147
+ exec_argv: Optional[tuple[str, ...]] = None
148
+ interval_ms: int = 100
149
+
150
+ def __post_init__(self):
151
+ if (self.tcp_port is None) == (self.exec_argv is None):
152
+ raise InvalidError("Probe must be created with Probe.with_tcp(...) or Probe.with_exec(...)")
153
+
154
+ @classmethod
155
+ def with_tcp(cls, port: int, *, interval_ms: int = 100) -> "Probe":
156
+ if not isinstance(port, int):
157
+ raise InvalidError("Probe.with_tcp() expects an integer `port`")
158
+ if port <= 0 or port > 65535:
159
+ raise InvalidError(f"Probe.with_tcp() expects `port` in [1, 65535], got {port}")
160
+ if not isinstance(interval_ms, int):
161
+ raise InvalidError("Probe.with_tcp() expects an integer `interval_ms`")
162
+ if interval_ms <= 0:
163
+ raise InvalidError(f"Probe.with_tcp() expects `interval_ms` > 0, got {interval_ms}")
164
+ return cls(tcp_port=port, interval_ms=interval_ms)
165
+
166
+ @classmethod
167
+ def with_exec(cls, *argv: str, interval_ms: int = 100) -> "Probe":
168
+ if len(argv) == 0:
169
+ raise InvalidError("Probe.with_exec() requires at least one argument")
170
+ if not all(isinstance(arg, str) for arg in argv):
171
+ raise InvalidError("Probe.with_exec() expects all arguments to be strings")
172
+ if not isinstance(interval_ms, int):
173
+ raise InvalidError("Probe.with_exec() expects an integer `interval_ms`")
174
+ if interval_ms <= 0:
175
+ raise InvalidError(f"Probe.with_exec() expects `interval_ms` > 0, got {interval_ms}")
176
+ return cls(exec_argv=tuple(argv), interval_ms=interval_ms)
177
+
178
+ def _to_proto(self) -> api_pb2.Probe:
179
+ if self.tcp_port is not None:
180
+ return api_pb2.Probe(tcp_port=self.tcp_port, interval_ms=self.interval_ms)
181
+ if self.exec_argv is not None:
182
+ return api_pb2.Probe(
183
+ exec_command=api_pb2.Probe.ExecCommand(argv=list(self.exec_argv)),
184
+ interval_ms=self.interval_ms,
185
+ )
186
+ raise InvalidError("Probe must be created with Probe.with_tcp(...) or Probe.with_exec(...)")
187
+
188
+
121
189
  class _Sandbox(_Object, type_prefix="sb"):
122
190
  """A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
123
191
  [asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
@@ -166,6 +234,7 @@ class _Sandbox(_Object, type_prefix="sb"):
166
234
  h2_ports: Sequence[int] = [],
167
235
  unencrypted_ports: Sequence[int] = [],
168
236
  proxy: Optional[_Proxy] = None,
237
+ readiness_probe: Optional[Probe] = None,
169
238
  experimental_options: Optional[dict[str, bool]] = None,
170
239
  enable_snapshot: bool = False,
171
240
  verbose: bool = False,
@@ -279,6 +348,7 @@ class _Sandbox(_Object, type_prefix="sb"):
279
348
  open_ports=api_pb2.PortSpecs(ports=open_ports),
280
349
  network_access=network_access,
281
350
  proxy_id=(proxy.object_id if proxy else None),
351
+ readiness_probe=(readiness_probe._to_proto() if readiness_probe else None),
282
352
  enable_snapshot=enable_snapshot,
283
353
  verbose=verbose,
284
354
  name=name,
@@ -339,6 +409,8 @@ class _Sandbox(_Object, type_prefix="sb"):
339
409
  proxy: Optional[_Proxy] = None,
340
410
  # If True, the sandbox will receive a MODAL_IDENTITY_TOKEN env var for OIDC-based auth.
341
411
  include_oidc_identity_token: bool = False,
412
+ # Probe used to determine when the sandbox has become ready.
413
+ readiness_probe: Optional[Probe] = None,
342
414
  # Enable verbose logging for sandbox operations.
343
415
  verbose: bool = False,
344
416
  experimental_options: Optional[dict[str, bool]] = None,
@@ -403,6 +475,7 @@ class _Sandbox(_Object, type_prefix="sb"):
403
475
  h2_ports=h2_ports,
404
476
  unencrypted_ports=unencrypted_ports,
405
477
  proxy=proxy,
478
+ readiness_probe=readiness_probe,
406
479
  experimental_options=experimental_options,
407
480
  _experimental_enable_snapshot=_experimental_enable_snapshot,
408
481
  include_oidc_identity_token=include_oidc_identity_token,
@@ -439,6 +512,7 @@ class _Sandbox(_Object, type_prefix="sb"):
439
512
  unencrypted_ports: Sequence[int] = [],
440
513
  proxy: Optional[_Proxy] = None,
441
514
  include_oidc_identity_token: bool = False,
515
+ readiness_probe: Optional[Probe] = None,
442
516
  experimental_options: Optional[dict[str, bool]] = None,
443
517
  _experimental_enable_snapshot: bool = False,
444
518
  client: Optional[_Client] = None,
@@ -490,6 +564,7 @@ class _Sandbox(_Object, type_prefix="sb"):
490
564
  h2_ports=h2_ports,
491
565
  unencrypted_ports=unencrypted_ports,
492
566
  proxy=proxy,
567
+ readiness_probe=readiness_probe,
493
568
  experimental_options=experimental_options,
494
569
  enable_snapshot=_experimental_enable_snapshot,
495
570
  verbose=verbose,
@@ -958,6 +1033,39 @@ class _Sandbox(_Object, type_prefix="sb"):
958
1033
  raise SandboxTerminatedError()
959
1034
  break
960
1035
 
1036
+ async def wait_until_ready(self, *, timeout: int = 300) -> None:
1037
+ """Wait for the Sandbox readiness probe to report that the Sandbox is ready.
1038
+
1039
+ The Sandbox must be configured with a `readiness_probe` in order to use this method.
1040
+
1041
+ Usage:
1042
+ ```py notest
1043
+ app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
1044
+ sandbox = modal.Sandbox.create(
1045
+ "python3", "-m", "http.server", "8080",
1046
+ readiness_probe=modal.Probe.with_tcp(8080),
1047
+ app=app,
1048
+ )
1049
+ sandbox.wait_until_ready()
1050
+ ```
1051
+ """
1052
+ if timeout <= 0:
1053
+ raise InvalidError(f"`timeout` must be positive, got: {timeout}")
1054
+
1055
+ deadline = time.monotonic() + timeout
1056
+ remaining_timeout = deadline - time.monotonic()
1057
+ while remaining_timeout > 0:
1058
+ req = api_pb2.SandboxWaitUntilReadyRequest(
1059
+ sandbox_id=self.object_id,
1060
+ timeout=min(remaining_timeout, 50.0),
1061
+ )
1062
+ resp = await self._client.stub.SandboxWaitUntilReady(req)
1063
+ if resp.ready_at > 0:
1064
+ return
1065
+
1066
+ remaining_timeout = deadline - time.monotonic()
1067
+ raise TimeoutError()
1068
+
961
1069
  async def tunnels(self, timeout: int = 50) -> dict[int, Tunnel]:
962
1070
  """Get Tunnel metadata for the sandbox.
963
1071
 
@@ -71,6 +71,68 @@ class SandboxConnectCredentials:
71
71
  """Return hash(self)."""
72
72
  ...
73
73
 
74
+ class Probe:
75
+ """Probe configuration for the Sandbox Readiness Probe.
76
+
77
+ Usage:
78
+ ```py notest
79
+ # Wait until a file exists.
80
+ readiness_probe = modal.Probe.with_exec(
81
+ "sh", "-c", "test -f /tmp/ready",
82
+ )
83
+
84
+ # Wait until a TCP port is accepting connections.
85
+ readiness_probe = modal.Probe.with_tcp(8080)
86
+
87
+ app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
88
+ sandbox = modal.Sandbox.create(
89
+ "python3", "-m", "http.server", "8080",
90
+ readiness_probe=readiness_probe,
91
+ app=app,
92
+ )
93
+ sandbox.wait_until_ready()
94
+ ```
95
+ """
96
+
97
+ tcp_port: typing.Optional[int]
98
+ exec_argv: typing.Optional[tuple[str, ...]]
99
+ interval_ms: int
100
+
101
+ def __post_init__(self): ...
102
+ @classmethod
103
+ def with_tcp(cls, port: int, *, interval_ms: int = 100) -> Probe: ...
104
+ @classmethod
105
+ def with_exec(cls, *argv: str, interval_ms: int = 100) -> Probe: ...
106
+ def _to_proto(self) -> modal_proto.api_pb2.Probe: ...
107
+ def __init__(
108
+ self,
109
+ tcp_port: typing.Optional[int] = None,
110
+ exec_argv: typing.Optional[tuple[str, ...]] = None,
111
+ interval_ms: int = 100,
112
+ ) -> None:
113
+ """Initialize self. See help(type(self)) for accurate signature."""
114
+ ...
115
+
116
+ def __repr__(self):
117
+ """Return repr(self)."""
118
+ ...
119
+
120
+ def __eq__(self, other):
121
+ """Return self==value."""
122
+ ...
123
+
124
+ def __setattr__(self, name, value):
125
+ """Implement setattr(self, name, value)."""
126
+ ...
127
+
128
+ def __delattr__(self, name):
129
+ """Implement delattr(self, name)."""
130
+ ...
131
+
132
+ def __hash__(self):
133
+ """Return hash(self)."""
134
+ ...
135
+
74
136
  class _Sandbox(modal._object._Object):
75
137
  """A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
76
138
  [asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
@@ -120,6 +182,7 @@ class _Sandbox(modal._object._Object):
120
182
  h2_ports: collections.abc.Sequence[int] = [],
121
183
  unencrypted_ports: collections.abc.Sequence[int] = [],
122
184
  proxy: typing.Optional[modal.proxy._Proxy] = None,
185
+ readiness_probe: typing.Optional[Probe] = None,
123
186
  experimental_options: typing.Optional[dict[str, bool]] = None,
124
187
  enable_snapshot: bool = False,
125
188
  verbose: bool = False,
@@ -159,6 +222,7 @@ class _Sandbox(modal._object._Object):
159
222
  custom_domain: typing.Optional[str] = None,
160
223
  proxy: typing.Optional[modal.proxy._Proxy] = None,
161
224
  include_oidc_identity_token: bool = False,
225
+ readiness_probe: typing.Optional[Probe] = None,
162
226
  verbose: bool = False,
163
227
  experimental_options: typing.Optional[dict[str, bool]] = None,
164
228
  _experimental_enable_snapshot: bool = False,
@@ -211,6 +275,7 @@ class _Sandbox(modal._object._Object):
211
275
  unencrypted_ports: collections.abc.Sequence[int] = [],
212
276
  proxy: typing.Optional[modal.proxy._Proxy] = None,
213
277
  include_oidc_identity_token: bool = False,
278
+ readiness_probe: typing.Optional[Probe] = None,
214
279
  experimental_options: typing.Optional[dict[str, bool]] = None,
215
280
  _experimental_enable_snapshot: bool = False,
216
281
  client: typing.Optional[modal.client._Client] = None,
@@ -360,6 +425,24 @@ class _Sandbox(modal._object._Object):
360
425
  """Wait for the Sandbox to finish running."""
361
426
  ...
362
427
 
428
+ async def wait_until_ready(self, *, timeout: int = 300) -> None:
429
+ """Wait for the Sandbox readiness probe to report that the Sandbox is ready.
430
+
431
+ The Sandbox must be configured with a `readiness_probe` in order to use this method.
432
+
433
+ Usage:
434
+ ```py notest
435
+ app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
436
+ sandbox = modal.Sandbox.create(
437
+ "python3", "-m", "http.server", "8080",
438
+ readiness_probe=modal.Probe.with_tcp(8080),
439
+ app=app,
440
+ )
441
+ sandbox.wait_until_ready()
442
+ ```
443
+ """
444
+ ...
445
+
363
446
  async def tunnels(self, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]:
364
447
  """Get Tunnel metadata for the sandbox.
365
448
 
@@ -843,6 +926,7 @@ class Sandbox(modal.object.Object):
843
926
  h2_ports: collections.abc.Sequence[int] = [],
844
927
  unencrypted_ports: collections.abc.Sequence[int] = [],
845
928
  proxy: typing.Optional[modal.proxy.Proxy] = None,
929
+ readiness_probe: typing.Optional[Probe] = None,
846
930
  experimental_options: typing.Optional[dict[str, bool]] = None,
847
931
  enable_snapshot: bool = False,
848
932
  verbose: bool = False,
@@ -886,6 +970,7 @@ class Sandbox(modal.object.Object):
886
970
  custom_domain: typing.Optional[str] = None,
887
971
  proxy: typing.Optional[modal.proxy.Proxy] = None,
888
972
  include_oidc_identity_token: bool = False,
973
+ readiness_probe: typing.Optional[Probe] = None,
889
974
  verbose: bool = False,
890
975
  experimental_options: typing.Optional[dict[str, bool]] = None,
891
976
  _experimental_enable_snapshot: bool = False,
@@ -941,6 +1026,7 @@ class Sandbox(modal.object.Object):
941
1026
  custom_domain: typing.Optional[str] = None,
942
1027
  proxy: typing.Optional[modal.proxy.Proxy] = None,
943
1028
  include_oidc_identity_token: bool = False,
1029
+ readiness_probe: typing.Optional[Probe] = None,
944
1030
  verbose: bool = False,
945
1031
  experimental_options: typing.Optional[dict[str, bool]] = None,
946
1032
  _experimental_enable_snapshot: bool = False,
@@ -999,6 +1085,7 @@ class Sandbox(modal.object.Object):
999
1085
  unencrypted_ports: collections.abc.Sequence[int] = [],
1000
1086
  proxy: typing.Optional[modal.proxy.Proxy] = None,
1001
1087
  include_oidc_identity_token: bool = False,
1088
+ readiness_probe: typing.Optional[Probe] = None,
1002
1089
  experimental_options: typing.Optional[dict[str, bool]] = None,
1003
1090
  _experimental_enable_snapshot: bool = False,
1004
1091
  client: typing.Optional[modal.client.Client] = None,
@@ -1047,6 +1134,7 @@ class Sandbox(modal.object.Object):
1047
1134
  unencrypted_ports: collections.abc.Sequence[int] = [],
1048
1135
  proxy: typing.Optional[modal.proxy.Proxy] = None,
1049
1136
  include_oidc_identity_token: bool = False,
1137
+ readiness_probe: typing.Optional[Probe] = None,
1050
1138
  experimental_options: typing.Optional[dict[str, bool]] = None,
1051
1139
  _experimental_enable_snapshot: bool = False,
1052
1140
  client: typing.Optional[modal.client.Client] = None,
@@ -1357,6 +1445,45 @@ class Sandbox(modal.object.Object):
1357
1445
 
1358
1446
  wait: __wait_spec
1359
1447
 
1448
+ class __wait_until_ready_spec(typing_extensions.Protocol):
1449
+ def __call__(self, /, *, timeout: int = 300) -> None:
1450
+ """Wait for the Sandbox readiness probe to report that the Sandbox is ready.
1451
+
1452
+ The Sandbox must be configured with a `readiness_probe` in order to use this method.
1453
+
1454
+ Usage:
1455
+ ```py notest
1456
+ app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
1457
+ sandbox = modal.Sandbox.create(
1458
+ "python3", "-m", "http.server", "8080",
1459
+ readiness_probe=modal.Probe.with_tcp(8080),
1460
+ app=app,
1461
+ )
1462
+ sandbox.wait_until_ready()
1463
+ ```
1464
+ """
1465
+ ...
1466
+
1467
+ async def aio(self, /, *, timeout: int = 300) -> None:
1468
+ """Wait for the Sandbox readiness probe to report that the Sandbox is ready.
1469
+
1470
+ The Sandbox must be configured with a `readiness_probe` in order to use this method.
1471
+
1472
+ Usage:
1473
+ ```py notest
1474
+ app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
1475
+ sandbox = modal.Sandbox.create(
1476
+ "python3", "-m", "http.server", "8080",
1477
+ readiness_probe=modal.Probe.with_tcp(8080),
1478
+ app=app,
1479
+ )
1480
+ sandbox.wait_until_ready()
1481
+ ```
1482
+ """
1483
+ ...
1484
+
1485
+ wait_until_ready: __wait_until_ready_spec
1486
+
1360
1487
  class __tunnels_spec(typing_extensions.Protocol):
1361
1488
  def __call__(self, /, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]:
1362
1489
  """Get Tunnel metadata for the sandbox.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.1.dev1
3
+ Version: 1.4.1.dev3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.4.1.dev1"
4
+ __version__ = "1.4.1.dev3"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes