modal 1.5.5.dev5__tar.gz → 1.5.5.dev6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/PKG-INFO +1 -1
  2. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_environments.py +4 -0
  3. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/_help.py +143 -4
  4. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/app.py +51 -0
  5. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/config.py +3 -2
  6. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/entry_point.py +9 -4
  7. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/environment.py +18 -2
  8. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/profile.py +4 -4
  9. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/run.py +28 -1
  10. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/token.py +3 -11
  11. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/client.py +8 -0
  12. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/client.pyi +2 -2
  13. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/config.py +7 -0
  14. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/environments.pyi +2 -0
  15. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/experimental/__init__.py +2 -0
  16. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal.egg-info/PKG-INFO +1 -1
  17. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/api_grpc.py +16 -0
  18. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/api_pb2.py +905 -905
  19. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/api_pb2.pyi +8 -2
  20. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/api_pb2_grpc.py +33 -0
  21. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/api_pb2_grpc.pyi +10 -0
  22. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/modal_api_grpc.py +1 -0
  23. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_version/__init__.py +1 -1
  24. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/LICENSE +0 -0
  25. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/README.md +0 -0
  26. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/__init__.py +0 -0
  27. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/__main__.py +0 -0
  28. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_billing.py +0 -0
  29. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_clustered_functions.py +0 -0
  30. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_clustered_functions.pyi +0 -0
  31. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_container_entrypoint.py +0 -0
  32. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_function_variants.py +0 -0
  33. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_functions.py +0 -0
  34. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_grpc_client.py +0 -0
  35. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_image.py +0 -0
  36. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_ipython.py +0 -0
  37. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_load_context.py +0 -0
  38. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_location.py +0 -0
  39. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_logs.py +0 -0
  40. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_logs_manager.py +0 -0
  41. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_logs_manager.pyi +0 -0
  42. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_object.py +0 -0
  43. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_output/__init__.py +0 -0
  44. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_output/manager.py +0 -0
  45. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_output/pty.py +0 -0
  46. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_output/rich.py +0 -0
  47. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_output/status.py +0 -0
  48. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_partial_function.py +0 -0
  49. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_resolver.py +0 -0
  50. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_resources.py +0 -0
  51. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/__init__.py +0 -0
  52. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/asgi.py +0 -0
  53. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/container_io_manager.py +0 -0
  54. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/container_io_manager.pyi +0 -0
  55. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/execution_context.py +0 -0
  56. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/execution_context.pyi +0 -0
  57. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  58. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/task_lifecycle_manager.py +0 -0
  59. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  60. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/telemetry.py +0 -0
  61. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/user_code_event_loop.py +0 -0
  62. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_runtime/user_code_imports.py +0 -0
  63. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_serialization.py +0 -0
  64. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_server.py +0 -0
  65. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_supports_logs.py +0 -0
  66. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_traceback.py +0 -0
  67. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_tunnel.py +0 -0
  68. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_tunnel.pyi +0 -0
  69. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_type_manager.py +0 -0
  70. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/__init__.py +0 -0
  71. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/app_utils.py +0 -0
  72. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/async_utils.py +0 -0
  73. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/auth_token_manager.py +0 -0
  74. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/blob_utils.py +0 -0
  75. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/browser_utils.py +0 -0
  76. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/bytes_io_segment_payload.py +0 -0
  77. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/curl_utils.py +0 -0
  78. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/deprecation.py +0 -0
  79. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/display_utils.py +0 -0
  80. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/docker_utils.py +0 -0
  81. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/function_utils.py +0 -0
  82. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/git_utils.py +0 -0
  83. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/grpc_testing.py +0 -0
  84. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/grpc_utils.py +0 -0
  85. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/hash_utils.py +0 -0
  86. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/http_utils.py +0 -0
  87. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/jwt_utils.py +0 -0
  88. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/logger.py +0 -0
  89. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/mount_utils.py +0 -0
  90. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/name_utils.py +0 -0
  91. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/package_utils.py +0 -0
  92. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/pattern_utils.py +0 -0
  93. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/proxy_support.py +0 -0
  94. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/rand_pb_testing.py +0 -0
  95. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/sandbox_fs_utils.py +0 -0
  96. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/shell_utils.py +0 -0
  97. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/task_command_router_client.py +0 -0
  98. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_utils/time_utils.py +0 -0
  99. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_vendor/__init__.py +0 -0
  100. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  101. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_vendor/cloudpickle.py +0 -0
  102. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_vendor/tblib.py +0 -0
  103. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_vendor/version.py +0 -0
  104. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_watcher.py +0 -0
  105. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/_workspace.py +0 -0
  106. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/app.py +0 -0
  107. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/app.pyi +0 -0
  108. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/billing.py +0 -0
  109. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/billing.pyi +0 -0
  110. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/2023.12.312.txt +0 -0
  111. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/2023.12.txt +0 -0
  112. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/2024.04.txt +0 -0
  113. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/2024.10.txt +0 -0
  114. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/2025.06.txt +0 -0
  115. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/PREVIEW.txt +0 -0
  116. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/README.md +0 -0
  117. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/builder/base-images.json +0 -0
  118. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/call_graph.py +0 -0
  119. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/__init__.py +0 -0
  120. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/_download.py +0 -0
  121. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/_traceback.py +0 -0
  122. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/billing.py +0 -0
  123. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/bootstrap.py +0 -0
  124. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/changelog.py +0 -0
  125. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/cluster.py +0 -0
  126. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/container.py +0 -0
  127. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/curl.py +0 -0
  128. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/dashboard.py +0 -0
  129. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/dict.py +0 -0
  130. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/endpoint.py +0 -0
  131. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/image.py +0 -0
  132. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/import_refs.py +0 -0
  133. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/launch.py +0 -0
  134. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/logo.py +0 -0
  135. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/network_file_system.py +0 -0
  136. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/programs/__init__.py +0 -0
  137. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/programs/run_jupyter.py +0 -0
  138. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/programs/vscode.py +0 -0
  139. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/queues.py +0 -0
  140. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/secret.py +0 -0
  141. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/selector.py +0 -0
  142. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/shell.py +0 -0
  143. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/skills.py +0 -0
  144. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/utils.py +0 -0
  145. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/volume.py +0 -0
  146. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cli/workspace.py +0 -0
  147. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cloud_bucket_mount.py +0 -0
  148. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cloud_bucket_mount.pyi +0 -0
  149. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cls.py +0 -0
  150. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/cls.pyi +0 -0
  151. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/container_process.py +0 -0
  152. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/container_process.pyi +0 -0
  153. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/dict.py +0 -0
  154. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/dict.pyi +0 -0
  155. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/environments.py +0 -0
  156. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/exception.py +0 -0
  157. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/experimental/flash.py +0 -0
  158. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/experimental/flash.pyi +0 -0
  159. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/experimental/ipython.py +0 -0
  160. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/file_io.py +0 -0
  161. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/file_io.pyi +0 -0
  162. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/file_pattern_matcher.py +0 -0
  163. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/functions.py +0 -0
  164. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/functions.pyi +0 -0
  165. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/image.py +0 -0
  166. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/image.pyi +0 -0
  167. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/io_streams.py +0 -0
  168. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/io_streams.pyi +0 -0
  169. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/mount.py +0 -0
  170. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/mount.pyi +0 -0
  171. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/network_file_system.py +0 -0
  172. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/network_file_system.pyi +0 -0
  173. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/object.py +0 -0
  174. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/object.pyi +0 -0
  175. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/output.py +0 -0
  176. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/parallel_map.py +0 -0
  177. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/parallel_map.pyi +0 -0
  178. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/partial_function.py +0 -0
  179. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/partial_function.pyi +0 -0
  180. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/proxy.py +0 -0
  181. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/proxy.pyi +0 -0
  182. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/py.typed +0 -0
  183. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/queue.py +0 -0
  184. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/queue.pyi +0 -0
  185. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/retries.py +0 -0
  186. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/runner.py +0 -0
  187. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/runner.pyi +0 -0
  188. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/running_app.py +0 -0
  189. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/sandbox.py +0 -0
  190. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/sandbox.pyi +0 -0
  191. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/sandbox_fs.py +0 -0
  192. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/sandbox_fs.pyi +0 -0
  193. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/schedule.py +0 -0
  194. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/scheduler_placement.py +0 -0
  195. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/secret.py +0 -0
  196. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/secret.pyi +0 -0
  197. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/server.py +0 -0
  198. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/server.pyi +0 -0
  199. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/serving.py +0 -0
  200. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/serving.pyi +0 -0
  201. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/skills/modal/SKILL.md +0 -0
  202. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/snapshot.py +0 -0
  203. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/snapshot.pyi +0 -0
  204. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/stream_type.py +0 -0
  205. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/token_flow.py +0 -0
  206. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/token_flow.pyi +0 -0
  207. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/types.py +0 -0
  208. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/volume.py +0 -0
  209. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/volume.pyi +0 -0
  210. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/workspace.py +0 -0
  211. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal/workspace.pyi +0 -0
  212. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal.egg-info/SOURCES.txt +0 -0
  213. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal.egg-info/dependency_links.txt +0 -0
  214. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal.egg-info/entry_points.txt +0 -0
  215. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal.egg-info/requires.txt +0 -0
  216. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal.egg-info/top_level.txt +0 -0
  217. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/__init__.py +0 -0
  218. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/gen_cli_docs.py +0 -0
  219. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/gen_cli_docs_main.py +0 -0
  220. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/gen_reference_docs.py +0 -0
  221. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/gen_reference_docs_main.py +0 -0
  222. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/mdmd/__init__.py +0 -0
  223. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/mdmd/mdmd.py +0 -0
  224. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/mdmd/signatures.py +0 -0
  225. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/mdmd/types.py +0 -0
  226. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_docs/reference_intro.md +0 -0
  227. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/__init__.py +0 -0
  228. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/py.typed +0 -0
  229. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/task_command_router_grpc.py +0 -0
  230. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2.py +0 -0
  231. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2.pyi +0 -0
  232. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  233. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  234. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/modal_version/__main__.py +0 -0
  235. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/pyproject.toml +0 -0
  236. {modal-1.5.5.dev5 → modal-1.5.5.dev6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.5.dev5
3
+ Version: 1.5.5.dev6
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -39,6 +39,7 @@ class _EnvironmentManager:
39
39
  name: str, # Name to use for the new Environment
40
40
  *,
41
41
  restricted: bool = False, # If True, enable RBAC restrictions on the Environment
42
+ default_role: str | None = None, # Default member Role for the Restricted Environment
42
43
  experimental_options: dict[str, Any] | None = None, # Experimental options for Environment creation
43
44
  client: _Client | None = None, # Optional client with Modal credentials
44
45
  ) -> None:
@@ -51,8 +52,11 @@ class _EnvironmentManager:
51
52
  ```
52
53
  """
53
54
  check_environment_name(name)
55
+
54
56
  client = await _Client.from_env() if client is None else client
55
57
  request = api_pb2.EnvironmentCreateRequest(name=name, is_managed=restricted)
58
+ if default_role:
59
+ request.default_member_role_str = default_role
56
60
  if (experimental_options or {}).get("is_public", False):
57
61
  request.environment_type = api_pb2.ENVIRONMENT_TYPE_PUBLIC
58
62
  await client.stub.EnvironmentCreate(request)
@@ -7,6 +7,7 @@ import inspect
7
7
  import os
8
8
  import shutil
9
9
  import sys
10
+ from collections.abc import Sequence
10
11
  from typing import Any
11
12
 
12
13
  import click
@@ -28,6 +29,83 @@ _MAX_HELP_WIDTH = 80
28
29
  _HELP_PADDING = 1
29
30
 
30
31
 
32
+ class ModalGlobalOption(click.Option):
33
+ """An option supported at every level of the Modal CLI."""
34
+
35
+ def __init__(self, *args: Any, environment_variable: str, **kwargs: Any) -> None:
36
+ kwargs.setdefault("expose_value", False)
37
+ super().__init__(*args, **kwargs)
38
+ self.environment_variable = environment_variable
39
+
40
+ def set_environment_value(self, value: object) -> None:
41
+ os.environ[self.environment_variable] = str(value)
42
+
43
+
44
+ class ModalProfileOption(ModalGlobalOption):
45
+ """Global option that updates Modal's active-profile cache."""
46
+
47
+ def set_environment_value(self, value: object) -> None:
48
+ from modal.config import _set_profile
49
+
50
+ # The profile is a global variable and currently set when modal is imported
51
+ # We could probably clean this up and simplify to just setting MODAL_PROFILE
52
+ _set_profile(str(value))
53
+
54
+
55
+ def _root_global_options(ctx: click.Context) -> list[tuple[ModalGlobalOption, click.Context]]:
56
+ root_ctx = ctx.find_root()
57
+ return [
58
+ (param, root_ctx) for param in root_ctx.command.get_params(root_ctx) if isinstance(param, ModalGlobalOption)
59
+ ]
60
+
61
+
62
+ def _global_option_token_length(ctx: click.Context, args: list[str], index: int) -> int:
63
+ option_name = args[index].split("=", 1)[0]
64
+ for option, _ in _root_global_options(ctx):
65
+ if option_name in (*option.opts, *option.secondary_opts):
66
+ return 1 if "=" in args[index] or option.is_flag else option.nargs + 1
67
+ return 0
68
+
69
+
70
+ def _consume_global_options(ctx: click.Context, args: list[str]) -> list[str]:
71
+ remaining_args: list[str] = []
72
+ value: str | bool
73
+ index = 0
74
+ while index < len(args):
75
+ arg = args[index]
76
+ option_name, separator, option_value = arg.partition("=")
77
+ global_option = next(
78
+ (
79
+ option
80
+ for option, _ in _root_global_options(ctx)
81
+ if option_name in (*option.opts, *option.secondary_opts)
82
+ ),
83
+ None,
84
+ )
85
+ if global_option is None:
86
+ remaining_args.append(arg)
87
+ index += 1
88
+ continue
89
+
90
+ if separator:
91
+ value = option_value
92
+ index += 1
93
+ elif global_option.is_flag:
94
+ value = global_option.flag_value
95
+ if option_name in global_option.secondary_opts and isinstance(value, bool):
96
+ value = not value
97
+ index += 1
98
+ else:
99
+ if index + global_option.nargs >= len(args):
100
+ raise click.UsageError(f"Option '{option_name}' requires an argument.", ctx)
101
+ value = args[index + 1]
102
+ index += global_option.nargs + 1
103
+
104
+ global_option.set_environment_value(value)
105
+
106
+ return remaining_args
107
+
108
+
31
109
  def use_rich_style() -> bool:
32
110
  """Whether help output should be rendered in the rich style."""
33
111
  env = os.environ.get("MODAL_RICH_CLI") # TODO move to config
@@ -82,11 +160,11 @@ def _option_label(param: click.Parameter, ctx: click.Context) -> Text:
82
160
 
83
161
  def _build_options(cmd: click.Command, ctx: click.Context) -> RenderableType | None:
84
162
  rows: list[tuple[Text, str]] = []
85
- for param in cmd.get_params(ctx):
86
- rec = param.get_help_record(ctx)
163
+ for param, param_ctx in _options_with_global_options(cmd, ctx):
164
+ rec = param.get_help_record(param_ctx)
87
165
  if rec is None: # skips arguments and hidden options
88
166
  continue
89
- rows.append((_option_label(param, ctx), rec[1] or ""))
167
+ rows.append((_option_label(param, param_ctx), rec[1] or ""))
90
168
  if not rows:
91
169
  return None
92
170
 
@@ -98,6 +176,36 @@ def _build_options(cmd: click.Command, ctx: click.Context) -> RenderableType | N
98
176
  return Group(Text("Options", style=_HEADING_STYLE), table)
99
177
 
100
178
 
179
+ def _global_options(ctx: click.Context) -> Sequence[tuple[click.Option, click.Context]]:
180
+ root_ctx = ctx.find_root()
181
+ if root_ctx is ctx:
182
+ return []
183
+ return _root_global_options(ctx)
184
+
185
+
186
+ def _options_with_global_options(cmd: click.Command, ctx: click.Context) -> list[tuple[click.Parameter, click.Context]]:
187
+ params = [(param, ctx) for param in cmd.get_params(ctx)]
188
+ if global_options := _global_options(ctx):
189
+ for index, (param, _) in enumerate(params):
190
+ if "--help" in param.opts or "--help" in param.secondary_opts:
191
+ params[index:index] = global_options
192
+ break
193
+ else:
194
+ params.extend(global_options)
195
+ return params
196
+
197
+
198
+ def _format_options(cmd: click.Command, ctx: click.Context, formatter: click.HelpFormatter) -> None:
199
+ records = [
200
+ rec
201
+ for param, param_ctx in _options_with_global_options(cmd, ctx)
202
+ if (rec := param.get_help_record(param_ctx)) is not None
203
+ ]
204
+ if records:
205
+ with formatter.section("Options"):
206
+ formatter.write_dl(records)
207
+
208
+
101
209
  def _build_epilog(cmd: click.Command) -> RenderableType | None:
102
210
  if not cmd.epilog:
103
211
  return None
@@ -114,6 +222,10 @@ def group_commands_by_panel(group: click.Group) -> dict[str, list[tuple[str, cli
114
222
  return panels
115
223
 
116
224
 
225
+ def _has_visible_commands(group: click.Group) -> bool:
226
+ return bool(group_commands_by_panel(group))
227
+
228
+
117
229
  def _build_commands(group: click.Group, available_width: int) -> RenderableType | None:
118
230
  panels = group_commands_by_panel(group)
119
231
  if not panels:
@@ -189,6 +301,9 @@ class ModalCommand(click.Command):
189
301
  super().__init__(*args, **kwargs)
190
302
  self.panel = panel
191
303
 
304
+ def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
305
+ _format_options(self, ctx, formatter)
306
+
192
307
  def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
193
308
  if not use_rich_style():
194
309
  return super().format_help(ctx, formatter)
@@ -210,6 +325,7 @@ class ModalGroup(click.Group):
210
325
 
211
326
  command_class = ModalCommand
212
327
  group_class = type # nested @group.group() reuses the enclosing class
328
+ defer_global_option_parsing = False
213
329
 
214
330
  def __init__(self, *args: Any, panel: str | None = None, **kwargs: Any) -> None:
215
331
  # Default to showing help when a group is invoked with no subcommand.
@@ -232,6 +348,29 @@ class ModalGroup(click.Group):
232
348
  if hidden is not None:
233
349
  cmd.hidden = hidden
234
350
 
351
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
352
+ index = 0
353
+ while index < len(args):
354
+ arg = args[index]
355
+ if global_option_length := _global_option_token_length(ctx, args, index):
356
+ index += global_option_length
357
+ continue
358
+ command = self.commands.get(arg)
359
+ if command and getattr(command, "defer_global_option_parsing", False):
360
+ args[:] = _consume_global_options(ctx, args[:index]) + args[index:]
361
+ break
362
+ index += 1
363
+ else:
364
+ args[:] = _consume_global_options(ctx, args)
365
+
366
+ return super().parse_args(ctx, args)
367
+
368
+ def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
369
+ if _has_visible_commands(self):
370
+ self.format_commands(ctx, formatter)
371
+ else:
372
+ _format_options(self, ctx, formatter)
373
+
235
374
  def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
236
375
  # Replaces click's single flat "Commands:" section with one section per
237
376
  # panel so the simple-style help output still preserves grouping.
@@ -249,7 +388,7 @@ class ModalGroup(click.Group):
249
388
  [
250
389
  _build_usage(self, ctx),
251
390
  _build_help_text(self),
252
- _build_options(self, ctx),
391
+ None if _has_visible_commands(self) else _build_options(self, ctx),
253
392
  _build_commands(self, _available_width(console)),
254
393
  _build_epilog(self),
255
394
  ],
@@ -351,6 +351,57 @@ async def logs(
351
351
  )
352
352
 
353
353
 
354
+ @app_cli.command("promote", no_args_is_help=True, hidden=True)
355
+ @click.argument("app_identifier")
356
+ @click.argument("version")
357
+ @env_option
358
+ @synchronizer.create_blocking
359
+ async def promote(
360
+ app_identifier: str,
361
+ version: str,
362
+ *,
363
+ env: str | None = None,
364
+ ):
365
+ """Deploy a staged version from an App's deployment history.
366
+
367
+ When a staged version gets promoted, the app is deployed with a new version that
368
+ refers back to the staged version.
369
+
370
+ Examples:
371
+
372
+ Promote an App to a specific version:
373
+
374
+ ```
375
+ modal app promote my-app v5
376
+ ```
377
+
378
+ Promote an App using its App ID instead of its name:
379
+
380
+ ```
381
+ modal app promote ap-abcdefghABCDEFGH123456 v5
382
+ ```
383
+
384
+ """
385
+ if m := re.fullmatch(r"v?([1-9]\d*)", version):
386
+ version_number = int(m.group(1))
387
+ else:
388
+ raise UsageError(f"Invalid version specifier: {version}. Expected a positive version number, e.g. 'v5' or '5'.")
389
+
390
+ env = ensure_env(env)
391
+ client = await _Client.from_env()
392
+ app_id, environment_name, lifecycle = await resolve_app_identifier(app_identifier, env, client)
393
+ if lifecycle.app_state != api_pb2.APP_STATE_DEPLOYED:
394
+ env_suffix = f" in the '{environment_name}' environment" if environment_name else ""
395
+ raise InvalidError(f"App '{app_identifier}' is not deployed{env_suffix}.")
396
+
397
+ resp = await client.stub.AppPromote(api_pb2.AppPromoteRequest(app_id=app_id, version=version_number))
398
+ print_server_warnings(resp.server_warnings)
399
+
400
+ output_mgr = OutputManager.get()
401
+ output_mgr.print(f"[green]✓[/green] Promoted App to v{version_number}!")
402
+ output_mgr.print(f"\nView Deployment: [magenta]{resp.url}[/magenta]")
403
+
404
+
354
405
  @app_cli.command("rollback", no_args_is_help=True, context_settings={"ignore_unknown_options": True})
355
406
  @click.argument("app_identifier")
356
407
  @click.argument("version", default="")
@@ -3,7 +3,8 @@ import json
3
3
 
4
4
  import click
5
5
 
6
- from modal.config import _profile, _store_user_config, config
6
+ from modal import config as config_module
7
+ from modal.config import _store_user_config, config
7
8
  from modal.environments import Environment
8
9
  from modal.output import OutputManager
9
10
 
@@ -46,7 +47,7 @@ def set_environment(environment_name: str):
46
47
  # Confirm that the environment exists by looking it up
47
48
  Environment.from_name(environment_name).hydrate()
48
49
  _store_user_config({"environment": environment_name})
49
- click.echo(f"New default environment for profile {_profile}: {environment_name}")
50
+ click.echo(f"New default environment for profile {config_module._profile}: {environment_name}")
50
51
 
51
52
 
52
53
  @config_cli.command("set", hidden=True, no_args_is_help=True)
@@ -8,7 +8,7 @@ from modal._utils.async_utils import synchronizer
8
8
  from modal.output import OutputManager
9
9
 
10
10
  from . import run, shell as shell_module
11
- from ._help import ModalCommand, ModalGroup
11
+ from ._help import ModalCommand, ModalGroup, ModalProfileOption
12
12
  from .app import app_cli
13
13
  from .billing import billing_cli
14
14
  from .bootstrap import bootstrap
@@ -52,6 +52,12 @@ from .workspace import workspace_cli
52
52
  expose_value=False,
53
53
  callback=lambda ctx, param, value: _version_callback(ctx, value),
54
54
  )
55
+ @click.option(
56
+ "--profile",
57
+ cls=ModalProfileOption,
58
+ environment_variable="MODAL_PROFILE",
59
+ help="Use this Modal profile for the command.",
60
+ )
55
61
  def entrypoint_cli():
56
62
  pass
57
63
 
@@ -88,14 +94,13 @@ def check_path():
88
94
 
89
95
 
90
96
  @click.command("setup", cls=ModalCommand, help="Bootstrap Modal's configuration.")
91
- @click.option("--profile", default=None)
92
97
  @synchronizer.create_blocking
93
- async def setup(profile: str | None = None):
98
+ async def setup():
94
99
  check_path()
95
100
  print_logo()
96
101
 
97
102
  # Fetch a new token (same as `modal token new` but redirect to /home once finishes)
98
- await _new_token(profile=profile, next_url="/home")
103
+ await _new_token(next_url="/home")
99
104
 
100
105
  output = OutputManager.get()
101
106
  output.print("[green]→[/green] Run [bold]modal skills install[/bold] to install agent skills")
@@ -72,10 +72,26 @@ def list_(json: bool = False):
72
72
  @environment_cli.command("create", help="Create a new environment in the current workspace.", no_args_is_help=True)
73
73
  @click.argument("name")
74
74
  @click.option("--restricted", is_flag=True, default=False, help="Enable RBAC restrictions on the new environment")
75
+ @click.option(
76
+ "--default-role",
77
+ type=str,
78
+ default=None,
79
+ help="Default member Role for the Restricted Environment",
80
+ )
75
81
  @click.option("--public", is_flag=True, default=False, hidden=True)
76
- def create(name: str, restricted: bool = False, public: bool = False):
82
+ def create(
83
+ name: str,
84
+ restricted: bool = False,
85
+ default_role: str | None = None,
86
+ public: bool = False,
87
+ ):
77
88
  check_environment_name(name)
78
- Environment.objects.create(name, restricted=restricted, experimental_options={"is_public": public})
89
+ Environment.objects.create(
90
+ name,
91
+ restricted=restricted,
92
+ default_role=default_role,
93
+ experimental_options={"is_public": public},
94
+ )
79
95
  prefix = "Public " if public else "Restricted " if restricted else ""
80
96
  rich.print(f"[green]✓[/green] {prefix}Environment created: {name}")
81
97
 
@@ -8,11 +8,11 @@ import click
8
8
  from rich.json import JSON
9
9
  from rich.table import Table
10
10
 
11
+ from modal import config as config_module
11
12
  from modal._utils.async_utils import synchronizer
12
13
  from modal.config import (
13
14
  Config,
14
15
  _lookup_workspace,
15
- _profile,
16
16
  config_profiles,
17
17
  config_set_active_profile,
18
18
  )
@@ -41,7 +41,7 @@ def activate(profile: str):
41
41
 
42
42
  @profile_cli.command("current", help="Print the currently active Modal profile.")
43
43
  def current():
44
- click.echo(_profile)
44
+ click.echo(config_module._profile)
45
45
 
46
46
 
47
47
  @profile_cli.command("list", help="Show all Modal profiles and highlight the active one.")
@@ -62,7 +62,7 @@ async def list_(json: bool | None = False):
62
62
 
63
63
  rows = []
64
64
  for profile, resp in zip(profiles, responses):
65
- active = profile == _profile
65
+ active = profile == config_module._profile
66
66
  if isinstance(resp, AuthError):
67
67
  workspace = "Unknown (authentication failure)"
68
68
  elif isinstance(resp, TimeoutError):
@@ -80,7 +80,7 @@ async def list_(json: bool | None = False):
80
80
  if "MODAL_TOKEN_ID" in os.environ:
81
81
  try:
82
82
  env_based_resp = await _lookup_workspace(
83
- config.get("server_url", profile=_profile),
83
+ config.get("server_url", profile=config_module._profile),
84
84
  os.environ["MODAL_TOKEN_ID"],
85
85
  os.environ.get("MODAL_TOKEN_SECRET", ""),
86
86
  )
@@ -23,7 +23,7 @@ from ..functions import Function
23
23
  from ..output import OutputManager
24
24
  from ..runner import DEPLOYMENT_STRATEGY_TYPE, deploy_app, run_app
25
25
  from ..serving import serve_app
26
- from ._help import ModalCommand, ModalGroup
26
+ from ._help import ModalCommand, ModalGroup, _consume_global_options, _global_option_token_length
27
27
  from .import_refs import (
28
28
  CLICommand,
29
29
  MethodReference,
@@ -411,6 +411,33 @@ def _get_runnable_list(all_usable_commands: list[CLICommand]) -> str:
411
411
  class RunGroup(ModalGroup):
412
412
  """Click group that resolves subcommands dynamically from a file/module ref."""
413
413
 
414
+ defer_global_option_parsing = True
415
+
416
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
417
+ option_params: dict[str, click.Option] = {}
418
+ for param in self.get_params(ctx):
419
+ if isinstance(param, click.Option):
420
+ for option in (*param.opts, *param.secondary_opts):
421
+ option_params[option] = param
422
+
423
+ func_ref_index = 0
424
+ while func_ref_index < len(args):
425
+ arg = args[func_ref_index]
426
+ if global_option_length := _global_option_token_length(ctx, args, func_ref_index):
427
+ func_ref_index += global_option_length
428
+ elif arg == "--":
429
+ func_ref_index += 1
430
+ break
431
+ elif param := option_params.get(arg.split("=", 1)[0]):
432
+ func_ref_index += 1 if "=" in arg or param.is_flag else param.nargs + 1
433
+ elif arg.startswith("-"):
434
+ func_ref_index += 1
435
+ else:
436
+ break
437
+
438
+ args[:] = _consume_global_options(ctx, args[:func_ref_index]) + args[func_ref_index:]
439
+ return click.Group.parse_args(self, ctx, args)
440
+
414
441
  def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
415
442
  # note: get_command here is run before the "group logic" in the `run` logic below
416
443
  # so to ensure that `env` has been globally populated before user code is loaded, it
@@ -15,11 +15,6 @@ from ._help import ModalGroup
15
15
 
16
16
  token_cli = ModalGroup(name="token", help="Manage tokens.")
17
17
 
18
- _PROFILE_HELP = (
19
- "Modal profile to set credentials for. If unspecified "
20
- "(and MODAL_PROFILE environment variable is not set), "
21
- "uses the workspace name associated with the credentials."
22
- )
23
18
  _ACTIVATE_HELP = "Activate the profile containing this token after creation."
24
19
  _VERIFY_HELP = "Make a test request to verify the new credentials."
25
20
 
@@ -27,14 +22,12 @@ _VERIFY_HELP = "Make a test request to verify the new credentials."
27
22
  @token_cli.command("set")
28
23
  @click.option("--token-id", default=None, help="Account token ID.")
29
24
  @click.option("--token-secret", default=None, help="Account token secret.")
30
- @click.option("--profile", default=None, help=_PROFILE_HELP)
31
25
  @click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
32
26
  @click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
33
27
  @synchronizer.create_blocking
34
28
  async def set(
35
29
  token_id: str | None,
36
30
  token_secret: str | None,
37
- profile: str | None,
38
31
  activate: bool,
39
32
  verify: bool,
40
33
  ):
@@ -46,18 +39,17 @@ async def set(
46
39
  token_id = getpass.getpass("Token ID:")
47
40
  if token_secret is None:
48
41
  token_secret = getpass.getpass("Token secret:")
49
- await _set_token(token_id, token_secret, profile=profile, activate=activate, verify=verify)
42
+ await _set_token(token_id, token_secret, activate=activate, verify=verify)
50
43
 
51
44
 
52
45
  @token_cli.command("new")
53
- @click.option("--profile", default=None, help=_PROFILE_HELP)
54
46
  @click.option("--activate/--no-activate", default=True, help=_ACTIVATE_HELP)
55
47
  @click.option("--verify/--no-verify", default=True, help=_VERIFY_HELP)
56
48
  @click.option("--source", default=None, hidden=True)
57
49
  @synchronizer.create_blocking
58
- async def new(profile: str | None, activate: bool, verify: bool, source: str | None):
50
+ async def new(activate: bool, verify: bool, source: str | None):
59
51
  """Create a new token by using an authenticated web session."""
60
- await _new_token(profile=profile, activate=activate, verify=verify, source=source)
52
+ await _new_token(activate=activate, verify=verify, source=source)
61
53
 
62
54
 
63
55
  @token_cli.command("info")
@@ -17,6 +17,7 @@ from modal._utils.async_utils import synchronizer
17
17
  from modal_proto import api_pb2, modal_api_grpc
18
18
  from modal_version import __version__
19
19
 
20
+ from . import config as config_module
20
21
  from ._traceback import print_server_warnings
21
22
  from ._utils import async_utils
22
23
  from ._utils.async_utils import TaskContext, synchronize_api
@@ -240,6 +241,13 @@ class _Client:
240
241
  client_type = api_pb2.CLIENT_TYPE_CLIENT
241
242
  credentials = (token_id, token_secret)
242
243
  else:
244
+ profile = config_module._profile
245
+ if os.environ.get("MODAL_PROFILE") and profile not in config_module._user_config:
246
+ raise AuthError(
247
+ f"Modal profile '{profile}' was not found in {config_module.user_config_path}. "
248
+ "Run `modal profile list` to see available profiles, or "
249
+ f"`modal token new --profile {profile}` to configure it."
250
+ )
243
251
  raise AuthError(
244
252
  "Token missing. Could not authenticate client."
245
253
  " If you have token credentials, see modal.com/docs/sdk/py/latest/config for setup help."
@@ -38,7 +38,7 @@ class _Client:
38
38
  server_url: str,
39
39
  client_type: int,
40
40
  credentials: typing.Optional[tuple[str, str]],
41
- version: str = "1.5.5.dev5",
41
+ version: str = "1.5.5.dev6",
42
42
  ):
43
43
  """mdmd:hidden
44
44
  The Modal client object is not intended to be instantiated directly by users.
@@ -209,7 +209,7 @@ class Client:
209
209
  server_url: str,
210
210
  client_type: int,
211
211
  credentials: typing.Optional[tuple[str, str]],
212
- version: str = "1.5.5.dev5",
212
+ version: str = "1.5.5.dev6",
213
213
  ):
214
214
  """mdmd:hidden
215
215
  The Modal client object is not intended to be instantiated directly by users.
@@ -242,6 +242,13 @@ def _check_config() -> None:
242
242
 
243
243
  _profile = os.environ.get("MODAL_PROFILE") or _config_active_profile()
244
244
 
245
+
246
+ def _set_profile(profile: str) -> None:
247
+ os.environ["MODAL_PROFILE"] = profile
248
+ global _profile
249
+ _profile = profile
250
+
251
+
245
252
  # Define settings
246
253
 
247
254
 
@@ -23,6 +23,7 @@ class EnvironmentManager:
23
23
  name: str,
24
24
  *,
25
25
  restricted: bool = False,
26
+ default_role: typing.Optional[str] = None,
26
27
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
27
28
  client: typing.Optional[modal.client.Client] = None,
28
29
  ) -> None:
@@ -42,6 +43,7 @@ class EnvironmentManager:
42
43
  name: str,
43
44
  *,
44
45
  restricted: bool = False,
46
+ default_role: typing.Optional[str] = None,
45
47
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
46
48
  client: typing.Optional[modal.client.Client] = None,
47
49
  ) -> None:
@@ -105,6 +105,7 @@ async def list_deployed_apps(environment_name: str = "", client: _Client | None
105
105
  class AppLifecycle:
106
106
  """Lifecycle information about an App."""
107
107
 
108
+ version: int | None # Current version of a deployed App, or None for an ephemeral App
108
109
  created_at: datetime # Time the App was initially created
109
110
  created_by: str # User or service user name
110
111
  deployed_at: datetime | None # Time of the most recent deployment event, if ever deployed
@@ -132,6 +133,7 @@ async def get_app_lifecycle(app_id: str, *, client: _Client | None = None) -> Ap
132
133
  )
133
134
  lifecycle = resp.lifecycle
134
135
  return AppLifecycle(
136
+ version=lifecycle.version or None,
135
137
  created_at=datetime.fromtimestamp(lifecycle.created_at, timezone.utc),
136
138
  created_by=lifecycle.created_by,
137
139
  deployed_at=_timestamp_to_datetime(lifecycle.deployed_at),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.5.dev5
3
+ Version: 1.5.5.dev6
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -615,6 +615,10 @@ class ModalClientBase(abc.ABC):
615
615
  async def SandboxGetExitSnapshot(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetExitSnapshotRequest, modal_proto.api_pb2.SandboxGetExitSnapshotResponse]') -> None:
616
616
  pass
617
617
 
618
+ @abc.abstractmethod
619
+ async def SandboxGetExitSnapshotV2(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetExitSnapshotRequest, modal_proto.api_pb2.SandboxGetExitSnapshotResponse]') -> None:
620
+ pass
621
+
618
622
  @abc.abstractmethod
619
623
  async def SandboxGetFromName(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.SandboxGetFromNameRequest, modal_proto.api_pb2.SandboxGetFromNameResponse]') -> None:
620
624
  pass
@@ -1871,6 +1875,12 @@ class ModalClientBase(abc.ABC):
1871
1875
  modal_proto.api_pb2.SandboxGetExitSnapshotRequest,
1872
1876
  modal_proto.api_pb2.SandboxGetExitSnapshotResponse,
1873
1877
  ),
1878
+ '/modal.client.ModalClient/SandboxGetExitSnapshotV2': grpclib.const.Handler(
1879
+ self.SandboxGetExitSnapshotV2,
1880
+ grpclib.const.Cardinality.UNARY_UNARY,
1881
+ modal_proto.api_pb2.SandboxGetExitSnapshotRequest,
1882
+ modal_proto.api_pb2.SandboxGetExitSnapshotResponse,
1883
+ ),
1874
1884
  '/modal.client.ModalClient/SandboxGetFromName': grpclib.const.Handler(
1875
1885
  self.SandboxGetFromName,
1876
1886
  grpclib.const.Cardinality.UNARY_UNARY,
@@ -3311,6 +3321,12 @@ class ModalClientStub:
3311
3321
  modal_proto.api_pb2.SandboxGetExitSnapshotRequest,
3312
3322
  modal_proto.api_pb2.SandboxGetExitSnapshotResponse,
3313
3323
  )
3324
+ self.SandboxGetExitSnapshotV2 = grpclib.client.UnaryUnaryMethod(
3325
+ channel,
3326
+ '/modal.client.ModalClient/SandboxGetExitSnapshotV2',
3327
+ modal_proto.api_pb2.SandboxGetExitSnapshotRequest,
3328
+ modal_proto.api_pb2.SandboxGetExitSnapshotResponse,
3329
+ )
3314
3330
  self.SandboxGetFromName = grpclib.client.UnaryUnaryMethod(
3315
3331
  channel,
3316
3332
  '/modal.client.ModalClient/SandboxGetFromName',