modal 1.4.2.dev6__tar.gz → 1.4.2.dev8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/PKG-INFO +1 -1
  2. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/app.py +110 -26
  3. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/container.py +20 -2
  4. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/utils.py +10 -17
  5. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/client.pyi +2 -2
  6. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/image.py +2 -0
  7. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/image.pyi +2 -0
  8. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal.egg-info/PKG-INFO +1 -1
  9. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/api_pb2.py +340 -340
  10. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/api_pb2.pyi +12 -2
  11. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_version/__init__.py +1 -1
  12. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/LICENSE +0 -0
  13. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/README.md +0 -0
  14. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/__init__.py +0 -0
  15. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/__main__.py +0 -0
  16. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_billing.py +0 -0
  17. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_clustered_functions.py +0 -0
  18. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_clustered_functions.pyi +0 -0
  19. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_container_entrypoint.py +0 -0
  20. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_functions.py +0 -0
  21. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_grpc_client.py +0 -0
  22. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_ipython.py +0 -0
  23. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_load_context.py +0 -0
  24. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_location.py +0 -0
  25. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_logs.py +0 -0
  26. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_object.py +0 -0
  27. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_output/__init__.py +0 -0
  28. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_output/manager.py +0 -0
  29. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_output/pty.py +0 -0
  30. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_output/rich.py +0 -0
  31. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_output/status.py +0 -0
  32. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_partial_function.py +0 -0
  33. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_resolver.py +0 -0
  34. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_resources.py +0 -0
  35. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/__init__.py +0 -0
  36. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/asgi.py +0 -0
  37. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/container_io_manager.py +0 -0
  38. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/container_io_manager.pyi +0 -0
  39. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/execution_context.py +0 -0
  40. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/execution_context.pyi +0 -0
  41. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  42. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/telemetry.py +0 -0
  43. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/user_code_event_loop.py +0 -0
  44. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_runtime/user_code_imports.py +0 -0
  45. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_serialization.py +0 -0
  46. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_server.py +0 -0
  47. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_traceback.py +0 -0
  48. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_tunnel.py +0 -0
  49. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_tunnel.pyi +0 -0
  50. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_type_manager.py +0 -0
  51. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/__init__.py +0 -0
  52. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/app_utils.py +0 -0
  53. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/async_utils.py +0 -0
  54. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/auth_token_manager.py +0 -0
  55. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/blob_utils.py +0 -0
  56. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/browser_utils.py +0 -0
  57. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/bytes_io_segment_payload.py +0 -0
  58. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/deprecation.py +0 -0
  59. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/docker_utils.py +0 -0
  60. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/function_utils.py +0 -0
  61. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/git_utils.py +0 -0
  62. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/grpc_testing.py +0 -0
  63. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/grpc_utils.py +0 -0
  64. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/hash_utils.py +0 -0
  65. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/http_utils.py +0 -0
  66. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/jwt_utils.py +0 -0
  67. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/logger.py +0 -0
  68. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/mount_utils.py +0 -0
  69. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/name_utils.py +0 -0
  70. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/package_utils.py +0 -0
  71. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/pattern_utils.py +0 -0
  72. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/rand_pb_testing.py +0 -0
  73. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/sandbox_fs_utils.py +0 -0
  74. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/shell_utils.py +0 -0
  75. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/task_command_router_client.py +0 -0
  76. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_utils/time_utils.py +0 -0
  77. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_vendor/__init__.py +0 -0
  78. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  79. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_vendor/cloudpickle.py +0 -0
  80. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_vendor/tblib.py +0 -0
  81. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_vendor/version.py +0 -0
  82. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/_watcher.py +0 -0
  83. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/app.py +0 -0
  84. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/app.pyi +0 -0
  85. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/billing.py +0 -0
  86. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/2023.12.312.txt +0 -0
  87. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/2023.12.txt +0 -0
  88. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/2024.04.txt +0 -0
  89. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/2024.10.txt +0 -0
  90. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/2025.06.txt +0 -0
  91. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/PREVIEW.txt +0 -0
  92. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/README.md +0 -0
  93. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/builder/base-images.json +0 -0
  94. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/call_graph.py +0 -0
  95. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/__init__.py +0 -0
  96. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/_download.py +0 -0
  97. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/_traceback.py +0 -0
  98. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/billing.py +0 -0
  99. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/changelog.py +0 -0
  100. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/cluster.py +0 -0
  101. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/config.py +0 -0
  102. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/dashboard.py +0 -0
  103. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/dict.py +0 -0
  104. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/entry_point.py +0 -0
  105. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/environment.py +0 -0
  106. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/import_refs.py +0 -0
  107. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/launch.py +0 -0
  108. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/network_file_system.py +0 -0
  109. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/profile.py +0 -0
  110. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/programs/__init__.py +0 -0
  111. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/programs/run_jupyter.py +0 -0
  112. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/programs/vscode.py +0 -0
  113. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/queues.py +0 -0
  114. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/run.py +0 -0
  115. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/secret.py +0 -0
  116. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/selector.py +0 -0
  117. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/shell.py +0 -0
  118. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/token.py +0 -0
  119. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cli/volume.py +0 -0
  120. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/client.py +0 -0
  121. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cloud_bucket_mount.py +0 -0
  122. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cloud_bucket_mount.pyi +0 -0
  123. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cls.py +0 -0
  124. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/cls.pyi +0 -0
  125. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/config.py +0 -0
  126. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/container_process.py +0 -0
  127. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/container_process.pyi +0 -0
  128. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/dict.py +0 -0
  129. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/dict.pyi +0 -0
  130. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/environments.py +0 -0
  131. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/environments.pyi +0 -0
  132. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/exception.py +0 -0
  133. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/experimental/__init__.py +0 -0
  134. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/experimental/flash.py +0 -0
  135. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/experimental/flash.pyi +0 -0
  136. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/experimental/ipython.py +0 -0
  137. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/file_io.py +0 -0
  138. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/file_io.pyi +0 -0
  139. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/file_pattern_matcher.py +0 -0
  140. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/functions.py +0 -0
  141. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/functions.pyi +0 -0
  142. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/io_streams.py +0 -0
  143. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/io_streams.pyi +0 -0
  144. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/mount.py +0 -0
  145. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/mount.pyi +0 -0
  146. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/network_file_system.py +0 -0
  147. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/network_file_system.pyi +0 -0
  148. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/object.py +0 -0
  149. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/object.pyi +0 -0
  150. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/output.py +0 -0
  151. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/parallel_map.py +0 -0
  152. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/parallel_map.pyi +0 -0
  153. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/partial_function.py +0 -0
  154. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/partial_function.pyi +0 -0
  155. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/proxy.py +0 -0
  156. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/proxy.pyi +0 -0
  157. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/py.typed +0 -0
  158. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/queue.py +0 -0
  159. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/queue.pyi +0 -0
  160. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/retries.py +0 -0
  161. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/runner.py +0 -0
  162. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/runner.pyi +0 -0
  163. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/running_app.py +0 -0
  164. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/sandbox.py +0 -0
  165. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/sandbox.pyi +0 -0
  166. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/sandbox_fs.py +0 -0
  167. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/sandbox_fs.pyi +0 -0
  168. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/schedule.py +0 -0
  169. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/scheduler_placement.py +0 -0
  170. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/secret.py +0 -0
  171. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/secret.pyi +0 -0
  172. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/server.py +0 -0
  173. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/server.pyi +0 -0
  174. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/serving.py +0 -0
  175. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/serving.pyi +0 -0
  176. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/snapshot.py +0 -0
  177. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/snapshot.pyi +0 -0
  178. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/stream_type.py +0 -0
  179. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/token_flow.py +0 -0
  180. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/token_flow.pyi +0 -0
  181. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/volume.py +0 -0
  182. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal/volume.pyi +0 -0
  183. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal.egg-info/SOURCES.txt +0 -0
  184. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal.egg-info/dependency_links.txt +0 -0
  185. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal.egg-info/entry_points.txt +0 -0
  186. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal.egg-info/requires.txt +0 -0
  187. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal.egg-info/top_level.txt +0 -0
  188. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/__init__.py +0 -0
  189. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/gen_cli_docs.py +0 -0
  190. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/gen_cli_docs_main.py +0 -0
  191. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/gen_reference_docs.py +0 -0
  192. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/gen_reference_docs_main.py +0 -0
  193. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/mdmd/__init__.py +0 -0
  194. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/mdmd/mdmd.py +0 -0
  195. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_docs/mdmd/signatures.py +0 -0
  196. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/__init__.py +0 -0
  197. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/api_grpc.py +0 -0
  198. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/api_pb2_grpc.py +0 -0
  199. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/api_pb2_grpc.pyi +0 -0
  200. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/modal_api_grpc.py +0 -0
  201. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/py.typed +0 -0
  202. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/task_command_router_grpc.py +0 -0
  203. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/task_command_router_pb2.py +0 -0
  204. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/task_command_router_pb2.pyi +0 -0
  205. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  206. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  207. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/modal_version/__main__.py +0 -0
  208. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/pyproject.toml +0 -0
  209. {modal-1.4.2.dev6 → modal-1.4.2.dev8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.2.dev6
3
+ Version: 1.4.2.dev8
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -20,13 +20,22 @@ from modal._utils.async_utils import synchronizer
20
20
  from modal._utils.browser_utils import open_url_and_display
21
21
  from modal.client import _Client
22
22
  from modal.environments import ensure_env
23
+ from modal.exception import InvalidError, NotFoundError
23
24
  from modal.output import OutputManager
24
25
  from modal.runner import DEPLOYMENT_STRATEGY_TYPE, _stop_and_wait_for_containers
25
26
  from modal_proto import api_pb2
26
27
 
27
28
  from .._logs import _FETCH_LIMIT, _MAX_FETCH_RANGE, LogsFilters
28
29
  from .._utils.time_utils import locale_tz, timestamp_to_localized_str
29
- from .utils import ENV_OPTION, display_table, fetch_app_logs, get_app_id_from_name, stream_app_logs, tail_app_logs
30
+ from .utils import (
31
+ ENV_OPTION,
32
+ YES_OPTION,
33
+ confirm_or_suggest_yes,
34
+ display_table,
35
+ fetch_app_logs,
36
+ stream_app_logs,
37
+ tail_app_logs,
38
+ )
30
39
 
31
40
  APP_IDENTIFIER = Argument("", help="App name or ID")
32
41
  NAME_OPTION = typer.Option("", "-n", "--name", help="Deprecated: Pass App name as a positional argument")
@@ -45,12 +54,50 @@ APP_STATE_TO_MESSAGE = {
45
54
  }
46
55
 
47
56
 
48
- @synchronizer.create_blocking
49
- async def get_app_id(app_identifier: str, env: Optional[str], client: Optional[_Client] = None) -> str:
50
- """Resolve an app_identifier that may be a name or an ID into an ID."""
57
+ async def resolve_app_identifier(
58
+ app_identifier: str, env: Optional[str], client: Optional[_Client] = None
59
+ ) -> tuple[str, str, api_pb2.AppLifecycle]: # Return app_id, environment_name, lifecycle
60
+ """Handle an App ID or an App name and return context about the App it points at.
61
+
62
+ When a name is provided, we may retrieve either a currently deployed App or an App that
63
+ was recently stopped (if no other App with that name has been deployed since).
64
+ It is up to callers of this function to decide whether it's valid to use the App ID
65
+ based on the lifecycle returned and their specific operations.
66
+
67
+ Can also raise a NotFoundError if the argument matches the App ID regex but the App
68
+ doesn't exist on the backend, or if there is no currently deployed or recently stopped App
69
+ with that name.
70
+
71
+ The function also always returns a valid environment name for any name-based lookups,
72
+ which may reflect the server-defined default environment when the provided argument was null.
73
+
74
+ """
75
+ if client is None:
76
+ client = await _Client.from_env()
51
77
  if re.match(r"^ap-[a-zA-Z0-9]{22}$", app_identifier):
52
- return app_identifier
53
- return await get_app_id_from_name.aio(app_identifier, env, client)
78
+ # Identifier is an App ID. This is unambiguous, so we can make the request and return
79
+ # the lifecycle. AppGetLifecycle will raise NotFoundError if the ID doesn't point at an App.
80
+ # If we return, it's a real App, but it's up to the caller to decide what to do based on
81
+ # the App's current state as conveyed by the lifecycle. We do propagate a NotFoundError
82
+ # from the server if the App ID doesn't actually exist.
83
+ request = api_pb2.AppGetLifecycleRequest(app_id=app_identifier)
84
+ resp = await client.stub.AppGetLifecycle(request)
85
+ return app_identifier, "", resp.lifecycle
86
+ else:
87
+ # Identifier is treated as a name, which may or may not point at a currently deployed App
88
+ # (inside a specific environment)
89
+ request = api_pb2.AppGetByDeploymentNameRequest(name=app_identifier, environment_name=env or "")
90
+ resp = await client.stub.AppGetByDeploymentName(request)
91
+ if resp.app_id:
92
+ # App is currently deployed
93
+ return resp.app_id, resp.environment_name, resp.lifecycle
94
+ elif resp.previous_app_id:
95
+ # An App with this name was recently stopped. Return the ID of the stopped App
96
+ # and let callers decide what to do based on the lifecycle.
97
+ return resp.previous_app_id, resp.environment_name, resp.lifecycle
98
+ else:
99
+ msg = f"No App with name '{app_identifier}' found in the '{resp.environment_name}' environment."
100
+ raise NotFoundError(msg)
54
101
 
55
102
 
56
103
  @app_cli.command("list")
@@ -129,7 +176,8 @@ _SOURCE_OPTIONS = {
129
176
 
130
177
 
131
178
  @app_cli.command("logs", no_args_is_help=True)
132
- def logs(
179
+ @synchronizer.create_blocking
180
+ async def logs(
133
181
  app_identifier: str = APP_IDENTIFIER,
134
182
  follow: bool = typer.Option(False, "-f", "--follow", help="Stream log output until App stops"),
135
183
  since: Optional[str] = typer.Option(
@@ -229,7 +277,7 @@ def logs(
229
277
  if tail is not None and tail > _FETCH_LIMIT:
230
278
  raise UsageError(f"--tail value must not exceed {_FETCH_LIMIT}.")
231
279
 
232
- app_id = get_app_id(app_identifier, env)
280
+ app_id, _, _ = await resolve_app_identifier(app_identifier, env)
233
281
 
234
282
  if source is not None:
235
283
  if source not in _SOURCE_OPTIONS:
@@ -255,7 +303,7 @@ def logs(
255
303
  )
256
304
 
257
305
  if follow:
258
- stream_app_logs(
306
+ await stream_app_logs.aio(
259
307
  app_id,
260
308
  task_id=container_id or "",
261
309
  show_timestamps=timestamps,
@@ -278,7 +326,7 @@ def logs(
278
326
 
279
327
  if since and tail is None:
280
328
  # Range mode: --since without --tail fetches everything in the range.
281
- fetch_app_logs(
329
+ await fetch_app_logs.aio(
282
330
  app_id,
283
331
  since_dt,
284
332
  until_dt or now,
@@ -290,7 +338,7 @@ def logs(
290
338
  # Tail mode: single fetch with limit.
291
339
  # --since is a hard floor, --until shifts the anchor.
292
340
  effective_tail = tail if tail is not None else _DEFAULT_LOGS_TAIL
293
- tail_app_logs(
341
+ await tail_app_logs.aio(
294
342
  app_id,
295
343
  effective_tail,
296
344
  show_timestamps=timestamps,
@@ -338,14 +386,18 @@ async def rollback(
338
386
  """
339
387
  env = ensure_env(env)
340
388
  client = await _Client.from_env()
341
- app_id = await get_app_id.aio(app_identifier, env, client)
389
+ app_id, environment_name, lifecycle = await resolve_app_identifier(app_identifier, env, client)
390
+ if lifecycle.app_state != api_pb2.APP_STATE_DEPLOYED:
391
+ env_suffix = f" in the '{environment_name}' environment" if environment_name else ""
392
+ raise InvalidError(f"App '{app_identifier}' is not deployed{env_suffix}.")
393
+
342
394
  if not version:
343
395
  version_number = -1
344
396
  else:
345
397
  if m := re.match(r"v(\d+)", version):
346
398
  version_number = int(m.group(1))
347
399
  else:
348
- raise UsageError(f"Invalid version specifer: {version}")
400
+ raise UsageError(f"Invalid version specifier: {version}")
349
401
  req = api_pb2.AppRollbackRequest(app_id=app_id, version=version_number)
350
402
  await client.stub.AppRollback(req)
351
403
  rich.print("[green]✓[/green] Deployment rollback successful!")
@@ -363,7 +415,7 @@ async def rollover(
363
415
  ),
364
416
  env: Optional[str] = ENV_OPTION,
365
417
  ):
366
- """Rollover an App.
418
+ """Redeploy an App to get new containers without code changes.
367
419
 
368
420
  A rollover replaces existing containers with fresh ones built from the same
369
421
  App version — useful for refreshing containers without changing your code.
@@ -372,13 +424,13 @@ async def rollover(
372
424
  **Examples:**
373
425
 
374
426
  Rollover an App using a rolling deployment. Running containers are now considered
375
- outdated and new containers will replace them.
427
+ outdated and will be gracefully replaced by new ones.
376
428
 
377
429
  ```
378
430
  modal app rollover my-app
379
431
  ```
380
432
 
381
- Rollover an App by termatining all running containers. Inputs on the queue will
433
+ Rollover an App by terminating any running containers. Inputs on the queue will
382
434
  start new containers.
383
435
 
384
436
  ```
@@ -386,13 +438,17 @@ async def rollover(
386
438
  ```
387
439
  """
388
440
  env = ensure_env(env)
441
+ client = await _Client.from_env()
442
+
443
+ app_id, environment_name, lifecycle = await resolve_app_identifier(app_identifier, env, client)
444
+ if lifecycle.app_state != api_pb2.APP_STATE_DEPLOYED:
445
+ env_suffix = f" in the '{environment_name}' environment" if environment_name else ""
446
+ raise InvalidError(f"App '{app_identifier}' is not deployed{env_suffix}.")
447
+
389
448
  output_mgr = OutputManager.get()
390
449
  output_mgr.print(f"🔨 Starting app rollover with {strategy} strategy")
391
450
  t0 = time.monotonic()
392
451
 
393
- client = await _Client.from_env()
394
- app_id = await get_app_id.aio(app_identifier, env, client)
395
-
396
452
  req = api_pb2.AppRolloverRequest(app_id=app_id)
397
453
  response = await client.stub.AppRollover(req)
398
454
  print_server_warnings(response.server_warnings)
@@ -415,11 +471,40 @@ async def rollover(
415
471
  async def stop(
416
472
  app_identifier: str = APP_IDENTIFIER,
417
473
  *,
474
+ yes: bool = YES_OPTION,
418
475
  env: Optional[str] = ENV_OPTION,
419
476
  ):
420
- """Stop an app."""
477
+ """Permanently stop an App and terminate its running containers."""
478
+ env = ensure_env(env)
421
479
  client = await _Client.from_env()
422
- app_id = await get_app_id.aio(app_identifier, env)
480
+ app_id, environment_name, lifecycle = await resolve_app_identifier(app_identifier, env, client)
481
+
482
+ if lifecycle.app_state == api_pb2.APP_STATE_STOPPED:
483
+ msg = "App is already stopped."
484
+ if lifecycle.stopped_at:
485
+ stopped_at = timestamp_to_localized_str(lifecycle.stopped_at)
486
+ verb = "Stopped" if lifecycle.stopped_by else "Finished"
487
+ attribution = f" by '{lifecycle.stopped_by}'" if lifecycle.stopped_by else ""
488
+ msg += f" ({verb} at {stopped_at}{attribution})."
489
+ raise SystemExit(msg)
490
+
491
+ if not yes:
492
+ res = await client.stub.TaskList(api_pb2.TaskListRequest(app_id=app_id))
493
+ num_containers = len(res.tasks)
494
+
495
+ if environment_name:
496
+ msg = f"Are you sure you want to stop App '{app_identifier}' in the '{environment_name}' environment?"
497
+ else:
498
+ msg = f"Are you sure you want to stop App '{app_identifier}'?"
499
+
500
+ if num_containers:
501
+ msg += (
502
+ f" This will immediately terminate {num_containers} running"
503
+ f" container{'s' if num_containers != 1 else ''}."
504
+ )
505
+ else:
506
+ msg += " No containers are currently running."
507
+ confirm_or_suggest_yes(msg)
423
508
  req = api_pb2.AppStopRequest(app_id=app_id, source=api_pb2.APP_STOP_SOURCE_CLI)
424
509
  await client.stub.AppStop(req)
425
510
 
@@ -432,7 +517,7 @@ async def history(
432
517
  env: Optional[str] = ENV_OPTION,
433
518
  json: bool = False,
434
519
  ):
435
- """Show App deployment history, for a currently deployed app
520
+ """Show an App's deployment history.
436
521
 
437
522
  **Examples:**
438
523
 
@@ -442,7 +527,7 @@ async def history(
442
527
  modal app history ap-123456
443
528
  ```
444
529
 
445
- Get the history for a currently deployed App based on its name:
530
+ Get the history for an App based on its name:
446
531
 
447
532
  ```
448
533
  modal app history my-app
@@ -451,7 +536,7 @@ async def history(
451
536
  """
452
537
  env = ensure_env(env)
453
538
  client = await _Client.from_env()
454
- app_id = await get_app_id.aio(app_identifier, env, client)
539
+ app_id, _, _ = await resolve_app_identifier(app_identifier, env, client)
455
540
  resp = await client.stub.AppDeploymentHistory(api_pb2.AppDeploymentHistoryRequest(app_id=app_id))
456
541
 
457
542
  columns = [
@@ -526,7 +611,6 @@ async def dashboard(
526
611
  ```
527
612
  """
528
613
  client = await _Client.from_env()
529
- app_id = await get_app_id.aio(app_identifier, env, client)
530
-
614
+ app_id, _, _ = await resolve_app_identifier(app_identifier, env, client)
531
615
  url = f"https://modal.com/id/{app_id}"
532
616
  open_url_and_display(url, "App dashboard")
@@ -14,7 +14,16 @@ from modal._output.pty import get_pty_info
14
14
  from modal._utils.async_utils import synchronizer
15
15
  from modal._utils.time_utils import timestamp_to_localized_str
16
16
  from modal.cli.app import _DEFAULT_LOGS_TAIL, _SOURCE_OPTIONS, _parse_time_arg
17
- from modal.cli.utils import ENV_OPTION, display_table, fetch_app_logs, is_tty, stream_app_logs, tail_app_logs
17
+ from modal.cli.utils import (
18
+ ENV_OPTION,
19
+ YES_OPTION,
20
+ confirm_or_suggest_yes,
21
+ display_table,
22
+ fetch_app_logs,
23
+ is_tty,
24
+ stream_app_logs,
25
+ tail_app_logs,
26
+ )
18
27
  from modal.client import _Client
19
28
  from modal.config import config
20
29
  from modal.container_process import _ContainerProcess
@@ -285,11 +294,20 @@ async def exec(
285
294
 
286
295
  @container_cli.command("stop")
287
296
  @synchronizer.create_blocking
288
- async def stop(container_id: str = typer.Argument(help="Container ID")):
297
+ async def stop(
298
+ container_id: str = typer.Argument(help="Container ID"),
299
+ *,
300
+ yes: bool = YES_OPTION,
301
+ ):
289
302
  """Stop a currently-running container and reassign its in-progress inputs.
290
303
 
291
304
  This will send the container a SIGINT signal that Modal will handle.
292
305
  """
293
306
  client = await _Client.from_env()
307
+ resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=container_id))
308
+ if resp.info.finished_at:
309
+ raise SystemExit(f"Container '{container_id}' is already stopped.")
310
+ if not yes:
311
+ confirm_or_suggest_yes(f"Are you sure you want to stop container '{container_id}'?")
294
312
  request = api_pb2.ContainerStopRequest(task_id=container_id)
295
313
  await client.stub.ContainerStop(request)
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import asyncio
3
3
  import io
4
+ import sys
4
5
  from collections.abc import Sequence
5
6
  from contextlib import nullcontext
6
7
  from csv import writer as csv_writer
@@ -12,14 +13,11 @@ import typer
12
13
  from rich.table import Column, Table
13
14
  from rich.text import Text
14
15
 
15
- from modal_proto import api_pb2
16
-
17
16
  from .._logs import LogsFilters, fetch_logs, tail_logs
18
17
  from .._output.pty import _build_log_prefix, get_app_logs_loop
19
18
  from .._utils.async_utils import synchronizer
20
19
  from ..client import _Client
21
- from ..environments import ensure_env
22
- from ..exception import InvalidError, NotFoundError
20
+ from ..exception import InvalidError
23
21
  from ..output import OutputManager
24
22
 
25
23
 
@@ -125,19 +123,6 @@ async def fetch_app_logs(
125
123
  await _drain_batches(output_mgr, batches, prefix_fields or [], filters.search_text)
126
124
 
127
125
 
128
- @synchronizer.create_blocking
129
- async def get_app_id_from_name(name: str, env: Optional[str], client: Optional[_Client] = None) -> str:
130
- if client is None:
131
- client = await _Client.from_env()
132
- env_name = ensure_env(env)
133
- request = api_pb2.AppGetByDeploymentNameRequest(name=name, environment_name=env_name)
134
- resp = await client.stub.AppGetByDeploymentName(request)
135
- if not resp.app_id:
136
- env_comment = f" in the '{env_name}' environment" if env_name else ""
137
- raise NotFoundError(f"Could not find a deployed app named '{name}'{env_comment}.")
138
- return resp.app_id
139
-
140
-
141
126
  def _plain(text: Union[Text, str]) -> str:
142
127
  return text.plain if isinstance(text, Text) else text
143
128
 
@@ -185,3 +170,11 @@ Otherwise, raises an error if the workspace has multiple environments.
185
170
  ENV_OPTION = typer.Option(None, "-e", "--env", help=ENV_OPTION_HELP)
186
171
 
187
172
  YES_OPTION = typer.Option(False, "-y", "--yes", help="Run without pausing for confirmation.")
173
+
174
+
175
+ def confirm_or_suggest_yes(msg: str) -> None:
176
+ """Prompt for confirmation, or abort with a hint to use --yes if stdin is not a TTY."""
177
+ if not sys.stdin.isatty():
178
+ typer.echo(f"{msg} [y/N]: ")
179
+ raise SystemExit("Aborted: no interactive terminal detected. Rerun with --yes (-y) to skip confirmation.")
180
+ typer.confirm(msg, default=False, abort=True)
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.4.2.dev6",
38
+ version: str = "1.4.2.dev8",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -171,7 +171,7 @@ class Client:
171
171
  server_url: str,
172
172
  client_type: int,
173
173
  credentials: typing.Optional[tuple[str, str]],
174
- version: str = "1.4.2.dev6",
174
+ version: str = "1.4.2.dev8",
175
175
  ):
176
176
  """mdmd:hidden
177
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -1644,6 +1644,7 @@ class _Image(_Object, type_prefix="im"):
1644
1644
  context_dir: Optional[Union[Path, str]] = None, # Context for relative COPY commands
1645
1645
  force_build: bool = False, # Ignore cached builds, similar to 'docker build --no-cache'
1646
1646
  ignore: Union[Sequence[str], Callable[[Path], bool]] = AUTO_DOCKERIGNORE,
1647
+ build_args: dict[str, str] = {}, # Dockerfile variables to set
1647
1648
  ) -> "_Image":
1648
1649
  """
1649
1650
  Extend an image with arbitrary Dockerfile-like commands.
@@ -1706,6 +1707,7 @@ class _Image(_Object, type_prefix="im"):
1706
1707
  ignore=ignore, dockerfile_cmds=cmds, context_dir=context_dir
1707
1708
  ),
1708
1709
  force_build=self.force_build or force_build,
1710
+ build_args=build_args,
1709
1711
  )
1710
1712
 
1711
1713
  def entrypoint(
@@ -623,6 +623,7 @@ class _Image(modal._object._Object):
623
623
  ignore: typing.Union[
624
624
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
625
625
  ] = modal.image.AUTO_DOCKERIGNORE,
626
+ build_args: dict[str, str] = {},
626
627
  ) -> _Image:
627
628
  """Extend an image with arbitrary Dockerfile-like commands.
628
629
 
@@ -1619,6 +1620,7 @@ class Image(modal.object.Object):
1619
1620
  ignore: typing.Union[
1620
1621
  collections.abc.Sequence[str], collections.abc.Callable[[pathlib.Path], bool]
1621
1622
  ] = modal.image.AUTO_DOCKERIGNORE,
1623
+ build_args: dict[str, str] = {},
1622
1624
  ) -> Image:
1623
1625
  """Extend an image with arbitrary Dockerfile-like commands.
1624
1626
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.2.dev6
3
+ Version: 1.4.2.dev8
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0