modAL 1.3.6.dev9__tar.gz → 1.3.6.dev10__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 (210) hide show
  1. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/PKG-INFO +1 -1
  2. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/task_command_router_client.py +38 -0
  3. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/shell.py +57 -7
  4. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/client.pyi +2 -2
  5. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/sandbox.py +252 -12
  6. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/sandbox.pyi +235 -0
  7. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal.egg-info/PKG-INFO +1 -1
  8. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_version/__init__.py +1 -1
  9. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/LICENSE +0 -0
  10. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/README.md +0 -0
  11. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/__init__.py +0 -0
  12. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/__main__.py +0 -0
  13. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_billing.py +0 -0
  14. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_clustered_functions.py +0 -0
  15. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_clustered_functions.pyi +0 -0
  16. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_container_entrypoint.py +0 -0
  17. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_functions.py +0 -0
  18. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_grpc_client.py +0 -0
  19. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_ipython.py +0 -0
  20. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_load_context.py +0 -0
  21. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_location.py +0 -0
  22. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_logs.py +0 -0
  23. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_object.py +0 -0
  24. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_output/__init__.py +0 -0
  25. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_output/manager.py +0 -0
  26. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_output/pty.py +0 -0
  27. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_output/rich.py +0 -0
  28. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_output/status.py +0 -0
  29. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_partial_function.py +0 -0
  30. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_resolver.py +0 -0
  31. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_resources.py +0 -0
  32. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/__init__.py +0 -0
  33. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/asgi.py +0 -0
  34. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/container_io_manager.py +0 -0
  35. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
  36. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/execution_context.py +0 -0
  37. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/execution_context.pyi +0 -0
  38. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  39. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/telemetry.py +0 -0
  40. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
  41. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_runtime/user_code_imports.py +0 -0
  42. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_serialization.py +0 -0
  43. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_server.py +0 -0
  44. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_traceback.py +0 -0
  45. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_tunnel.py +0 -0
  46. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_tunnel.pyi +0 -0
  47. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_type_manager.py +0 -0
  48. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/__init__.py +0 -0
  49. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/app_utils.py +0 -0
  50. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/async_utils.py +0 -0
  51. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/auth_token_manager.py +0 -0
  52. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/blob_utils.py +0 -0
  53. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/browser_utils.py +0 -0
  54. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
  55. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/deprecation.py +0 -0
  56. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/docker_utils.py +0 -0
  57. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/function_utils.py +0 -0
  58. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/git_utils.py +0 -0
  59. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/grpc_testing.py +0 -0
  60. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/grpc_utils.py +0 -0
  61. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/hash_utils.py +0 -0
  62. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/http_utils.py +0 -0
  63. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/jwt_utils.py +0 -0
  64. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/logger.py +0 -0
  65. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/mount_utils.py +0 -0
  66. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/name_utils.py +0 -0
  67. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/package_utils.py +0 -0
  68. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/pattern_utils.py +0 -0
  69. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/rand_pb_testing.py +0 -0
  70. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/sandbox_fs_utils.py +0 -0
  71. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/shell_utils.py +0 -0
  72. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_utils/time_utils.py +0 -0
  73. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_vendor/__init__.py +0 -0
  74. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  75. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_vendor/cloudpickle.py +0 -0
  76. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_vendor/tblib.py +0 -0
  77. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_vendor/version.py +0 -0
  78. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/_watcher.py +0 -0
  79. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/app.py +0 -0
  80. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/app.pyi +0 -0
  81. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/billing.py +0 -0
  82. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/2023.12.312.txt +0 -0
  83. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/2023.12.txt +0 -0
  84. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/2024.04.txt +0 -0
  85. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/2024.10.txt +0 -0
  86. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/2025.06.txt +0 -0
  87. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/PREVIEW.txt +0 -0
  88. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/README.md +0 -0
  89. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/builder/base-images.json +0 -0
  90. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/call_graph.py +0 -0
  91. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/__init__.py +0 -0
  92. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/_download.py +0 -0
  93. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/_traceback.py +0 -0
  94. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/app.py +0 -0
  95. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/billing.py +0 -0
  96. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/changelog.py +0 -0
  97. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/cluster.py +0 -0
  98. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/config.py +0 -0
  99. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/container.py +0 -0
  100. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/dashboard.py +0 -0
  101. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/dict.py +0 -0
  102. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/entry_point.py +0 -0
  103. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/environment.py +0 -0
  104. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/import_refs.py +0 -0
  105. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/launch.py +0 -0
  106. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/network_file_system.py +0 -0
  107. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/profile.py +0 -0
  108. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/programs/__init__.py +0 -0
  109. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/programs/run_jupyter.py +0 -0
  110. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/programs/vscode.py +0 -0
  111. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/queues.py +0 -0
  112. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/run.py +0 -0
  113. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/secret.py +0 -0
  114. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/selector.py +0 -0
  115. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/token.py +0 -0
  116. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/utils.py +0 -0
  117. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cli/volume.py +0 -0
  118. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/client.py +0 -0
  119. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cloud_bucket_mount.py +0 -0
  120. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cloud_bucket_mount.pyi +0 -0
  121. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cls.py +0 -0
  122. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/cls.pyi +0 -0
  123. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/config.py +0 -0
  124. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/container_process.py +0 -0
  125. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/container_process.pyi +0 -0
  126. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/dict.py +0 -0
  127. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/dict.pyi +0 -0
  128. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/environments.py +0 -0
  129. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/environments.pyi +0 -0
  130. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/exception.py +0 -0
  131. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/experimental/__init__.py +0 -0
  132. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/experimental/flash.py +0 -0
  133. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/experimental/flash.pyi +0 -0
  134. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/experimental/ipython.py +0 -0
  135. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/file_io.py +0 -0
  136. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/file_io.pyi +0 -0
  137. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/file_pattern_matcher.py +0 -0
  138. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/functions.py +0 -0
  139. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/functions.pyi +0 -0
  140. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/gpu.py +0 -0
  141. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/image.py +0 -0
  142. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/image.pyi +0 -0
  143. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/io_streams.py +0 -0
  144. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/io_streams.pyi +0 -0
  145. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/mount.py +0 -0
  146. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/mount.pyi +0 -0
  147. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/network_file_system.py +0 -0
  148. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/network_file_system.pyi +0 -0
  149. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/object.py +0 -0
  150. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/object.pyi +0 -0
  151. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/output.py +0 -0
  152. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/parallel_map.py +0 -0
  153. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/parallel_map.pyi +0 -0
  154. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/partial_function.py +0 -0
  155. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/partial_function.pyi +0 -0
  156. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/proxy.py +0 -0
  157. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/proxy.pyi +0 -0
  158. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/py.typed +0 -0
  159. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/queue.py +0 -0
  160. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/queue.pyi +0 -0
  161. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/retries.py +0 -0
  162. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/runner.py +0 -0
  163. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/runner.pyi +0 -0
  164. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/running_app.py +0 -0
  165. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/sandbox_fs.py +0 -0
  166. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/sandbox_fs.pyi +0 -0
  167. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/schedule.py +0 -0
  168. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/scheduler_placement.py +0 -0
  169. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/secret.py +0 -0
  170. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/secret.pyi +0 -0
  171. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/server.py +0 -0
  172. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/server.pyi +0 -0
  173. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/serving.py +0 -0
  174. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/serving.pyi +0 -0
  175. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/snapshot.py +0 -0
  176. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/snapshot.pyi +0 -0
  177. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/stream_type.py +0 -0
  178. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/token_flow.py +0 -0
  179. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/token_flow.pyi +0 -0
  180. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/volume.py +0 -0
  181. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal/volume.pyi +0 -0
  182. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal.egg-info/SOURCES.txt +0 -0
  183. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal.egg-info/dependency_links.txt +0 -0
  184. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal.egg-info/entry_points.txt +0 -0
  185. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal.egg-info/requires.txt +0 -0
  186. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal.egg-info/top_level.txt +0 -0
  187. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/__init__.py +0 -0
  188. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/gen_cli_docs.py +0 -0
  189. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/gen_cli_docs_main.py +0 -0
  190. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/gen_reference_docs.py +0 -0
  191. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/gen_reference_docs_main.py +0 -0
  192. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/mdmd/__init__.py +0 -0
  193. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/mdmd/mdmd.py +0 -0
  194. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_docs/mdmd/signatures.py +0 -0
  195. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/__init__.py +0 -0
  196. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/api_grpc.py +0 -0
  197. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/api_pb2.py +0 -0
  198. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/api_pb2.pyi +0 -0
  199. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/api_pb2_grpc.py +0 -0
  200. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
  201. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/modal_api_grpc.py +0 -0
  202. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/py.typed +0 -0
  203. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/task_command_router_grpc.py +0 -0
  204. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2.py +0 -0
  205. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
  206. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  207. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  208. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/modal_version/__main__.py +0 -0
  209. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/pyproject.toml +0 -0
  210. {modal-1.3.6.dev9 → modal-1.3.6.dev10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.6.dev9
3
+ Version: 1.3.6.dev10
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -242,6 +242,44 @@ class TaskCommandRouterClient:
242
242
  lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
243
243
  )
244
244
 
245
+ async def container_create(self, request: sr_pb2.TaskContainerCreateRequest) -> sr_pb2.TaskContainerCreateResponse:
246
+ """Create an additional container via task command router."""
247
+ with grpc_error_converter():
248
+ return await call_with_retries_on_transient_errors(
249
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerCreate, request)
250
+ )
251
+
252
+ async def container_terminate(
253
+ self,
254
+ request: sr_pb2.TaskContainerTerminateRequest,
255
+ ) -> sr_pb2.TaskContainerTerminateResponse:
256
+ """Terminate an additional container via task command router."""
257
+ with grpc_error_converter():
258
+ return await call_with_retries_on_transient_errors(
259
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerTerminate, request)
260
+ )
261
+
262
+ async def container_wait(self, request: sr_pb2.TaskContainerWaitRequest) -> sr_pb2.TaskContainerWaitResponse:
263
+ """Wait for an additional container via task command router."""
264
+ with grpc_error_converter():
265
+ return await call_with_retries_on_transient_errors(
266
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerWait, request)
267
+ )
268
+
269
+ async def container_get(self, request: sr_pb2.TaskContainerGetRequest) -> sr_pb2.TaskContainerGetResponse:
270
+ """Get the latest tracked container for a logical name via task command router."""
271
+ with grpc_error_converter():
272
+ return await call_with_retries_on_transient_errors(
273
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerGet, request)
274
+ )
275
+
276
+ async def container_list(self, request: sr_pb2.TaskContainerListRequest) -> sr_pb2.TaskContainerListResponse:
277
+ """List sandbox containers via task command router."""
278
+ with grpc_error_converter():
279
+ return await call_with_retries_on_transient_errors(
280
+ lambda: self._call_with_auth_retry(self._stub.TaskContainerList, request)
281
+ )
282
+
245
283
  async def exec_stdio_read(
246
284
  self,
247
285
  task_id: str,
@@ -18,6 +18,7 @@ from ..mount import _Mount
18
18
  from ..runner import interactive_shell
19
19
  from ..sandbox import Sandbox
20
20
  from ..secret import Secret
21
+ from ..stream_type import StreamType
21
22
  from ..volume import Volume
22
23
  from .container import exec
23
24
  from .import_refs import (
@@ -64,26 +65,75 @@ def _is_valid_modal_id(ref: str, prefix: str) -> bool:
64
65
  return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
65
66
 
66
67
 
68
+ def _parse_sandbox_container_ref(ref: str) -> tuple[str, Optional[str]]:
69
+ """Parse a sandbox:container reference into (sandbox_id, container_name).
70
+
71
+ Supports the pattern `sb-xyz:container_name` for accessing sandbox containers.
72
+ Returns (ref, None) if no container name is specified.
73
+ """
74
+ if _is_valid_modal_id(ref, "sb-"):
75
+ return ref, None
76
+
77
+ if ":" in ref:
78
+ parts = ref.split(":", 1)
79
+ sandbox_id, container_name = parts[0], parts[1]
80
+ if _is_valid_modal_id(sandbox_id, "sb-") and container_name:
81
+ return sandbox_id, container_name
82
+
83
+ return ref, None
84
+
85
+
67
86
  def _is_running_container_ref(ref: Optional[str]) -> bool:
68
87
  if ref is None:
69
88
  return False
70
- return _is_valid_modal_id(ref, "sb-") or _is_valid_modal_id(ref, "ta-")
89
+ sandbox_id, _ = _parse_sandbox_container_ref(ref)
90
+ return _is_valid_modal_id(sandbox_id, "sb-") or _is_valid_modal_id(ref, "ta-")
91
+
92
+
93
+ def _start_shell_in_sandbox_container(sandbox_id: str, container_name: str, cmd: str, pty: bool) -> None:
94
+ """Shell into a named container within a sandbox."""
95
+ try:
96
+ sandbox = Sandbox.from_id(sandbox_id)
97
+ except NotFoundError:
98
+ raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
99
+ except Exception as e:
100
+ raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
101
+
102
+ try:
103
+ sandbox_container = sandbox._experimental_containers.get(name=container_name)
104
+ if pty:
105
+ process = sandbox_container.exec(*shlex.split(cmd), pty=pty)
106
+ process.attach()
107
+ else:
108
+ process = sandbox_container.exec(
109
+ *shlex.split(cmd), pty=pty, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
110
+ )
111
+ process.wait()
112
+ except NotFoundError:
113
+ raise ClickException(f"Container '{container_name}' not found in Sandbox '{sandbox_id}'.")
114
+ except Exception as e:
115
+ raise ClickException(f"Error exec-ing into container '{container_name}' in Sandbox '{sandbox_id}': {str(e)}")
71
116
 
72
117
 
73
118
  def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
74
- if _is_valid_modal_id(ref, "sb-"):
119
+ sandbox_id, container_name = _parse_sandbox_container_ref(ref)
120
+
121
+ if container_name is not None:
122
+ return _start_shell_in_sandbox_container(sandbox_id, container_name, cmd, pty)
123
+
124
+ if _is_valid_modal_id(sandbox_id, "sb-"):
75
125
  try:
76
- sandbox = Sandbox.from_id(ref)
126
+ sandbox = Sandbox.from_id(sandbox_id)
77
127
  ref = sandbox._get_task_id()
78
- except NotFoundError as e:
79
- raise ClickException(f"Sandbox '{ref}' not found (is it still running?)")
128
+ except NotFoundError:
129
+ raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
80
130
  except Exception as e:
81
- raise ClickException(f"Error connecting to Sandbox '{ref}': {str(e)}")
131
+ raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
82
132
 
83
133
  assert _is_valid_modal_id(ref, "ta-")
84
134
  try:
85
135
  exec(container_id=ref, command=shlex.split(cmd), pty=pty)
86
- except NotFoundError as e:
136
+ except NotFoundError:
87
137
  raise ClickException(f"Container '{ref}' not found (is it still running?)")
88
138
  except Exception as e:
89
139
  raise ClickException(f"Error connecting to container '{ref}': {str(e)}")
@@ -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.3.6.dev9",
38
+ version: str = "1.3.6.dev10",
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.3.6.dev9",
174
+ version: str = "1.3.6.dev10",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -35,7 +35,14 @@ from ._utils.name_utils import check_object_name
35
35
  from ._utils.task_command_router_client import TaskCommandRouterClient
36
36
  from .client import _Client
37
37
  from .container_process import _ContainerProcess
38
- from .exception import ClientClosed, Error, ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
38
+ from .exception import (
39
+ ClientClosed,
40
+ Error,
41
+ ExecutionError,
42
+ InvalidError,
43
+ SandboxTerminatedError,
44
+ SandboxTimeoutError,
45
+ )
39
46
  from .file_io import FileWatchEvent, FileWatchEventType, _FileIO, ls, mkdir, rm, watch
40
47
  from .gpu import GPU_T
41
48
  from .image import _Image
@@ -68,6 +75,16 @@ if TYPE_CHECKING:
68
75
  import modal.app
69
76
 
70
77
 
78
+ def _result_returncode(result: Optional[api_pb2.GenericResult]) -> Optional[int]:
79
+ if result is None or result.status == api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
80
+ return None
81
+ if result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
82
+ return 124
83
+ if result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
84
+ return 137
85
+ return result.exitcode
86
+
87
+
71
88
  def _validate_exec_args(args: Sequence[str]) -> None:
72
89
  # Entrypoint args must be strings.
73
90
  if not all(isinstance(arg, str) for arg in args):
@@ -904,6 +921,12 @@ class _Sandbox(_Object, type_prefix="sb"):
904
921
  self._command_router_client = await TaskCommandRouterClient.try_init(self._client, task_id)
905
922
  return self._command_router_client
906
923
 
924
+ @property
925
+ def _experimental_containers(self) -> "_SandboxContainerManager":
926
+ """Manage additional containers running in this Sandbox."""
927
+ self._ensure_attached()
928
+ return _SandboxContainerManager(self)
929
+
907
930
  @overload
908
931
  async def exec(
909
932
  self,
@@ -1006,6 +1029,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1006
1029
  secrets: Optional[Collection[_Secret]] = None,
1007
1030
  text: bool = True,
1008
1031
  bufsize: Literal[-1, 1] = -1,
1032
+ container_id: Optional[str] = None,
1009
1033
  ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1010
1034
  """Private method used internally.
1011
1035
 
@@ -1035,6 +1059,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1035
1059
  "text": text,
1036
1060
  "bufsize": bufsize,
1037
1061
  "runtime_debug": config.get("function_runtime_debug"),
1062
+ "container_id": container_id,
1038
1063
  }
1039
1064
  # NB: This must come after the task ID is set, since the sandbox must be
1040
1065
  # scheduled before we can create a router client.
@@ -1057,8 +1082,11 @@ class _Sandbox(_Object, type_prefix="sb"):
1057
1082
  text: bool = True,
1058
1083
  bufsize: Literal[-1, 1] = -1,
1059
1084
  runtime_debug: bool = False,
1085
+ container_id: Optional[str] = None,
1060
1086
  ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1061
1087
  """Execute a command through the Modal server."""
1088
+ if container_id:
1089
+ raise RuntimeError("Internal error: additional container exec requires task command router support")
1062
1090
  req = api_pb2.ContainerExecRequest(
1063
1091
  task_id=task_id,
1064
1092
  command=args,
@@ -1097,6 +1125,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1097
1125
  text: bool = True,
1098
1126
  bufsize: Literal[-1, 1] = -1,
1099
1127
  runtime_debug: bool = False,
1128
+ container_id: Optional[str] = None,
1100
1129
  ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1101
1130
  """Execute a command through a task command router running on the Modal worker."""
1102
1131
 
@@ -1134,6 +1163,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1134
1163
  secret_ids=secret_ids,
1135
1164
  pty_info=pty_info,
1136
1165
  runtime_debug=runtime_debug,
1166
+ container_id=container_id or "",
1137
1167
  )
1138
1168
  _ = await command_router_client.exec_start(start_req)
1139
1169
 
@@ -1332,17 +1362,7 @@ class _Sandbox(_Object, type_prefix="sb"):
1332
1362
  @property
1333
1363
  def returncode(self) -> Optional[int]:
1334
1364
  """Return code of the Sandbox process if it has finished running, else `None`."""
1335
- if self._result is None or self._result.status == api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
1336
- return None
1337
-
1338
- # Statuses are converted to exitcodes so we can conform to subprocess API.
1339
- # TODO: perhaps there should be a separate property that returns an enum directly?
1340
- elif self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
1341
- return 124
1342
- elif self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
1343
- return 137
1344
- else:
1345
- return self._result.exitcode
1365
+ return _result_returncode(self._result)
1346
1366
 
1347
1367
  @staticmethod
1348
1368
  async def list(
@@ -1382,4 +1402,224 @@ class _Sandbox(_Object, type_prefix="sb"):
1382
1402
  before_timestamp = resp.sandboxes[-1].created_at
1383
1403
 
1384
1404
 
1405
+ class _SandboxContainer:
1406
+ """Handle to an additional container running in a Sandbox."""
1407
+
1408
+ _result: Optional[api_pb2.GenericResult]
1409
+
1410
+ def __init__(
1411
+ self,
1412
+ sandbox: _Sandbox,
1413
+ container_id: str,
1414
+ container_name: str,
1415
+ result: Optional[api_pb2.GenericResult] = None,
1416
+ ) -> None:
1417
+ self._sandbox = sandbox
1418
+ self._container_id = container_id
1419
+ self._container_name = container_name
1420
+ self._result = result
1421
+
1422
+ @property
1423
+ def object_id(self) -> str:
1424
+ return self._container_id
1425
+
1426
+ @property
1427
+ def name(self) -> str:
1428
+ return self._container_name
1429
+
1430
+ @staticmethod
1431
+ def _from_container_info(sandbox: "_Sandbox", container_info: sr_pb2.TaskContainerInfo) -> "_SandboxContainer":
1432
+ result = container_info.result if container_info.HasField("result") else None
1433
+ return _SandboxContainer(sandbox, container_info.container_id, container_info.container_name, result)
1434
+
1435
+ async def _get_command_router(self) -> tuple[str, "TaskCommandRouterClient"]:
1436
+ """Get task ID and command router client, raising if unavailable."""
1437
+ task_id = await self._sandbox._get_task_id()
1438
+ command_router_client = await self._sandbox._get_command_router_client(task_id)
1439
+ if command_router_client is None:
1440
+ raise RuntimeError("Internal error: additional container operations require task command router support")
1441
+ return task_id, command_router_client
1442
+
1443
+ async def exec(
1444
+ self,
1445
+ *args: str,
1446
+ stdout: StreamType = StreamType.PIPE,
1447
+ stderr: StreamType = StreamType.PIPE,
1448
+ timeout: Optional[int] = None,
1449
+ workdir: Optional[str] = None,
1450
+ env: Optional[dict[str, Optional[str]]] = None,
1451
+ secrets: Optional[Collection[_Secret]] = None,
1452
+ text: bool = True,
1453
+ bufsize: Literal[-1, 1] = -1,
1454
+ pty: bool = False,
1455
+ ) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
1456
+ pty_info = self._sandbox._default_pty_info() if pty else None
1457
+ return await self._sandbox._exec(
1458
+ *args,
1459
+ pty_info=pty_info,
1460
+ stdout=stdout,
1461
+ stderr=stderr,
1462
+ timeout=timeout,
1463
+ workdir=workdir,
1464
+ env=env,
1465
+ secrets=secrets,
1466
+ text=text,
1467
+ bufsize=bufsize,
1468
+ container_id=self._container_id,
1469
+ )
1470
+
1471
+ async def wait(self, raise_on_termination: bool = True) -> None:
1472
+ if self._result is not None and self._result.status != api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
1473
+ if self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED and raise_on_termination:
1474
+ raise SandboxTerminatedError()
1475
+ return
1476
+
1477
+ task_id, command_router_client = await self._get_command_router()
1478
+ while True:
1479
+ resp = await command_router_client.container_wait(
1480
+ sr_pb2.TaskContainerWaitRequest(
1481
+ task_id=task_id,
1482
+ container_id=self._container_id,
1483
+ timeout=10,
1484
+ )
1485
+ )
1486
+ if resp.result.status:
1487
+ self._result = resp.result
1488
+ if resp.result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED and raise_on_termination:
1489
+ raise SandboxTerminatedError()
1490
+ return
1491
+
1492
+ async def poll(self) -> Optional[int]:
1493
+ if self._result is not None and self._result.status != api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
1494
+ return _result_returncode(self._result)
1495
+
1496
+ task_id, command_router_client = await self._get_command_router()
1497
+ resp = await command_router_client.container_wait(
1498
+ sr_pb2.TaskContainerWaitRequest(
1499
+ task_id=task_id,
1500
+ container_id=self._container_id,
1501
+ timeout=0,
1502
+ )
1503
+ )
1504
+ if resp.result.status:
1505
+ self._result = resp.result
1506
+ return _result_returncode(self._result)
1507
+
1508
+ @overload
1509
+ async def terminate(
1510
+ self,
1511
+ *,
1512
+ wait: Literal[True],
1513
+ ) -> int: ...
1514
+
1515
+ @overload
1516
+ async def terminate(
1517
+ self,
1518
+ *,
1519
+ wait: Literal[False] = False,
1520
+ ) -> None: ...
1521
+
1522
+ async def terminate(
1523
+ self,
1524
+ *,
1525
+ wait: bool = False,
1526
+ ) -> int | None:
1527
+ task_id, command_router_client = await self._get_command_router()
1528
+ await command_router_client.container_terminate(
1529
+ sr_pb2.TaskContainerTerminateRequest(
1530
+ task_id=task_id,
1531
+ container_id=self._container_id,
1532
+ )
1533
+ )
1534
+ if wait:
1535
+ await self.wait(raise_on_termination=False)
1536
+ return _result_returncode(self._result)
1537
+
1538
+
1539
+ class _SandboxContainerManager:
1540
+ """Creates and manages additional containers in a Sandbox."""
1541
+
1542
+ def __init__(self, sandbox: _Sandbox) -> None:
1543
+ self._sandbox = sandbox
1544
+
1545
+ async def _get_command_router(self) -> tuple[str, "TaskCommandRouterClient"]:
1546
+ """Get task ID and command router client, raising if unavailable."""
1547
+ task_id = await self._sandbox._get_task_id()
1548
+ command_router_client = await self._sandbox._get_command_router_client(task_id)
1549
+ if command_router_client is None:
1550
+ raise RuntimeError("Internal error: additional container operations require task command router support")
1551
+ return task_id, command_router_client
1552
+
1553
+ async def create(
1554
+ self,
1555
+ *args: str,
1556
+ name: str,
1557
+ image: _Image,
1558
+ env: Optional[dict[str, str]] = None,
1559
+ secrets: Optional[Collection[_Secret]] = None,
1560
+ workdir: Optional[str] = None,
1561
+ ) -> _SandboxContainer:
1562
+ if workdir is not None and not workdir.startswith("/"):
1563
+ raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
1564
+ _validate_exec_args(args)
1565
+
1566
+ if image._mount_layers:
1567
+ raise InvalidError(
1568
+ "Sandbox._experimental_containers.create(image=...) only supports pre-built images. "
1569
+ "When using `add_local*` methods, specify `copy=True` and call `.build()` before passing "
1570
+ "the image to `._experimental_containers.create()`:\n\nE.g.\n"
1571
+ 'img = modal.Image.debian_slim().add_local_file("foo", "/foo", copy=True).build(app)\n'
1572
+ 'sandbox._experimental_containers.create(name="worker", image=img)'
1573
+ )
1574
+ if not image._object_id:
1575
+ raise InvalidError(
1576
+ "Sandbox._experimental_containers.create(image=...) currently only supports Images that are "
1577
+ "either:\n"
1578
+ "- prebuilt using `image.build()`\n"
1579
+ "- referenced by id, e.g. `Image.from_id()`\n"
1580
+ "- filesystem/directory snapshots e.g. created by `.snapshot_directory()` "
1581
+ "or `.snapshot_filesystem()`\n"
1582
+ )
1583
+
1584
+ secrets = secrets or []
1585
+ secret_coros = [secret.hydrate(client=self._sandbox._client) for secret in secrets]
1586
+ await TaskContext.gather(*secret_coros)
1587
+
1588
+ task_id, command_router_client = await self._get_command_router()
1589
+
1590
+ create_req = sr_pb2.TaskContainerCreateRequest(
1591
+ task_id=task_id,
1592
+ container_name=name,
1593
+ image_id=image.object_id,
1594
+ args=list(args),
1595
+ env=env or {},
1596
+ workdir=workdir or "",
1597
+ secret_ids=[secret.object_id for secret in secrets],
1598
+ )
1599
+ create_resp = await command_router_client.container_create(create_req)
1600
+ container_id = create_resp.container_id
1601
+ container_name = create_resp.container_name or name
1602
+ return _SandboxContainer(self._sandbox, container_id, container_name)
1603
+
1604
+ async def get(self, *, name: str, include_terminated: bool = False) -> "_SandboxContainer":
1605
+ task_id, command_router_client = await self._get_command_router()
1606
+ resp = await command_router_client.container_get(
1607
+ sr_pb2.TaskContainerGetRequest(
1608
+ task_id=task_id,
1609
+ container_name=name,
1610
+ include_terminated=include_terminated,
1611
+ )
1612
+ )
1613
+ return _SandboxContainer._from_container_info(self._sandbox, resp.container)
1614
+
1615
+ async def list(self, include_terminated: bool = False) -> builtins.list[_SandboxContainer]:
1616
+ task_id, command_router_client = await self._get_command_router()
1617
+ resp = await command_router_client.container_list(
1618
+ sr_pb2.TaskContainerListRequest(task_id=task_id, include_terminated=include_terminated)
1619
+ )
1620
+ return [_SandboxContainer._from_container_info(self._sandbox, container) for container in resp.containers]
1621
+
1622
+
1623
+ SandboxContainer = synchronize_api(_SandboxContainer)
1624
+ SandboxContainerManager = synchronize_api(_SandboxContainerManager)
1385
1625
  Sandbox = synchronize_api(_Sandbox)