modal 1.5.3.dev5__tar.gz → 1.5.3.dev6__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 (231) hide show
  1. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/PKG-INFO +1 -1
  2. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_functions.py +11 -15
  3. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/task_command_router_client.py +127 -3
  4. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/client.pyi +2 -2
  5. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/container_process.py +13 -1
  6. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/container_process.pyi +27 -0
  7. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/functions.pyi +20 -20
  8. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox.py +29 -10
  9. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox.pyi +6 -2
  10. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox_fs.py +32 -80
  11. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox_fs.pyi +24 -0
  12. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/PKG-INFO +1 -1
  13. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_reference_docs.py +0 -1
  14. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/mdmd.py +9 -2
  15. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/types.py +1 -0
  16. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_version/__init__.py +1 -1
  17. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/LICENSE +0 -0
  18. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/README.md +0 -0
  19. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/__init__.py +0 -0
  20. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/__main__.py +0 -0
  21. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_billing.py +0 -0
  22. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_clustered_functions.py +0 -0
  23. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_clustered_functions.pyi +0 -0
  24. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_container_entrypoint.py +0 -0
  25. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_environments.py +0 -0
  26. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_function_variants.py +0 -0
  27. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_grpc_client.py +0 -0
  28. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_image.py +0 -0
  29. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_ipython.py +0 -0
  30. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_load_context.py +0 -0
  31. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_location.py +0 -0
  32. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_logs.py +0 -0
  33. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_object.py +0 -0
  34. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/__init__.py +0 -0
  35. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/manager.py +0 -0
  36. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/pty.py +0 -0
  37. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/rich.py +0 -0
  38. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/status.py +0 -0
  39. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_partial_function.py +0 -0
  40. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_resolver.py +0 -0
  41. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_resources.py +0 -0
  42. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/__init__.py +0 -0
  43. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/asgi.py +0 -0
  44. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/container_io_manager.py +0 -0
  45. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/container_io_manager.pyi +0 -0
  46. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/execution_context.py +0 -0
  47. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/execution_context.pyi +0 -0
  48. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  49. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/task_lifecycle_manager.py +0 -0
  50. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  51. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/telemetry.py +0 -0
  52. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/user_code_event_loop.py +0 -0
  53. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/user_code_imports.py +0 -0
  54. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_serialization.py +0 -0
  55. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_server.py +0 -0
  56. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_traceback.py +0 -0
  57. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_tunnel.py +0 -0
  58. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_tunnel.pyi +0 -0
  59. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_type_manager.py +0 -0
  60. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/__init__.py +0 -0
  61. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/app_utils.py +0 -0
  62. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/async_utils.py +0 -0
  63. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/auth_token_manager.py +0 -0
  64. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/blob_utils.py +0 -0
  65. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/browser_utils.py +0 -0
  66. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/bytes_io_segment_payload.py +0 -0
  67. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/curl_utils.py +0 -0
  68. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/deprecation.py +0 -0
  69. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/docker_utils.py +0 -0
  70. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/function_utils.py +0 -0
  71. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/git_utils.py +0 -0
  72. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/grpc_testing.py +0 -0
  73. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/grpc_utils.py +0 -0
  74. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/hash_utils.py +0 -0
  75. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/http_utils.py +0 -0
  76. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/jwt_utils.py +0 -0
  77. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/logger.py +0 -0
  78. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/mount_utils.py +0 -0
  79. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/name_utils.py +0 -0
  80. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/package_utils.py +0 -0
  81. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/pattern_utils.py +0 -0
  82. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/proxy_support.py +0 -0
  83. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/rand_pb_testing.py +0 -0
  84. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/sandbox_fs_utils.py +0 -0
  85. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/shell_utils.py +0 -0
  86. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/time_utils.py +0 -0
  87. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/__init__.py +0 -0
  88. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  89. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/cloudpickle.py +0 -0
  90. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/tblib.py +0 -0
  91. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/version.py +0 -0
  92. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_watcher.py +0 -0
  93. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_workspace.py +0 -0
  94. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/app.py +0 -0
  95. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/app.pyi +0 -0
  96. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/billing.py +0 -0
  97. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/billing.pyi +0 -0
  98. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2023.12.312.txt +0 -0
  99. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2023.12.txt +0 -0
  100. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2024.04.txt +0 -0
  101. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2024.10.txt +0 -0
  102. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2025.06.txt +0 -0
  103. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/PREVIEW.txt +0 -0
  104. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/README.md +0 -0
  105. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/base-images.json +0 -0
  106. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/call_graph.py +0 -0
  107. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/__init__.py +0 -0
  108. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/_download.py +0 -0
  109. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/_help.py +0 -0
  110. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/_traceback.py +0 -0
  111. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/app.py +0 -0
  112. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/billing.py +0 -0
  113. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/bootstrap.py +0 -0
  114. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/changelog.py +0 -0
  115. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/cluster.py +0 -0
  116. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/config.py +0 -0
  117. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/container.py +0 -0
  118. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/curl.py +0 -0
  119. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/dashboard.py +0 -0
  120. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/dict.py +0 -0
  121. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/endpoint.py +0 -0
  122. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/entry_point.py +0 -0
  123. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/environment.py +0 -0
  124. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/image.py +0 -0
  125. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/import_refs.py +0 -0
  126. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/launch.py +0 -0
  127. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/logo.py +0 -0
  128. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/network_file_system.py +0 -0
  129. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/profile.py +0 -0
  130. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/programs/__init__.py +0 -0
  131. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/programs/run_jupyter.py +0 -0
  132. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/programs/vscode.py +0 -0
  133. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/queues.py +0 -0
  134. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/run.py +0 -0
  135. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/secret.py +0 -0
  136. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/selector.py +0 -0
  137. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/shell.py +0 -0
  138. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/skills.py +0 -0
  139. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/token.py +0 -0
  140. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/utils.py +0 -0
  141. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/volume.py +0 -0
  142. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/workspace.py +0 -0
  143. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/client.py +0 -0
  144. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cloud_bucket_mount.py +0 -0
  145. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cloud_bucket_mount.pyi +0 -0
  146. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cls.py +0 -0
  147. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cls.pyi +0 -0
  148. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/config.py +0 -0
  149. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/dict.py +0 -0
  150. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/dict.pyi +0 -0
  151. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/environments.py +0 -0
  152. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/environments.pyi +0 -0
  153. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/exception.py +0 -0
  154. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/__init__.py +0 -0
  155. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/flash.py +0 -0
  156. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/flash.pyi +0 -0
  157. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/ipython.py +0 -0
  158. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/file_io.py +0 -0
  159. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/file_io.pyi +0 -0
  160. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/file_pattern_matcher.py +0 -0
  161. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/functions.py +0 -0
  162. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/image.py +0 -0
  163. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/image.pyi +0 -0
  164. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/io_streams.py +0 -0
  165. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/io_streams.pyi +0 -0
  166. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/mount.py +0 -0
  167. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/mount.pyi +0 -0
  168. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/network_file_system.py +0 -0
  169. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/network_file_system.pyi +0 -0
  170. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/object.py +0 -0
  171. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/object.pyi +0 -0
  172. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/output.py +0 -0
  173. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/parallel_map.py +0 -0
  174. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/parallel_map.pyi +0 -0
  175. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/partial_function.py +0 -0
  176. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/partial_function.pyi +0 -0
  177. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/proxy.py +0 -0
  178. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/proxy.pyi +0 -0
  179. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/py.typed +0 -0
  180. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/queue.py +0 -0
  181. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/queue.pyi +0 -0
  182. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/retries.py +0 -0
  183. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/runner.py +0 -0
  184. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/runner.pyi +0 -0
  185. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/running_app.py +0 -0
  186. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/schedule.py +0 -0
  187. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/scheduler_placement.py +0 -0
  188. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/secret.py +0 -0
  189. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/secret.pyi +0 -0
  190. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/server.py +0 -0
  191. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/server.pyi +0 -0
  192. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/serving.py +0 -0
  193. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/serving.pyi +0 -0
  194. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/skills/modal/SKILL.md +0 -0
  195. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/snapshot.py +0 -0
  196. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/snapshot.pyi +0 -0
  197. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/stream_type.py +0 -0
  198. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/token_flow.py +0 -0
  199. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/token_flow.pyi +0 -0
  200. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/types.py +0 -0
  201. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/volume.py +0 -0
  202. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/volume.pyi +0 -0
  203. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/workspace.py +0 -0
  204. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/workspace.pyi +0 -0
  205. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/SOURCES.txt +0 -0
  206. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/dependency_links.txt +0 -0
  207. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/entry_points.txt +0 -0
  208. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/requires.txt +0 -0
  209. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/top_level.txt +0 -0
  210. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/__init__.py +0 -0
  211. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_cli_docs.py +0 -0
  212. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_cli_docs_main.py +0 -0
  213. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_reference_docs_main.py +0 -0
  214. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/__init__.py +0 -0
  215. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/signatures.py +0 -0
  216. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/__init__.py +0 -0
  217. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_grpc.py +0 -0
  218. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2.py +0 -0
  219. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2.pyi +0 -0
  220. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2_grpc.py +0 -0
  221. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2_grpc.pyi +0 -0
  222. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/modal_api_grpc.py +0 -0
  223. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/py.typed +0 -0
  224. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_grpc.py +0 -0
  225. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2.py +0 -0
  226. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2.pyi +0 -0
  227. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  228. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  229. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_version/__main__.py +0 -0
  230. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/pyproject.toml +0 -0
  231. {modal-1.5.3.dev5 → modal-1.5.3.dev6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.3.dev5
3
+ Version: 1.5.3.dev6
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -2073,11 +2073,13 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2073
2073
 
2074
2074
  @live_method
2075
2075
  async def get_call_graph(self) -> list[InputInfo]:
2076
- """Returns a structure representing the call graph from a given root
2077
- call ID, along with the status of execution for each node.
2076
+ """Fetch information about the graph of Inputs this FunctionCall is part of.
2078
2077
 
2079
- See [`modal.call_graph`](https://modal.com/docs/sdk/py/latest/modal.call_graph) reference page
2080
- for documentation on the structure of the returned `InputInfo` items.
2078
+ Note: the call graph data is not populated in real-time, and its capture is best-effort.
2079
+ We do not recommend relying on this method for critical use cases.
2080
+
2081
+ See the [`modal.types`](/docs/sdk/py/latest/modal.types) reference for information
2082
+ on the return values.
2081
2083
 
2082
2084
  Returns:
2083
2085
  A list of `InputInfo` nodes describing the call graph.
@@ -2088,19 +2090,13 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2088
2090
  return _reconstruct_call_graph(response)
2089
2091
 
2090
2092
  @live_method
2091
- async def cancel(
2092
- self,
2093
- # if true, containers running the inputs are forcibly terminated
2094
- terminate_containers: bool = False,
2095
- ):
2096
- """Cancels the function call, which will stop its execution and mark its inputs as
2097
- [`TERMINATED`](https://modal.com/docs/sdk/py/latest/modal.call_graph#modalcall_graphinputstatus).
2098
-
2099
- If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
2100
- causing any non-cancelled inputs on those containers to be rescheduled in new containers.
2093
+ async def cancel(self, terminate_containers: bool = False):
2094
+ """Cancel the FunctionCall and terminate its inputs without retrying.
2101
2095
 
2102
2096
  Args:
2103
- terminate_containers: If True, forcibly terminate workers running cancelled inputs.
2097
+ terminate_containers: If True, terminate the containers running the cancelled
2098
+ inputs. Any other inputs running concurrently on those containers will be
2099
+ rescheduled.
2104
2100
  """
2105
2101
  request = api_pb2.FunctionCallCancelRequest(
2106
2102
  function_call_id=self.object_id, terminate_containers=terminate_containers
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2025
2
2
  import asyncio
3
3
  import base64
4
+ import io
4
5
  import json
5
6
  import socket
6
7
  import ssl
@@ -8,16 +9,16 @@ import time
8
9
  import typing
9
10
  import urllib.parse
10
11
  import weakref
11
- from collections.abc import AsyncGenerator, Callable
12
+ from collections.abc import AsyncGenerator, AsyncIterable, Callable
12
13
  from contextlib import suppress
13
- from typing import TypeVar
14
+ from typing import BinaryIO, TypeVar
14
15
 
15
16
  import grpclib.client
16
17
  from grpclib import GRPCError, Status
17
18
  from grpclib.exceptions import StreamTerminatedError
18
19
 
19
20
  from modal.config import logger
20
- from modal.exception import ExecTimeoutError, TimeoutError as ModalTimeoutError
21
+ from modal.exception import AuthError, ExecTimeoutError, InternalError, ServiceError, TimeoutError as ModalTimeoutError
21
22
  from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
22
23
  from modal_proto.task_command_router_grpc import TaskCommandRouterStub
23
24
 
@@ -26,6 +27,17 @@ from .._utils.grpc_utils import ModalChannel, create_channel_config
26
27
  from .async_utils import aclosing, retry
27
28
  from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
28
29
 
30
+ STREAMING_STDIN_CHUNK_SIZE = 256 * 1024
31
+
32
+ _STREAMING_STDIN_RESUMABLE_EXCEPTIONS = (
33
+ AuthError,
34
+ InternalError,
35
+ ServiceError,
36
+ StreamTerminatedError,
37
+ OSError,
38
+ asyncio.TimeoutError,
39
+ )
40
+
29
41
 
30
42
  @retry(n_attempts=34, base_delay=1, max_delay=10, attempt_timeout=10, total_timeout=310)
31
43
  async def _connect_channel(channel: grpclib.client.Channel):
@@ -468,6 +480,118 @@ class TaskCommandRouterClient:
468
480
  lambda: self._call_with_auth_retry(self._stub.TaskExecStdinWrite, request)
469
481
  )
470
482
 
483
+ async def exec_stdin_write_stream(
484
+ self,
485
+ task_id: str,
486
+ exec_id: str,
487
+ source: BinaryIO,
488
+ chunk_size: int = STREAMING_STDIN_CHUNK_SIZE,
489
+ max_resume_attempts: int = 9,
490
+ ) -> int:
491
+ """Stream `source` into the exec's stdin, with bounded resume on transient failures.
492
+
493
+ Streams the full contents of `source` in one client-streaming RPC and
494
+ closes stdin (EOF) on success. On a resumable error, queries
495
+ `exec_stdin_status` for the server's canonical offset, seeks `source`
496
+ to that point, and reopens the stream.
497
+
498
+ Args:
499
+ task_id: The task ID of the task running the exec'd command.
500
+ exec_id: The execution ID of the command to write to.
501
+ source: A seekable byte source (file, BytesIO, etc.).
502
+ chunk_size: Bytes per outbound message.
503
+ max_resume_attempts: Upper bound on resume retries before giving up.
504
+
505
+ Returns:
506
+ The total number of bytes streamed into the exec's stdin.
507
+ """
508
+ offset = 0
509
+ attempt = 0
510
+ while True:
511
+
512
+ async def _chunks():
513
+ while True:
514
+ chunk = source.read(chunk_size)
515
+ if not chunk:
516
+ return
517
+ yield chunk
518
+
519
+ try:
520
+ source.seek(offset)
521
+ await self._exec_stdin_write_stream(task_id, exec_id, offset, _chunks())
522
+ return source.tell()
523
+ except (*_STREAMING_STDIN_RESUMABLE_EXCEPTIONS, AttributeError) as e:
524
+ # StreamTerminatedError surfaces as AttributeError in grpclib<=0.4.7;
525
+ # see call_with_retries_on_transient_errors.
526
+ if isinstance(e, AttributeError) and "_write_appdata" not in str(e):
527
+ raise
528
+ attempt += 1
529
+ if attempt > max_resume_attempts:
530
+ raise
531
+ status = await self.exec_stdin_status(task_id=task_id, exec_id=exec_id)
532
+ if status.closed:
533
+ pos = source.tell()
534
+ if status.num_bytes_written == pos and source.seek(0, io.SEEK_END) == pos:
535
+ logger.debug(f"exec_stdin_write_stream completed but response was lost: {e}")
536
+ return pos
537
+ raise
538
+ offset = status.num_bytes_written
539
+ logger.debug(f"exec_stdin_write_stream resuming from offset {offset} after error: {e}")
540
+
541
+ async def _exec_stdin_write_stream(
542
+ self,
543
+ task_id: str,
544
+ exec_id: str,
545
+ start_offset: int,
546
+ chunks: AsyncIterable[bytes],
547
+ ) -> sr_pb2.TaskExecStdinWriteStreamResponse | None:
548
+ """Single client-streaming attempt: Start, Data chunks, then End (EOF).
549
+
550
+ Does not retry; `exec_stdin_write_stream` owns resume.
551
+ """
552
+ with grpc_error_converter():
553
+ stream = self._stub.TaskExecStdinWriteStream.open(metadata=self._get_metadata())
554
+ async with stream as s:
555
+ start = sr_pb2.TaskExecStdinWriteStreamRequest(
556
+ start=sr_pb2.TaskExecStdinWriteStreamStart(
557
+ task_id=task_id,
558
+ exec_id=exec_id,
559
+ offset=start_offset,
560
+ ),
561
+ )
562
+ await s.send_message(start)
563
+ async for data in chunks:
564
+ if not data:
565
+ continue
566
+ await s.send_message(sr_pb2.TaskExecStdinWriteStreamRequest(data=data))
567
+ # The server closes stdin only on this explicit End message. A
568
+ # stream that breaks before it leaves stdin open for resume.
569
+ await s.send_message(
570
+ sr_pb2.TaskExecStdinWriteStreamRequest(end=sr_pb2.TaskExecStdinWriteStreamEnd()),
571
+ end=True,
572
+ )
573
+ return await s.recv_message()
574
+
575
+ async def exec_stdin_status(self, task_id: str, exec_id: str) -> sr_pb2.TaskExecStdinStatusResponse:
576
+ """Read the current stdin write status for an exec'd command, to support retries from the right offset.
577
+
578
+ Used by streaming clients to find the resume offset after a stream
579
+ failure. Evicts any in-flight stdin stream for the exec, so it is not
580
+ safe to call for read-only observability.
581
+
582
+ Args:
583
+ task_id: The task ID of the task running the exec'd command.
584
+ exec_id: The execution ID of the command to query.
585
+ Raises:
586
+ Other errors: If retries are exhausted on transient errors or if there's an error
587
+ from the RPC itself.
588
+ """
589
+ request = sr_pb2.TaskExecStdinStatusRequest(task_id=task_id, exec_id=exec_id)
590
+ with grpc_error_converter():
591
+ return await call_with_retries_on_transient_errors(
592
+ lambda: self._call_with_auth_retry(self._stub.TaskExecStdinStatus, request)
593
+ )
594
+
471
595
  async def sandbox_stdin_write_v2(
472
596
  self, task_id: str, offset: int, data: bytes, eof: bool
473
597
  ) -> sr_pb2.SandboxStdinWriteV2Response:
@@ -37,7 +37,7 @@ class _Client:
37
37
  server_url: str,
38
38
  client_type: int,
39
39
  credentials: typing.Optional[tuple[str, str]],
40
- version: str = "1.5.3.dev5",
40
+ version: str = "1.5.3.dev6",
41
41
  ):
42
42
  """mdmd:hidden
43
43
  The Modal client object is not intended to be instantiated directly by users.
@@ -207,7 +207,7 @@ class Client:
207
207
  server_url: str,
208
208
  client_type: int,
209
209
  credentials: typing.Optional[tuple[str, str]],
210
- version: str = "1.5.3.dev5",
210
+ version: str = "1.5.3.dev6",
211
211
  ):
212
212
  """mdmd:hidden
213
213
  The Modal client object is not intended to be instantiated directly by users.
@@ -1,7 +1,7 @@
1
1
  # Copyright Modal Labs 2024
2
2
  import asyncio
3
3
  import platform
4
- from typing import Generic, TypeVar
4
+ from typing import BinaryIO, Generic, TypeVar
5
5
 
6
6
  from modal_proto import api_pb2
7
7
 
@@ -117,6 +117,18 @@ class _ContainerProcess(Generic[T]):
117
117
  )
118
118
  return self._returncode
119
119
 
120
+ async def _stdin_write_stream(self, source: BinaryIO) -> int:
121
+ """mdmd:hidden
122
+ Stream `source` into the process's stdin and close it (EOF) on success.
123
+
124
+ Returns the total bytes streamed.
125
+ """
126
+ return await self._command_router_client.exec_stdin_write_stream(
127
+ task_id=self._task_id,
128
+ exec_id=self._process_id,
129
+ source=source,
130
+ )
131
+
120
132
  async def poll(self) -> int | None:
121
133
  """Check if the container process has finished running.
122
134
 
@@ -53,6 +53,14 @@ class _ContainerProcess(typing.Generic[T]):
53
53
 
54
54
  @property
55
55
  def returncode(self) -> int: ...
56
+ async def _stdin_write_stream(self, source: typing.BinaryIO) -> int:
57
+ """mdmd:hidden
58
+ Stream `source` into the process's stdin and close it (EOF) on success.
59
+
60
+ Returns the total bytes streamed.
61
+ """
62
+ ...
63
+
56
64
  async def poll(self) -> typing.Optional[int]:
57
65
  """Check if the container process has finished running.
58
66
 
@@ -105,6 +113,25 @@ class ContainerProcess(typing.Generic[T]):
105
113
  @property
106
114
  def returncode(self) -> int: ...
107
115
 
116
+ class ___stdin_write_stream_spec(typing_extensions.Protocol):
117
+ def __call__(self, /, source: typing.BinaryIO) -> int:
118
+ """mdmd:hidden
119
+ Stream `source` into the process's stdin and close it (EOF) on success.
120
+
121
+ Returns the total bytes streamed.
122
+ """
123
+ ...
124
+
125
+ async def aio(self, /, source: typing.BinaryIO) -> int:
126
+ """mdmd:hidden
127
+ Stream `source` into the process's stdin and close it (EOF) on success.
128
+
129
+ Returns the total bytes streamed.
130
+ """
131
+ ...
132
+
133
+ _stdin_write_stream: ___stdin_write_stream_spec
134
+
108
135
  class __poll_spec(typing_extensions.Protocol):
109
136
  def __call__(self, /) -> typing.Optional[int]:
110
137
  """Check if the container process has finished running.
@@ -1044,11 +1044,13 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
1044
1044
 
1045
1045
  class __get_call_graph_spec(typing_extensions.Protocol):
1046
1046
  def __call__(self, /) -> list[modal.types.InputInfo]:
1047
- """Returns a structure representing the call graph from a given root
1048
- call ID, along with the status of execution for each node.
1047
+ """Fetch information about the graph of Inputs this FunctionCall is part of.
1049
1048
 
1050
- See [`modal.call_graph`](https://modal.com/docs/sdk/py/latest/modal.call_graph) reference page
1051
- for documentation on the structure of the returned `InputInfo` items.
1049
+ Note: the call graph data is not populated in real-time, and its capture is best-effort.
1050
+ We do not recommend relying on this method for critical use cases.
1051
+
1052
+ See the [`modal.types`](/docs/sdk/py/latest/modal.types) reference for information
1053
+ on the return values.
1052
1054
 
1053
1055
  Returns:
1054
1056
  A list of `InputInfo` nodes describing the call graph.
@@ -1056,11 +1058,13 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
1056
1058
  ...
1057
1059
 
1058
1060
  async def aio(self, /) -> list[modal.types.InputInfo]:
1059
- """Returns a structure representing the call graph from a given root
1060
- call ID, along with the status of execution for each node.
1061
+ """Fetch information about the graph of Inputs this FunctionCall is part of.
1062
+
1063
+ Note: the call graph data is not populated in real-time, and its capture is best-effort.
1064
+ We do not recommend relying on this method for critical use cases.
1061
1065
 
1062
- See [`modal.call_graph`](https://modal.com/docs/sdk/py/latest/modal.call_graph) reference page
1063
- for documentation on the structure of the returned `InputInfo` items.
1066
+ See the [`modal.types`](/docs/sdk/py/latest/modal.types) reference for information
1067
+ on the return values.
1064
1068
 
1065
1069
  Returns:
1066
1070
  A list of `InputInfo` nodes describing the call graph.
@@ -1071,26 +1075,22 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
1071
1075
 
1072
1076
  class __cancel_spec(typing_extensions.Protocol):
1073
1077
  def __call__(self, /, terminate_containers: bool = False):
1074
- """Cancels the function call, which will stop its execution and mark its inputs as
1075
- [`TERMINATED`](https://modal.com/docs/sdk/py/latest/modal.call_graph#modalcall_graphinputstatus).
1076
-
1077
- If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
1078
- causing any non-cancelled inputs on those containers to be rescheduled in new containers.
1078
+ """Cancel the FunctionCall and terminate its inputs without retrying.
1079
1079
 
1080
1080
  Args:
1081
- terminate_containers: If True, forcibly terminate workers running cancelled inputs.
1081
+ terminate_containers: If True, terminate the containers running the cancelled
1082
+ inputs. Any other inputs running concurrently on those containers will be
1083
+ rescheduled.
1082
1084
  """
1083
1085
  ...
1084
1086
 
1085
1087
  async def aio(self, /, terminate_containers: bool = False):
1086
- """Cancels the function call, which will stop its execution and mark its inputs as
1087
- [`TERMINATED`](https://modal.com/docs/sdk/py/latest/modal.call_graph#modalcall_graphinputstatus).
1088
-
1089
- If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
1090
- causing any non-cancelled inputs on those containers to be rescheduled in new containers.
1088
+ """Cancel the FunctionCall and terminate its inputs without retrying.
1091
1089
 
1092
1090
  Args:
1093
- terminate_containers: If True, forcibly terminate workers running cancelled inputs.
1091
+ terminate_containers: If True, terminate the containers running the cancelled
1092
+ inputs. Any other inputs running concurrently on those containers will be
1093
+ rescheduled.
1094
1094
  """
1095
1095
  ...
1096
1096
 
@@ -332,6 +332,7 @@ class _Sandbox(_Object, type_prefix="sb"):
332
332
  _attached: bool
333
333
  _filesystem: _SandboxFilesystem | None
334
334
  _is_v2: bool = False
335
+ _app_id: str | None
335
336
 
336
337
  @staticmethod
337
338
  def _default_pty_info() -> api_pb2.PTYInfo:
@@ -500,7 +501,7 @@ class _Sandbox(_Object, type_prefix="sb"):
500
501
  create_resp = await load_context.client.stub.SandboxCreate(create_req)
501
502
  rpc_elapsed = time.monotonic() - rpc_start
502
503
  sandbox_id = create_resp.sandbox_id
503
- self._hydrate(sandbox_id, load_context.client, None)
504
+ self._hydrate(sandbox_id, load_context.client, create_resp.metadata)
504
505
 
505
506
  if logger.isEnabledFor(logging.DEBUG):
506
507
  total_elapsed = time.monotonic() - load_start
@@ -1017,7 +1018,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1017
1018
  )
1018
1019
  rpc_elapsed = time.monotonic() - rpc_start
1019
1020
  sandbox_id = create_resp.sandbox_id
1020
- self._hydrate(sandbox_id, load_context.client, None)
1021
+ self._hydrate(sandbox_id, load_context.client, create_resp.metadata)
1021
1022
  self._is_v2 = True
1022
1023
  self._task_id = create_resp.task_id
1023
1024
  self._hydrate_metadata_v2()
@@ -1070,7 +1071,22 @@ class _Sandbox(_Object, type_prefix="sb"):
1070
1071
  await resolver.load(obj, load_context)
1071
1072
  return obj
1072
1073
 
1073
- def _hydrate_metadata(self, handle_metadata: Message | None):
1074
+ def _get_metadata(self) -> api_pb2.SandboxHandleMetadata:
1075
+ metadata = api_pb2.SandboxHandleMetadata(app_id=self._app_id or "")
1076
+ if hasattr(self, "_result") and self._result is not None:
1077
+ metadata.result.CopyFrom(self._result)
1078
+ return metadata
1079
+
1080
+ def _hydrate_metadata(self, handle_metadata: Message | None) -> None:
1081
+ self._app_id = None
1082
+ self._result = None
1083
+ if handle_metadata is not None:
1084
+ assert isinstance(handle_metadata, api_pb2.SandboxHandleMetadata), (
1085
+ f"{type(handle_metadata)} is not SandboxHandleMetadata"
1086
+ )
1087
+ self._app_id = handle_metadata.app_id
1088
+ if handle_metadata.HasField("result"):
1089
+ self._result = handle_metadata.result
1074
1090
  self._stdout = StreamReader(
1075
1091
  _StreamReaderThroughServerParams(
1076
1092
  file_descriptor=api_pb2.FILE_DESCRIPTOR_STDOUT,
@@ -1088,13 +1104,14 @@ class _Sandbox(_Object, type_prefix="sb"):
1088
1104
  by_line=True,
1089
1105
  )
1090
1106
  self._stdin = StreamWriter(_StreamWriterThroughServerParams(object_id=self.object_id, client=self._client))
1091
- self._result = None
1092
1107
  self._task_id = None
1093
1108
  self._tunnels = None
1094
1109
  self._enable_snapshot = False
1095
1110
  self._command_router_client = None
1096
1111
  self._filesystem = None
1097
- self._is_v2 = False
1112
+ self._is_v2 = _is_v2_sandbox_id(self.object_id)
1113
+ if self._is_v2:
1114
+ self._hydrate_metadata_v2()
1098
1115
 
1099
1116
  def _hydrate_metadata_v2(self) -> None:
1100
1117
  """Wire up V2 stdio readers that read directly from the worker. Cheap
@@ -1133,11 +1150,13 @@ class _Sandbox(_Object, type_prefix="sb"):
1133
1150
  super()._initialize_from_other(other)
1134
1151
  self._attached = other._attached
1135
1152
  self._is_v2 = other._is_v2
1153
+ self._app_id = other._app_id
1136
1154
 
1137
1155
  def _initialize_from_empty(self):
1138
1156
  super()._initialize_from_empty()
1139
1157
  self._attached = True
1140
1158
  self._is_v2 = False
1159
+ self._app_id = None
1141
1160
 
1142
1161
  async def detach(self):
1143
1162
  """Disconnects your client from the sandbox and cleans up resources assoicated with the connection.
@@ -1200,7 +1219,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1200
1219
 
1201
1220
  req = api_pb2.SandboxGetFromNameRequest(sandbox_name=name, app_name=app_name, environment_name=env_name)
1202
1221
  resp = await client.stub.SandboxGetFromName(req)
1203
- return _Sandbox._new_hydrated(resp.sandbox_id, client, None)
1222
+ return _Sandbox._new_hydrated(resp.sandbox_id, client, resp.metadata)
1204
1223
 
1205
1224
  @staticmethod
1206
1225
  async def _experimental_from_name(
@@ -1235,7 +1254,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1235
1254
  auth_token = await client._auth_token_manager.get_token()
1236
1255
  resp = await client.stub.SandboxGetFromNameV2(req, metadata=[("x-modal-auth-token", auth_token)])
1237
1256
 
1238
- obj = _Sandbox._new_hydrated(resp.sandbox_id, client, None)
1257
+ obj = _Sandbox._new_hydrated(resp.sandbox_id, client, resp.metadata)
1239
1258
  obj._is_v2 = True
1240
1259
  obj._hydrate_metadata_v2()
1241
1260
  return obj
@@ -1266,7 +1285,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1266
1285
  else:
1267
1286
  resp = await client.stub.SandboxWait(req)
1268
1287
 
1269
- obj = _Sandbox._new_hydrated(sandbox_id, client, None)
1288
+ obj = _Sandbox._new_hydrated(sandbox_id, client, resp.metadata)
1270
1289
  obj._is_v2 = is_v2
1271
1290
  if is_v2:
1272
1291
  obj._hydrate_metadata_v2()
@@ -2360,7 +2379,7 @@ class _Sandbox(_Object, type_prefix="sb"):
2360
2379
 
2361
2380
  for sandbox_info in resp.sandboxes:
2362
2381
  sandbox_info: api_pb2.SandboxInfo
2363
- obj = _Sandbox._new_hydrated(sandbox_info.id, client, None)
2382
+ obj = _Sandbox._new_hydrated(sandbox_info.id, client, sandbox_info.metadata)
2364
2383
  obj._result = sandbox_info.task_info.result # TODO: send SandboxInfo as metadata to _new_hydrated?
2365
2384
  yield obj
2366
2385
 
@@ -2415,7 +2434,7 @@ class _Sandbox(_Object, type_prefix="sb"):
2415
2434
 
2416
2435
  for sandbox_info in resp.sandboxes:
2417
2436
  sandbox_info: api_pb2.SandboxInfo
2418
- obj = _Sandbox._new_hydrated(sandbox_info.id, client, None)
2437
+ obj = _Sandbox._new_hydrated(sandbox_info.id, client, sandbox_info.metadata)
2419
2438
  # SandboxListV2 only returns V2 sandboxes; mark them as such so
2420
2439
  # operations like wait/terminate/exec use the V2 RPCs and stdio.
2421
2440
  obj._is_v2 = True
@@ -150,6 +150,7 @@ class _Sandbox(modal._object._Object):
150
150
  _attached: bool
151
151
  _filesystem: typing.Optional[modal.sandbox_fs._SandboxFilesystem]
152
152
  _is_v2: bool
153
+ _app_id: typing.Optional[str]
153
154
 
154
155
  @staticmethod
155
156
  def _default_pty_info() -> modal_proto.api_pb2.PTYInfo: ...
@@ -424,7 +425,8 @@ class _Sandbox(modal._object._Object):
424
425
  """
425
426
  ...
426
427
 
427
- def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]): ...
428
+ def _get_metadata(self) -> modal_proto.api_pb2.SandboxHandleMetadata: ...
429
+ def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]) -> None: ...
428
430
  def _hydrate_metadata_v2(self) -> None:
429
431
  """Wire up V2 stdio readers that read directly from the worker. Cheap
430
432
  to call eagerly: the router connection is opened lazily on first read.
@@ -1342,6 +1344,7 @@ class Sandbox(modal.object.Object):
1342
1344
  _attached: bool
1343
1345
  _filesystem: typing.Optional[modal.sandbox_fs.SandboxFilesystem]
1344
1346
  _is_v2: bool
1347
+ _app_id: typing.Optional[str]
1345
1348
 
1346
1349
  def __init__(self, *args, **kwargs):
1347
1350
  """mdmd:hidden"""
@@ -1872,7 +1875,8 @@ class Sandbox(modal.object.Object):
1872
1875
 
1873
1876
  _experimental_create: typing.ClassVar[___experimental_create_spec]
1874
1877
 
1875
- def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]): ...
1878
+ def _get_metadata(self) -> modal_proto.api_pb2.SandboxHandleMetadata: ...
1879
+ def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]) -> None: ...
1876
1880
  def _hydrate_metadata_v2(self) -> None:
1877
1881
  """Wire up V2 stdio readers that read directly from the worker. Cheap
1878
1882
  to call eagerly: the router connection is opened lazily on first read.