modal 1.2.1.dev14__tar.gz → 1.2.1.dev16__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (198) hide show
  1. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/PKG-INFO +1 -1
  2. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/task_command_router_client.py +3 -5
  3. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/client.pyi +2 -2
  4. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/container_process.py +7 -1
  5. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/container_process.pyi +4 -0
  6. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/experimental/flash.py +21 -47
  7. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/experimental/flash.pyi +6 -20
  8. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/io_streams.py +1 -1
  9. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/sandbox.py +124 -6
  10. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/sandbox.pyi +139 -0
  11. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal.egg-info/PKG-INFO +1 -1
  12. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/api.proto +0 -9
  13. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/api_grpc.py +0 -16
  14. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/api_pb2.py +185 -205
  15. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/api_pb2.pyi +0 -30
  16. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/api_pb2_grpc.py +0 -34
  17. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/api_pb2_grpc.pyi +0 -12
  18. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/modal_api_grpc.py +0 -1
  19. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_version/__init__.py +1 -1
  20. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/LICENSE +0 -0
  21. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/README.md +0 -0
  22. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/__init__.py +0 -0
  23. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/__main__.py +0 -0
  24. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_billing.py +0 -0
  25. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_clustered_functions.py +0 -0
  26. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_clustered_functions.pyi +0 -0
  27. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_container_entrypoint.py +0 -0
  28. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_functions.py +0 -0
  29. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_ipython.py +0 -0
  30. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_location.py +0 -0
  31. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_object.py +0 -0
  32. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_output.py +0 -0
  33. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_partial_function.py +0 -0
  34. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_pty.py +0 -0
  35. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_resolver.py +0 -0
  36. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_resources.py +0 -0
  37. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/__init__.py +0 -0
  38. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/asgi.py +0 -0
  39. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/container_io_manager.py +0 -0
  40. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/container_io_manager.pyi +0 -0
  41. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/execution_context.py +0 -0
  42. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/execution_context.pyi +0 -0
  43. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  44. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/telemetry.py +0 -0
  45. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_runtime/user_code_imports.py +0 -0
  46. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_serialization.py +0 -0
  47. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_traceback.py +0 -0
  48. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_tunnel.py +0 -0
  49. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_tunnel.pyi +0 -0
  50. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_type_manager.py +0 -0
  51. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/__init__.py +0 -0
  52. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/app_utils.py +0 -0
  53. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/async_utils.py +0 -0
  54. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/auth_token_manager.py +0 -0
  55. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/blob_utils.py +0 -0
  56. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/bytes_io_segment_payload.py +0 -0
  57. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/deprecation.py +0 -0
  58. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/docker_utils.py +0 -0
  59. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/function_utils.py +0 -0
  60. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/git_utils.py +0 -0
  61. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/grpc_testing.py +0 -0
  62. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/grpc_utils.py +0 -0
  63. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/hash_utils.py +0 -0
  64. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/http_utils.py +0 -0
  65. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/jwt_utils.py +0 -0
  66. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/logger.py +0 -0
  67. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/mount_utils.py +0 -0
  68. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/name_utils.py +0 -0
  69. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/package_utils.py +0 -0
  70. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/pattern_utils.py +0 -0
  71. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/rand_pb_testing.py +0 -0
  72. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/shell_utils.py +0 -0
  73. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_utils/time_utils.py +0 -0
  74. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_vendor/__init__.py +0 -0
  75. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  76. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_vendor/cloudpickle.py +0 -0
  77. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_vendor/tblib.py +0 -0
  78. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/_watcher.py +0 -0
  79. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/app.py +0 -0
  80. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/app.pyi +0 -0
  81. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/billing.py +0 -0
  82. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/2023.12.312.txt +0 -0
  83. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/2023.12.txt +0 -0
  84. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/2024.04.txt +0 -0
  85. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/2024.10.txt +0 -0
  86. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/2025.06.txt +0 -0
  87. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/PREVIEW.txt +0 -0
  88. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/README.md +0 -0
  89. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/builder/base-images.json +0 -0
  90. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/call_graph.py +0 -0
  91. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/__init__.py +0 -0
  92. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/_download.py +0 -0
  93. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/_traceback.py +0 -0
  94. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/app.py +0 -0
  95. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/cluster.py +0 -0
  96. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/config.py +0 -0
  97. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/container.py +0 -0
  98. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/dict.py +0 -0
  99. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/entry_point.py +0 -0
  100. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/environment.py +0 -0
  101. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/import_refs.py +0 -0
  102. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/launch.py +0 -0
  103. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/network_file_system.py +0 -0
  104. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/profile.py +0 -0
  105. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/programs/__init__.py +0 -0
  106. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/programs/launch_instance_ssh.py +0 -0
  107. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/programs/run_jupyter.py +0 -0
  108. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/programs/run_marimo.py +0 -0
  109. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/programs/vscode.py +0 -0
  110. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/queues.py +0 -0
  111. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/run.py +0 -0
  112. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/secret.py +0 -0
  113. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/token.py +0 -0
  114. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/utils.py +0 -0
  115. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cli/volume.py +0 -0
  116. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/client.py +0 -0
  117. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cloud_bucket_mount.py +0 -0
  118. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cloud_bucket_mount.pyi +0 -0
  119. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cls.py +0 -0
  120. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/cls.pyi +0 -0
  121. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/config.py +0 -0
  122. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/dict.py +0 -0
  123. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/dict.pyi +0 -0
  124. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/environments.py +0 -0
  125. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/environments.pyi +0 -0
  126. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/exception.py +0 -0
  127. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/experimental/__init__.py +0 -0
  128. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/experimental/ipython.py +0 -0
  129. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/file_io.py +0 -0
  130. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/file_io.pyi +0 -0
  131. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/file_pattern_matcher.py +0 -0
  132. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/functions.py +0 -0
  133. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/functions.pyi +0 -0
  134. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/gpu.py +0 -0
  135. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/image.py +0 -0
  136. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/image.pyi +0 -0
  137. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/io_streams.pyi +0 -0
  138. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/mount.py +0 -0
  139. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/mount.pyi +0 -0
  140. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/network_file_system.py +0 -0
  141. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/network_file_system.pyi +0 -0
  142. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/object.py +0 -0
  143. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/object.pyi +0 -0
  144. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/output.py +0 -0
  145. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/parallel_map.py +0 -0
  146. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/parallel_map.pyi +0 -0
  147. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/partial_function.py +0 -0
  148. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/partial_function.pyi +0 -0
  149. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/proxy.py +0 -0
  150. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/proxy.pyi +0 -0
  151. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/py.typed +0 -0
  152. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/queue.py +0 -0
  153. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/queue.pyi +0 -0
  154. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/retries.py +0 -0
  155. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/runner.py +0 -0
  156. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/runner.pyi +0 -0
  157. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/running_app.py +0 -0
  158. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/schedule.py +0 -0
  159. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/scheduler_placement.py +0 -0
  160. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/secret.py +0 -0
  161. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/secret.pyi +0 -0
  162. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/serving.py +0 -0
  163. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/serving.pyi +0 -0
  164. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/snapshot.py +0 -0
  165. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/snapshot.pyi +0 -0
  166. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/stream_type.py +0 -0
  167. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/token_flow.py +0 -0
  168. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/token_flow.pyi +0 -0
  169. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/volume.py +0 -0
  170. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal/volume.pyi +0 -0
  171. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal.egg-info/SOURCES.txt +0 -0
  172. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal.egg-info/dependency_links.txt +0 -0
  173. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal.egg-info/entry_points.txt +0 -0
  174. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal.egg-info/requires.txt +0 -0
  175. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal.egg-info/top_level.txt +0 -0
  176. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_docs/__init__.py +0 -0
  177. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_docs/gen_cli_docs.py +0 -0
  178. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_docs/gen_reference_docs.py +0 -0
  179. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_docs/mdmd/__init__.py +0 -0
  180. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_docs/mdmd/mdmd.py +0 -0
  181. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_docs/mdmd/signatures.py +0 -0
  182. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/__init__.py +0 -0
  183. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/py.typed +0 -0
  184. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/sandbox_router.proto +0 -0
  185. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/sandbox_router_grpc.py +0 -0
  186. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/sandbox_router_pb2.py +0 -0
  187. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/sandbox_router_pb2.pyi +0 -0
  188. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  189. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  190. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/task_command_router.proto +0 -0
  191. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/task_command_router_grpc.py +0 -0
  192. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/task_command_router_pb2.py +0 -0
  193. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/task_command_router_pb2.pyi +0 -0
  194. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  195. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  196. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/modal_version/__main__.py +0 -0
  197. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/pyproject.toml +0 -0
  198. {modal-1.2.1.dev14 → modal-1.2.1.dev16}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev14
3
+ Version: 1.2.1.dev16
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -442,12 +442,10 @@ class TaskCommandRouterClient:
442
442
  logger.debug(f"Cancelled JWT refresh loop for exec with task ID {self._task_id}")
443
443
  break
444
444
  except Exception as e:
445
+ # Exceptions here can stem from non-transient errors against the server sending
446
+ # the TaskGetCommandRouterAccess RPC, for instance, if the task has finished.
445
447
  logger.warning(f"Background JWT refresh failed for exec with task ID {self._task_id}: {e}")
446
- try:
447
- await asyncio.sleep(1.0)
448
- except Exception:
449
- # Ignore sleep issues; loop will re-check closed flag.
450
- pass
448
+ break
451
449
 
452
450
  async def _stream_stdio(
453
451
  self,
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.2.1.dev14",
36
+ version: str = "1.2.1.dev16",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.1.dev14",
167
+ version: str = "1.2.1.dev16",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -273,6 +273,9 @@ class _ContainerProcessThroughCommandRouter(Generic[T]):
273
273
  )
274
274
  self._returncode = None
275
275
 
276
+ def __repr__(self) -> str:
277
+ return f"ContainerProcess(process_id={self._process_id!r})"
278
+
276
279
  @property
277
280
  def stdout(self) -> _StreamReader[T]:
278
281
  return self._stdout
@@ -314,7 +317,10 @@ class _ContainerProcessThroughCommandRouter(Generic[T]):
314
317
  raise InvalidError("Unexpected exit status")
315
318
  except ExecTimeoutError:
316
319
  logger.debug(f"ContainerProcess poll for {self._process_id} did not complete within deadline")
317
- return None
320
+ # TODO(saltzm): This is a weird API, but customers currently may rely on it. This
321
+ # should probably raise an ExecTimeoutError instead.
322
+ self._returncode = -1
323
+ return self._returncode
318
324
  except Exception as e:
319
325
  # Re-raise non-transient errors or errors resulting from exceeding retries on transient errors.
320
326
  logger.warning(f"ContainerProcess poll for {self._process_id} failed: {e}")
@@ -112,6 +112,10 @@ class _ContainerProcessThroughCommandRouter(typing.Generic[T]):
112
112
  """Initialize self. See help(type(self)) for accurate signature."""
113
113
  ...
114
114
 
115
+ def __repr__(self) -> str:
116
+ """Return repr(self)."""
117
+ ...
118
+
115
119
  @property
116
120
  def stdout(self) -> modal.io_streams._StreamReader[T]: ...
117
121
  @property
@@ -321,7 +321,7 @@ class _FlashPrometheusAutoscaler:
321
321
 
322
322
  async def _compute_target_containers(self, current_replicas: int) -> int:
323
323
  """
324
- Gets internal metrics from container to autoscale up or down.
324
+ Gets metrics from container to autoscale up or down.
325
325
  """
326
326
  containers = await self._get_all_containers()
327
327
  if len(containers) > current_replicas:
@@ -334,7 +334,7 @@ class _FlashPrometheusAutoscaler:
334
334
  if current_replicas == 0:
335
335
  return 1
336
336
 
337
- # Get metrics based on autoscaler type (prometheus or internal)
337
+ # Get metrics based on autoscaler type
338
338
  sum_metric, n_containers_with_metrics = await self._get_scaling_info(containers)
339
339
 
340
340
  desired_replicas = self._calculate_desired_replicas(
@@ -406,39 +406,26 @@ class _FlashPrometheusAutoscaler:
406
406
  return desired_replicas
407
407
 
408
408
  async def _get_scaling_info(self, containers) -> tuple[float, int]:
409
- """Get metrics using either internal container metrics API or prometheus HTTP endpoints."""
410
- if self.metrics_endpoint == "internal":
411
- container_metrics_results = await asyncio.gather(
412
- *[self._get_container_metrics(container.task_id) for container in containers]
413
- )
414
- container_metrics_list = []
415
- for container_metric in container_metrics_results:
416
- if container_metric is None:
417
- continue
418
- container_metrics_list.append(getattr(container_metric.metrics, self.target_metric))
419
-
420
- sum_metric = sum(container_metrics_list)
421
- n_containers_with_metrics = len(container_metrics_list)
422
- else:
423
- sum_metric = 0
424
- n_containers_with_metrics = 0
425
-
426
- container_metrics_list = await asyncio.gather(
427
- *[
428
- self._get_metrics(f"https://{container.host}:{container.port}/{self.metrics_endpoint}")
429
- for container in containers
430
- ]
431
- )
409
+ """Get metrics using container exposed metrics endpoints."""
410
+ sum_metric = 0
411
+ n_containers_with_metrics = 0
412
+
413
+ container_metrics_list = await asyncio.gather(
414
+ *[
415
+ self._get_metrics(f"https://{container.host}:{container.port}/{self.metrics_endpoint}")
416
+ for container in containers
417
+ ]
418
+ )
432
419
 
433
- for container_metrics in container_metrics_list:
434
- if (
435
- container_metrics is None
436
- or self.target_metric not in container_metrics
437
- or len(container_metrics[self.target_metric]) == 0
438
- ):
439
- continue
440
- sum_metric += container_metrics[self.target_metric][0].value
441
- n_containers_with_metrics += 1
420
+ for container_metrics in container_metrics_list:
421
+ if (
422
+ container_metrics is None
423
+ or self.target_metric not in container_metrics
424
+ or len(container_metrics[self.target_metric]) == 0
425
+ ):
426
+ continue
427
+ sum_metric += container_metrics[self.target_metric][0].value
428
+ n_containers_with_metrics += 1
442
429
 
443
430
  return sum_metric, n_containers_with_metrics
444
431
 
@@ -474,15 +461,6 @@ class _FlashPrometheusAutoscaler:
474
461
 
475
462
  return metrics
476
463
 
477
- async def _get_container_metrics(self, container_id: str) -> Optional[api_pb2.TaskGetAutoscalingMetricsResponse]:
478
- req = api_pb2.TaskGetAutoscalingMetricsRequest(task_id=container_id)
479
- try:
480
- resp = await retry_transient_errors(self.client.stub.TaskGetAutoscalingMetrics, req)
481
- return resp
482
- except Exception as e:
483
- logger.warning(f"[Modal Flash] Error getting metrics for container {container_id}: {e}")
484
- return None
485
-
486
464
  async def _get_all_containers(self):
487
465
  req = api_pb2.FlashContainerListRequest(function_id=self.fn.object_id)
488
466
  resp = await retry_transient_errors(self.client.stub.FlashContainerList, req)
@@ -572,14 +550,10 @@ async def flash_prometheus_autoscaler(
572
550
  app_name: str,
573
551
  cls_name: str,
574
552
  # Endpoint to fetch metrics from. Must be in Prometheus format. Example: "/metrics"
575
- # If metrics_endpoint is "internal", we will use containers' internal metrics to autoscale instead.
576
553
  metrics_endpoint: str,
577
554
  # Target metric to autoscale on. Example: "vllm:num_requests_running"
578
- # If metrics_endpoint is "internal", target_metrics options are: [cpu_usage_percent, memory_usage_percent]
579
555
  target_metric: str,
580
556
  # Target metric value. Example: 25
581
- # If metrics_endpoint is "internal", target_metric_value is a percentage value between 0.1 and 1.0 (inclusive),
582
- # indicating container's usage of that metric.
583
557
  target_metric_value: float,
584
558
  min_containers: Optional[int] = None,
585
559
  max_containers: Optional[int] = None,
@@ -1,5 +1,4 @@
1
1
  import modal.client
2
- import modal_proto.api_pb2
3
2
  import subprocess
4
3
  import typing
5
4
  import typing_extensions
@@ -139,7 +138,7 @@ class _FlashPrometheusAutoscaler:
139
138
  async def start(self): ...
140
139
  async def _run_autoscaler_loop(self): ...
141
140
  async def _compute_target_containers(self, current_replicas: int) -> int:
142
- """Gets internal metrics from container to autoscale up or down."""
141
+ """Gets metrics from container to autoscale up or down."""
143
142
  ...
144
143
 
145
144
  def _calculate_desired_replicas(
@@ -154,13 +153,10 @@ class _FlashPrometheusAutoscaler:
154
153
  ...
155
154
 
156
155
  async def _get_scaling_info(self, containers) -> tuple[float, int]:
157
- """Get metrics using either internal container metrics API or prometheus HTTP endpoints."""
156
+ """Get metrics using container exposed metrics endpoints."""
158
157
  ...
159
158
 
160
159
  async def _get_metrics(self, url: str) -> typing.Optional[dict[str, list[typing.Any]]]: ...
161
- async def _get_container_metrics(
162
- self, container_id: str
163
- ) -> typing.Optional[modal_proto.api_pb2.TaskGetAutoscalingMetricsResponse]: ...
164
160
  async def _get_all_containers(self): ...
165
161
  async def _set_target_slots(self, target_slots: int): ...
166
162
  def _make_scaling_decision(
@@ -226,11 +222,11 @@ class FlashPrometheusAutoscaler:
226
222
 
227
223
  class ___compute_target_containers_spec(typing_extensions.Protocol[SUPERSELF]):
228
224
  def __call__(self, /, current_replicas: int) -> int:
229
- """Gets internal metrics from container to autoscale up or down."""
225
+ """Gets metrics from container to autoscale up or down."""
230
226
  ...
231
227
 
232
228
  async def aio(self, /, current_replicas: int) -> int:
233
- """Gets internal metrics from container to autoscale up or down."""
229
+ """Gets metrics from container to autoscale up or down."""
234
230
  ...
235
231
 
236
232
  _compute_target_containers: ___compute_target_containers_spec[typing_extensions.Self]
@@ -248,11 +244,11 @@ class FlashPrometheusAutoscaler:
248
244
 
249
245
  class ___get_scaling_info_spec(typing_extensions.Protocol[SUPERSELF]):
250
246
  def __call__(self, /, containers) -> tuple[float, int]:
251
- """Get metrics using either internal container metrics API or prometheus HTTP endpoints."""
247
+ """Get metrics using container exposed metrics endpoints."""
252
248
  ...
253
249
 
254
250
  async def aio(self, /, containers) -> tuple[float, int]:
255
- """Get metrics using either internal container metrics API or prometheus HTTP endpoints."""
251
+ """Get metrics using container exposed metrics endpoints."""
256
252
  ...
257
253
 
258
254
  _get_scaling_info: ___get_scaling_info_spec[typing_extensions.Self]
@@ -263,16 +259,6 @@ class FlashPrometheusAutoscaler:
263
259
 
264
260
  _get_metrics: ___get_metrics_spec[typing_extensions.Self]
265
261
 
266
- class ___get_container_metrics_spec(typing_extensions.Protocol[SUPERSELF]):
267
- def __call__(
268
- self, /, container_id: str
269
- ) -> typing.Optional[modal_proto.api_pb2.TaskGetAutoscalingMetricsResponse]: ...
270
- async def aio(
271
- self, /, container_id: str
272
- ) -> typing.Optional[modal_proto.api_pb2.TaskGetAutoscalingMetricsResponse]: ...
273
-
274
- _get_container_metrics: ___get_container_metrics_spec[typing_extensions.Self]
275
-
276
262
  class ___get_all_containers_spec(typing_extensions.Protocol[SUPERSELF]):
277
263
  def __call__(self, /): ...
278
264
  async def aio(self, /): ...
@@ -553,7 +553,7 @@ class _StreamReader(Generic[T]):
553
553
  # unimplemented for now.
554
554
  if stream_type == StreamType.STDOUT:
555
555
  raise NotImplementedError(
556
- "Currently only the PIPE stream type is supported when using exec "
556
+ "Currently the STDOUT stream type is not supported when using exec "
557
557
  "through a task command router, which is currently in beta."
558
558
  )
559
559
  params = _StreamReaderThroughCommandRouterParams(
@@ -3,6 +3,7 @@ import asyncio
3
3
  import json
4
4
  import os
5
5
  import time
6
+ import uuid
6
7
  from collections.abc import AsyncGenerator, Collection, Sequence
7
8
  from dataclasses import dataclass
8
9
  from typing import TYPE_CHECKING, Any, AsyncIterator, Literal, Optional, Union, overload
@@ -20,7 +21,7 @@ from modal._tunnel import Tunnel
20
21
  from modal.cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
21
22
  from modal.mount import _Mount
22
23
  from modal.volume import _Volume
23
- from modal_proto import api_pb2
24
+ from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
24
25
 
25
26
  from ._object import _get_environment_name, _Object
26
27
  from ._resolver import Resolver
@@ -30,6 +31,7 @@ from ._utils.deprecation import deprecation_warning
30
31
  from ._utils.grpc_utils import retry_transient_errors
31
32
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
32
33
  from ._utils.name_utils import is_valid_object_name
34
+ from ._utils.task_command_router_client import TaskCommandRouterClient
33
35
  from .client import _Client
34
36
  from .container_process import _ContainerProcess
35
37
  from .exception import AlreadyExistsError, ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
@@ -121,9 +123,10 @@ class _Sandbox(_Object, type_prefix="sb"):
121
123
  _stdout: _StreamReader[str]
122
124
  _stderr: _StreamReader[str]
123
125
  _stdin: _StreamWriter
124
- _task_id: Optional[str] = None
125
- _tunnels: Optional[dict[int, Tunnel]] = None
126
- _enable_snapshot: bool = False
126
+ _task_id: Optional[str]
127
+ _tunnels: Optional[dict[int, Tunnel]]
128
+ _enable_snapshot: bool
129
+ _command_router_client: Optional[TaskCommandRouterClient]
127
130
 
128
131
  @staticmethod
129
132
  def _default_pty_info() -> api_pb2.PTYInfo:
@@ -521,6 +524,10 @@ class _Sandbox(_Object, type_prefix="sb"):
521
524
  )
522
525
  self._stdin = StreamWriter(self.object_id, "sandbox", self._client)
523
526
  self._result = None
527
+ self._task_id = None
528
+ self._tunnels = None
529
+ self._enable_snapshot = False
530
+ self._command_router_client = None
524
531
 
525
532
  @staticmethod
526
533
  async def from_name(
@@ -730,6 +737,13 @@ class _Sandbox(_Object, type_prefix="sb"):
730
737
  await asyncio.sleep(0.5)
731
738
  return self._task_id
732
739
 
740
+ async def _get_command_router_client(self, task_id: str) -> Optional[TaskCommandRouterClient]:
741
+ if self._command_router_client is None:
742
+ # Attempt to initialize a router client. Returns None if the new exec path not enabled
743
+ # for this sandbox.
744
+ self._command_router_client = await TaskCommandRouterClient.try_init(self._client, task_id)
745
+ return self._command_router_client
746
+
733
747
  @overload
734
748
  async def exec(
735
749
  self,
@@ -855,14 +869,49 @@ class _Sandbox(_Object, type_prefix="sb"):
855
869
  await TaskContext.gather(*secret_coros)
856
870
 
857
871
  task_id = await self._get_task_id()
872
+ kwargs = {
873
+ "task_id": task_id,
874
+ "pty_info": pty_info,
875
+ "stdout": stdout,
876
+ "stderr": stderr,
877
+ "timeout": timeout,
878
+ "workdir": workdir,
879
+ "secret_ids": [secret.object_id for secret in secrets],
880
+ "text": text,
881
+ "bufsize": bufsize,
882
+ "runtime_debug": config.get("function_runtime_debug"),
883
+ }
884
+ # NB: This must come after the task ID is set, since the sandbox must be
885
+ # scheduled before we can create a router client.
886
+ if (command_router_client := await self._get_command_router_client(task_id)) is not None:
887
+ kwargs["command_router_client"] = command_router_client
888
+ return await self._exec_through_command_router(*args, **kwargs)
889
+ else:
890
+ return await self._exec_through_server(*args, **kwargs)
891
+
892
+ async def _exec_through_server(
893
+ self,
894
+ *args: str,
895
+ task_id: str,
896
+ pty_info: Optional[api_pb2.PTYInfo] = None,
897
+ stdout: StreamType = StreamType.PIPE,
898
+ stderr: StreamType = StreamType.PIPE,
899
+ timeout: Optional[int] = None,
900
+ workdir: Optional[str] = None,
901
+ secret_ids: Optional[Collection[str]] = None,
902
+ text: bool = True,
903
+ bufsize: Literal[-1, 1] = -1,
904
+ runtime_debug: bool = False,
905
+ ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
906
+ """Execute a command through the Modal server."""
858
907
  req = api_pb2.ContainerExecRequest(
859
908
  task_id=task_id,
860
909
  command=args,
861
910
  pty_info=pty_info,
862
- runtime_debug=config.get("function_runtime_debug"),
911
+ runtime_debug=runtime_debug,
863
912
  timeout_secs=timeout or 0,
864
913
  workdir=workdir,
865
- secret_ids=[secret.object_id for secret in secrets],
914
+ secret_ids=secret_ids,
866
915
  )
867
916
  resp = await retry_transient_errors(self._client.stub.ContainerExec, req)
868
917
  by_line = bufsize == 1
@@ -879,6 +928,75 @@ class _Sandbox(_Object, type_prefix="sb"):
879
928
  by_line=by_line,
880
929
  )
881
930
 
931
+ async def _exec_through_command_router(
932
+ self,
933
+ *args: str,
934
+ task_id: str,
935
+ command_router_client: TaskCommandRouterClient,
936
+ pty_info: Optional[api_pb2.PTYInfo] = None,
937
+ stdout: StreamType = StreamType.PIPE,
938
+ stderr: StreamType = StreamType.PIPE,
939
+ timeout: Optional[int] = None,
940
+ workdir: Optional[str] = None,
941
+ secret_ids: Optional[Collection[str]] = None,
942
+ text: bool = True,
943
+ bufsize: Literal[-1, 1] = -1,
944
+ runtime_debug: bool = False,
945
+ ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
946
+ """Execute a command through a task command router running on the Modal worker."""
947
+
948
+ # Generate a random process ID to use as a combination of idempotency key/process identifier.
949
+ process_id = str(uuid.uuid4())
950
+ if stdout == StreamType.PIPE:
951
+ stdout_config = sr_pb2.TaskExecStdoutConfig.TASK_EXEC_STDOUT_CONFIG_PIPE
952
+ elif stdout == StreamType.DEVNULL:
953
+ stdout_config = sr_pb2.TaskExecStdoutConfig.TASK_EXEC_STDOUT_CONFIG_DEVNULL
954
+ elif stdout == StreamType.STDOUT:
955
+ # TODO(saltzm): This is a behavior change from the old implementation. We should
956
+ # probably implement the old behavior of printing to stdout before moving out of beta.
957
+ raise NotImplementedError(
958
+ "Currently the STDOUT stream type is not supported when using exec "
959
+ "through a task command router, which is currently in beta."
960
+ )
961
+ else:
962
+ raise ValueError("Unsupported StreamType for stdout")
963
+
964
+ if stderr == StreamType.PIPE:
965
+ stderr_config = sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_PIPE
966
+ elif stderr == StreamType.DEVNULL:
967
+ stderr_config = sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_DEVNULL
968
+ elif stderr == StreamType.STDOUT:
969
+ stderr_config = sr_pb2.TaskExecStderrConfig.TASK_EXEC_STDERR_CONFIG_STDOUT
970
+ else:
971
+ raise ValueError("Unsupported StreamType for stderr")
972
+
973
+ # Start the process.
974
+ start_req = sr_pb2.TaskExecStartRequest(
975
+ task_id=task_id,
976
+ exec_id=process_id,
977
+ command_args=args,
978
+ stdout_config=stdout_config,
979
+ stderr_config=stderr_config,
980
+ timeout_secs=timeout,
981
+ workdir=workdir,
982
+ secret_ids=secret_ids,
983
+ pty_info=pty_info,
984
+ runtime_debug=runtime_debug,
985
+ )
986
+ _ = await command_router_client.exec_start(start_req)
987
+
988
+ return _ContainerProcess(
989
+ process_id,
990
+ task_id,
991
+ self._client,
992
+ command_router_client=command_router_client,
993
+ stdout=stdout,
994
+ stderr=stderr,
995
+ text=text,
996
+ by_line=bufsize == 1,
997
+ exec_deadline=time.monotonic() + int(timeout) if timeout else None,
998
+ )
999
+
882
1000
  async def _experimental_snapshot(self) -> _SandboxSnapshot:
883
1001
  await self._get_task_id()
884
1002
  snap_req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id)
@@ -3,6 +3,7 @@ import collections.abc
3
3
  import google.protobuf.message
4
4
  import modal._object
5
5
  import modal._tunnel
6
+ import modal._utils.task_command_router_client
6
7
  import modal.app
7
8
  import modal.client
8
9
  import modal.cloud_bucket_mount
@@ -83,6 +84,7 @@ class _Sandbox(modal._object._Object):
83
84
  _task_id: typing.Optional[str]
84
85
  _tunnels: typing.Optional[dict[int, modal._tunnel.Tunnel]]
85
86
  _enable_snapshot: bool
87
+ _command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]
86
88
 
87
89
  @staticmethod
88
90
  def _default_pty_info() -> modal_proto.api_pb2.PTYInfo: ...
@@ -305,6 +307,9 @@ class _Sandbox(modal._object._Object):
305
307
  ...
306
308
 
307
309
  async def _get_task_id(self) -> str: ...
310
+ async def _get_command_router_client(
311
+ self, task_id: str
312
+ ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
308
313
  @typing.overload
309
314
  async def exec(
310
315
  self,
@@ -356,6 +361,41 @@ class _Sandbox(modal._object._Object):
356
361
  """
357
362
  ...
358
363
 
364
+ async def _exec_through_server(
365
+ self,
366
+ *args: str,
367
+ task_id: str,
368
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
369
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
370
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
371
+ timeout: typing.Optional[int] = None,
372
+ workdir: typing.Optional[str] = None,
373
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
374
+ text: bool = True,
375
+ bufsize: typing.Literal[-1, 1] = -1,
376
+ runtime_debug: bool = False,
377
+ ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
378
+ """Execute a command through the Modal server."""
379
+ ...
380
+
381
+ async def _exec_through_command_router(
382
+ self,
383
+ *args: str,
384
+ task_id: str,
385
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
386
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
387
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
388
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
389
+ timeout: typing.Optional[int] = None,
390
+ workdir: typing.Optional[str] = None,
391
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
392
+ text: bool = True,
393
+ bufsize: typing.Literal[-1, 1] = -1,
394
+ runtime_debug: bool = False,
395
+ ) -> typing.Union[modal.container_process._ContainerProcess[bytes], modal.container_process._ContainerProcess[str]]:
396
+ """Execute a command through a task command router running on the Modal worker."""
397
+ ...
398
+
359
399
  async def _experimental_snapshot(self) -> modal.snapshot._SandboxSnapshot: ...
360
400
  @staticmethod
361
401
  async def _experimental_from_snapshot(
@@ -444,6 +484,7 @@ class Sandbox(modal.object.Object):
444
484
  _task_id: typing.Optional[str]
445
485
  _tunnels: typing.Optional[dict[int, modal._tunnel.Tunnel]]
446
486
  _enable_snapshot: bool
487
+ _command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]
447
488
 
448
489
  def __init__(self, *args, **kwargs):
449
490
  """mdmd:hidden"""
@@ -907,6 +948,16 @@ class Sandbox(modal.object.Object):
907
948
 
908
949
  _get_task_id: ___get_task_id_spec[typing_extensions.Self]
909
950
 
951
+ class ___get_command_router_client_spec(typing_extensions.Protocol[SUPERSELF]):
952
+ def __call__(
953
+ self, /, task_id: str
954
+ ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
955
+ async def aio(
956
+ self, /, task_id: str
957
+ ) -> typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient]: ...
958
+
959
+ _get_command_router_client: ___get_command_router_client_spec[typing_extensions.Self]
960
+
910
961
  class __exec_spec(typing_extensions.Protocol[SUPERSELF]):
911
962
  @typing.overload
912
963
  def __call__(
@@ -1026,6 +1077,94 @@ class Sandbox(modal.object.Object):
1026
1077
 
1027
1078
  _exec: ___exec_spec[typing_extensions.Self]
1028
1079
 
1080
+ class ___exec_through_server_spec(typing_extensions.Protocol[SUPERSELF]):
1081
+ def __call__(
1082
+ self,
1083
+ /,
1084
+ *args: str,
1085
+ task_id: str,
1086
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1087
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1088
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1089
+ timeout: typing.Optional[int] = None,
1090
+ workdir: typing.Optional[str] = None,
1091
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1092
+ text: bool = True,
1093
+ bufsize: typing.Literal[-1, 1] = -1,
1094
+ runtime_debug: bool = False,
1095
+ ) -> typing.Union[
1096
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1097
+ ]:
1098
+ """Execute a command through the Modal server."""
1099
+ ...
1100
+
1101
+ async def aio(
1102
+ self,
1103
+ /,
1104
+ *args: str,
1105
+ task_id: str,
1106
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1107
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1108
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1109
+ timeout: typing.Optional[int] = None,
1110
+ workdir: typing.Optional[str] = None,
1111
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1112
+ text: bool = True,
1113
+ bufsize: typing.Literal[-1, 1] = -1,
1114
+ runtime_debug: bool = False,
1115
+ ) -> typing.Union[
1116
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1117
+ ]:
1118
+ """Execute a command through the Modal server."""
1119
+ ...
1120
+
1121
+ _exec_through_server: ___exec_through_server_spec[typing_extensions.Self]
1122
+
1123
+ class ___exec_through_command_router_spec(typing_extensions.Protocol[SUPERSELF]):
1124
+ def __call__(
1125
+ self,
1126
+ /,
1127
+ *args: str,
1128
+ task_id: str,
1129
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
1130
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1131
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1132
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1133
+ timeout: typing.Optional[int] = None,
1134
+ workdir: typing.Optional[str] = None,
1135
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1136
+ text: bool = True,
1137
+ bufsize: typing.Literal[-1, 1] = -1,
1138
+ runtime_debug: bool = False,
1139
+ ) -> typing.Union[
1140
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1141
+ ]:
1142
+ """Execute a command through a task command router running on the Modal worker."""
1143
+ ...
1144
+
1145
+ async def aio(
1146
+ self,
1147
+ /,
1148
+ *args: str,
1149
+ task_id: str,
1150
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
1151
+ pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
1152
+ stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1153
+ stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
1154
+ timeout: typing.Optional[int] = None,
1155
+ workdir: typing.Optional[str] = None,
1156
+ secret_ids: typing.Optional[collections.abc.Collection[str]] = None,
1157
+ text: bool = True,
1158
+ bufsize: typing.Literal[-1, 1] = -1,
1159
+ runtime_debug: bool = False,
1160
+ ) -> typing.Union[
1161
+ modal.container_process.ContainerProcess[bytes], modal.container_process.ContainerProcess[str]
1162
+ ]:
1163
+ """Execute a command through a task command router running on the Modal worker."""
1164
+ ...
1165
+
1166
+ _exec_through_command_router: ___exec_through_command_router_spec[typing_extensions.Self]
1167
+
1029
1168
  class ___experimental_snapshot_spec(typing_extensions.Protocol[SUPERSELF]):
1030
1169
  def __call__(self, /) -> modal.snapshot.SandboxSnapshot: ...
1031
1170
  async def aio(self, /) -> modal.snapshot.SandboxSnapshot: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev14
3
+ Version: 1.2.1.dev16
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0