modal 1.5.2.dev14__tar.gz → 1.5.2.dev15__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 (232) hide show
  1. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/PKG-INFO +1 -1
  2. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/__init__.py +2 -1
  3. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_billing.py +2 -53
  4. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_environments.py +1 -1
  5. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_functions.py +1 -11
  6. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_image.py +1 -1
  7. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_workspace.py +2 -42
  8. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/billing.py +2 -1
  9. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/billing.pyi +27 -28
  10. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/call_graph.py +1 -30
  11. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/_download.py +2 -1
  12. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/cluster.py +1 -1
  13. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/config.py +2 -2
  14. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/container.py +2 -2
  15. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/curl.py +6 -1
  16. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/dict.py +5 -5
  17. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/environment.py +3 -3
  18. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/image.py +7 -7
  19. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/network_file_system.py +12 -6
  20. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/profile.py +1 -1
  21. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/queues.py +5 -5
  22. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/secret.py +2 -2
  23. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/volume.py +17 -9
  24. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/client.pyi +2 -2
  25. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/dict.py +1 -13
  26. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/dict.pyi +4 -24
  27. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/environments.pyi +3 -3
  28. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/file_io.py +1 -1
  29. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/file_io.pyi +9 -9
  30. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/functions.pyi +5 -5
  31. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/network_file_system.pyi +7 -7
  32. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/queue.py +1 -13
  33. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/queue.pyi +4 -24
  34. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/sandbox.py +10 -23
  35. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/sandbox.pyi +10 -39
  36. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/sandbox_fs.py +1 -64
  37. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/sandbox_fs.pyi +14 -117
  38. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/secret.py +1 -13
  39. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/secret.pyi +4 -24
  40. modal-1.5.2.dev15/modal/types.py +286 -0
  41. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/volume.py +1 -43
  42. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/volume.pyi +10 -75
  43. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/workspace.pyi +9 -10
  44. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal.egg-info/PKG-INFO +1 -1
  45. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal.egg-info/SOURCES.txt +1 -0
  46. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/gen_cli_docs.py +22 -7
  47. modal-1.5.2.dev15/modal_docs/gen_reference_docs.py +361 -0
  48. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/mdmd/mdmd.py +91 -9
  49. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/mdmd/signatures.py +28 -0
  50. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/api_pb2.py +857 -851
  51. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/api_pb2.pyi +37 -9
  52. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_version/__init__.py +1 -1
  53. modal-1.5.2.dev14/modal_docs/gen_reference_docs.py +0 -177
  54. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/LICENSE +0 -0
  55. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/README.md +0 -0
  56. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/__main__.py +0 -0
  57. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_clustered_functions.py +0 -0
  58. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_clustered_functions.pyi +0 -0
  59. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_container_entrypoint.py +0 -0
  60. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_function_variants.py +0 -0
  61. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_grpc_client.py +0 -0
  62. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_ipython.py +0 -0
  63. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_load_context.py +0 -0
  64. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_location.py +0 -0
  65. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_logs.py +0 -0
  66. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_object.py +0 -0
  67. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_output/__init__.py +0 -0
  68. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_output/manager.py +0 -0
  69. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_output/pty.py +0 -0
  70. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_output/rich.py +0 -0
  71. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_output/status.py +0 -0
  72. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_partial_function.py +0 -0
  73. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_resolver.py +0 -0
  74. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_resources.py +0 -0
  75. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/__init__.py +0 -0
  76. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/asgi.py +0 -0
  77. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/container_io_manager.py +0 -0
  78. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/container_io_manager.pyi +0 -0
  79. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/execution_context.py +0 -0
  80. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/execution_context.pyi +0 -0
  81. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  82. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/task_lifecycle_manager.py +0 -0
  83. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  84. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/telemetry.py +0 -0
  85. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/user_code_event_loop.py +0 -0
  86. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_runtime/user_code_imports.py +0 -0
  87. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_serialization.py +0 -0
  88. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_server.py +0 -0
  89. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_traceback.py +0 -0
  90. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_tunnel.py +0 -0
  91. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_tunnel.pyi +0 -0
  92. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_type_manager.py +0 -0
  93. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/__init__.py +0 -0
  94. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/app_utils.py +0 -0
  95. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/async_utils.py +0 -0
  96. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/auth_token_manager.py +0 -0
  97. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/blob_utils.py +0 -0
  98. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/browser_utils.py +0 -0
  99. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/bytes_io_segment_payload.py +0 -0
  100. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/curl_utils.py +0 -0
  101. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/deprecation.py +0 -0
  102. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/docker_utils.py +0 -0
  103. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/function_utils.py +0 -0
  104. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/git_utils.py +0 -0
  105. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/grpc_testing.py +0 -0
  106. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/grpc_utils.py +0 -0
  107. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/hash_utils.py +0 -0
  108. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/http_utils.py +0 -0
  109. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/jwt_utils.py +0 -0
  110. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/logger.py +0 -0
  111. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/mount_utils.py +0 -0
  112. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/name_utils.py +0 -0
  113. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/package_utils.py +0 -0
  114. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/pattern_utils.py +0 -0
  115. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/proxy_support.py +0 -0
  116. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/rand_pb_testing.py +0 -0
  117. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/sandbox_fs_utils.py +0 -0
  118. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/shell_utils.py +0 -0
  119. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/task_command_router_client.py +0 -0
  120. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_utils/time_utils.py +0 -0
  121. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_vendor/__init__.py +0 -0
  122. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  123. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_vendor/cloudpickle.py +0 -0
  124. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_vendor/tblib.py +0 -0
  125. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_vendor/version.py +0 -0
  126. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/_watcher.py +0 -0
  127. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/app.py +0 -0
  128. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/app.pyi +0 -0
  129. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/2023.12.312.txt +0 -0
  130. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/2023.12.txt +0 -0
  131. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/2024.04.txt +0 -0
  132. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/2024.10.txt +0 -0
  133. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/2025.06.txt +0 -0
  134. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/PREVIEW.txt +0 -0
  135. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/README.md +0 -0
  136. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/builder/base-images.json +0 -0
  137. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/__init__.py +0 -0
  138. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/_help.py +0 -0
  139. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/_traceback.py +0 -0
  140. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/app.py +0 -0
  141. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/billing.py +0 -0
  142. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/bootstrap.py +0 -0
  143. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/changelog.py +0 -0
  144. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/dashboard.py +0 -0
  145. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/endpoint.py +0 -0
  146. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/entry_point.py +0 -0
  147. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/import_refs.py +0 -0
  148. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/launch.py +0 -0
  149. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/logo.py +0 -0
  150. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/programs/__init__.py +0 -0
  151. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/programs/run_jupyter.py +0 -0
  152. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/programs/vscode.py +0 -0
  153. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/run.py +0 -0
  154. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/selector.py +0 -0
  155. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/shell.py +0 -0
  156. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/skills.py +0 -0
  157. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/token.py +0 -0
  158. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/utils.py +0 -0
  159. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cli/workspace.py +0 -0
  160. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/client.py +0 -0
  161. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cloud_bucket_mount.py +0 -0
  162. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cloud_bucket_mount.pyi +0 -0
  163. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cls.py +0 -0
  164. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/cls.pyi +0 -0
  165. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/config.py +0 -0
  166. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/container_process.py +0 -0
  167. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/container_process.pyi +0 -0
  168. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/environments.py +0 -0
  169. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/exception.py +0 -0
  170. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/experimental/__init__.py +0 -0
  171. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/experimental/flash.py +0 -0
  172. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/experimental/flash.pyi +0 -0
  173. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/experimental/ipython.py +0 -0
  174. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/file_pattern_matcher.py +0 -0
  175. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/functions.py +0 -0
  176. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/image.py +0 -0
  177. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/image.pyi +0 -0
  178. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/io_streams.py +0 -0
  179. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/io_streams.pyi +0 -0
  180. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/mount.py +0 -0
  181. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/mount.pyi +0 -0
  182. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/network_file_system.py +0 -0
  183. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/object.py +0 -0
  184. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/object.pyi +0 -0
  185. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/output.py +0 -0
  186. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/parallel_map.py +0 -0
  187. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/parallel_map.pyi +0 -0
  188. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/partial_function.py +0 -0
  189. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/partial_function.pyi +0 -0
  190. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/proxy.py +0 -0
  191. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/proxy.pyi +0 -0
  192. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/py.typed +0 -0
  193. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/retries.py +0 -0
  194. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/runner.py +0 -0
  195. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/runner.pyi +0 -0
  196. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/running_app.py +0 -0
  197. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/schedule.py +0 -0
  198. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/scheduler_placement.py +0 -0
  199. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/server.py +0 -0
  200. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/server.pyi +0 -0
  201. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/serving.py +0 -0
  202. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/serving.pyi +0 -0
  203. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/skills/modal/SKILL.md +0 -0
  204. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/snapshot.py +0 -0
  205. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/snapshot.pyi +0 -0
  206. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/stream_type.py +0 -0
  207. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/token_flow.py +0 -0
  208. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/token_flow.pyi +0 -0
  209. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal/workspace.py +0 -0
  210. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal.egg-info/dependency_links.txt +0 -0
  211. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal.egg-info/entry_points.txt +0 -0
  212. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal.egg-info/requires.txt +0 -0
  213. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal.egg-info/top_level.txt +0 -0
  214. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/__init__.py +0 -0
  215. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/gen_cli_docs_main.py +0 -0
  216. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/gen_reference_docs_main.py +0 -0
  217. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/mdmd/__init__.py +0 -0
  218. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_docs/mdmd/types.py +0 -0
  219. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/__init__.py +0 -0
  220. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/api_grpc.py +0 -0
  221. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/api_pb2_grpc.py +0 -0
  222. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/api_pb2_grpc.pyi +0 -0
  223. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/modal_api_grpc.py +0 -0
  224. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/py.typed +0 -0
  225. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/task_command_router_grpc.py +0 -0
  226. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2.py +0 -0
  227. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2.pyi +0 -0
  228. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  229. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  230. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/modal_version/__main__.py +0 -0
  231. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/pyproject.toml +0 -0
  232. {modal-1.5.2.dev14 → modal-1.5.2.dev15}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev14
3
+ Version: 1.5.2.dev15
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -9,7 +9,7 @@ if sys.version_info[:2] >= (3, 15):
9
9
  from modal_version import __version__
10
10
 
11
11
  try:
12
- from . import billing
12
+ from . import billing, types
13
13
  from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
14
14
  from ._tunnel import Tunnel, forward
15
15
  from .app import App
@@ -100,6 +100,7 @@ __all__ = [
100
100
  "interact",
101
101
  "method",
102
102
  "parameter",
103
+ "types",
103
104
  "web_endpoint",
104
105
  "web_server",
105
106
  "wsgi_app",
@@ -1,10 +1,7 @@
1
1
  # Copyright Modal Labs 2025
2
- from dataclasses import FrozenInstanceError, dataclass
3
- from datetime import datetime, timezone
2
+ from datetime import datetime
4
3
  from decimal import Decimal
5
- from typing import Any, Iterable, TypedDict
6
-
7
- from modal_proto import api_pb2
4
+ from typing import TypedDict
8
5
 
9
6
  from ._utils.deprecation import deprecation_warning
10
7
  from .client import _Client
@@ -19,54 +16,6 @@ class WorkspaceBillingReportItem(TypedDict):
19
16
  tags: dict[str, str]
20
17
 
21
18
 
22
- @dataclass(slots=True, frozen=True)
23
- class BillingReportItem:
24
- object_id: str
25
- description: str
26
- environment_name: str
27
- interval_start: datetime
28
- cost: Decimal
29
- cost_by_resource: dict[str, Decimal]
30
- tags: dict[str, str]
31
-
32
- def __getitem__(self, key: str) -> Any:
33
- """mdmd:ignore"""
34
- if key not in self.__slots__:
35
- raise KeyError(key)
36
-
37
- return getattr(self, key)
38
-
39
- def __setitem__(self, key: str, _: Any):
40
- """mdmd:ignore"""
41
- raise FrozenInstanceError(f"cannot assign to field {key!r}")
42
-
43
- def keys(self) -> Iterable[str]:
44
- """mdmd:ignore"""
45
- yield from self.__slots__
46
-
47
- def values(self) -> Iterable[Any]:
48
- """mdmd:ignore"""
49
- for k in self.__slots__:
50
- yield getattr(self, k)
51
-
52
- def items(self) -> Iterable[tuple[str, Any]]:
53
- """mdmd:ignore"""
54
- for k in self.__slots__:
55
- yield k, getattr(self, k)
56
-
57
- @classmethod
58
- def _from_proto(cls, pb_item: api_pb2.WorkspaceBillingReportItem) -> "BillingReportItem":
59
- return cls(
60
- object_id=pb_item.object_id,
61
- description=pb_item.description,
62
- environment_name=pb_item.environment_name,
63
- interval_start=pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
64
- cost=Decimal(pb_item.cost),
65
- cost_by_resource={k: Decimal(v) for k, v in pb_item.cost_by_resource.items()},
66
- tags=dict(pb_item.tags),
67
- )
68
-
69
-
70
19
  async def _workspace_billing_report(
71
20
  *,
72
21
  start: datetime, # Start of the report, inclusive
@@ -13,7 +13,6 @@ from synchronicity import classproperty
13
13
 
14
14
  from modal_proto import api_pb2
15
15
 
16
- from ._billing import BillingReportItem
17
16
  from ._load_context import LoadContext
18
17
  from ._object import _Object
19
18
  from ._resolver import Resolver
@@ -21,6 +20,7 @@ from ._utils.name_utils import check_environment_name
21
20
  from .client import _Client
22
21
  from .config import config, logger
23
22
  from .exception import InvalidError, WorkspaceManagementError
23
+ from .types import BillingReportItem
24
24
 
25
25
 
26
26
  @dataclass(frozen=True)
@@ -90,6 +90,7 @@ from .proxy import _Proxy
90
90
  from .retries import Retries, RetryManager
91
91
  from .schedule import Schedule
92
92
  from .secret import _Secret
93
+ from .types import FunctionStats
93
94
  from .volume import _Volume, _volume_to_mount_proto
94
95
 
95
96
  if TYPE_CHECKING:
@@ -541,17 +542,6 @@ class _InputPlaneInvocation:
541
542
  return [("x-modal-input-plane-region", input_plane_region), ("x-modal-auth-token", token)]
542
543
 
543
544
 
544
- # Wrapper type for api_pb2.FunctionStats
545
- @dataclass(frozen=True)
546
- class FunctionStats:
547
- """Simple data structure storing stats for a running function."""
548
-
549
- backlog: int
550
- num_total_runners: int
551
- num_running_inputs: int
552
- input_headroom: int
553
-
554
-
555
545
  @dataclass
556
546
  class _FunctionSpec:
557
547
  """
@@ -2976,7 +2976,7 @@ class _Image(_Object, type_prefix="im"):
2976
2976
  api_pb2.ImagePublishRequest(
2977
2977
  image_id=self._object_id,
2978
2978
  environment_name=resolved_env,
2979
- is_public=False,
2979
+ allow_public=False,
2980
2980
  tag=tag,
2981
2981
  )
2982
2982
  )
@@ -1,22 +1,19 @@
1
1
  # Copyright Modal Labs 2025
2
2
  import builtins
3
- from dataclasses import dataclass
4
3
  from datetime import datetime, timezone
5
- from typing import Literal, Optional
4
+ from typing import Optional
6
5
 
7
6
  from google.protobuf.empty_pb2 import Empty
8
7
 
9
8
  from modal.exception import InvalidError
10
9
  from modal_proto import api_pb2
11
10
 
12
- from ._billing import BillingReportItem
13
11
  from ._load_context import LoadContext
14
12
  from ._object import _Object
15
13
  from ._resolver import Resolver
16
14
  from ._utils.time_utils import timestamp_to_localized_dt
17
15
  from .client import _Client
18
-
19
- MemberRole = Literal["user", "manager", "owner"]
16
+ from .types import BillingReportItem, MemberRole, ProxyTokenInfo, TokenData, WorkspaceMemberInfo, WorkspaceSettings
20
17
 
21
18
 
22
19
  def _member_role_from_proto(proto_value: int) -> MemberRole:
@@ -31,18 +28,6 @@ def _member_role_from_proto(proto_value: int) -> MemberRole:
31
28
  raise ValueError(f"Unknown workspace member role: {proto_value}")
32
29
 
33
30
 
34
- @dataclass(frozen=True)
35
- class WorkspaceMemberInfo:
36
- """Metadata about a Workspace member."""
37
-
38
- name: str
39
- email: str
40
- user_id: str
41
- role: MemberRole
42
- joined_at: datetime
43
- last_active_at: Optional[datetime] # None if the member has never been active
44
-
45
-
46
31
  class _WorkspaceMembersManager:
47
32
  """mdmd:namespace
48
33
  Namespace with methods for managing the membership of a Workspace.
@@ -130,23 +115,6 @@ class _Workspace(_Object, type_prefix="ac"):
130
115
  return _WorkspaceSettingsManager(self)
131
116
 
132
117
 
133
- @dataclass(frozen=True)
134
- class TokenData:
135
- """A token ID / secret pair."""
136
-
137
- token_id: str
138
- token_secret: str
139
-
140
-
141
- @dataclass(frozen=True)
142
- class ProxyTokenInfo:
143
- """Metadata about a proxy token, not including the token secret."""
144
-
145
- token_id: str
146
- created_at: datetime
147
- scoped: bool
148
-
149
-
150
118
  class _WorkspaceProxyTokenManager:
151
119
  """mdmd:namespace
152
120
  Namespace with methods for managing the proxy tokens in a Workspace.
@@ -346,14 +314,6 @@ class _WorkspaceBillingManager:
346
314
  ]
347
315
 
348
316
 
349
- @dataclass(frozen=True)
350
- class WorkspaceSettings:
351
- """Current settings for the workspace."""
352
-
353
- default_environment: str
354
- image_builder_version: str
355
-
356
-
357
317
  class _WorkspaceSettingsManager:
358
318
  """mdmd:namespace
359
319
  Namespace for Workspace settings APIs.
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2025
2
- from ._billing import BillingReportItem, WorkspaceBillingReportItem, _workspace_billing_report
2
+ from ._billing import WorkspaceBillingReportItem, _workspace_billing_report
3
3
  from ._utils.async_utils import synchronize_api
4
+ from .types import BillingReportItem
4
5
 
5
6
  workspace_billing_report = synchronize_api(_workspace_billing_report, target_module=__name__)
6
7
 
@@ -2,12 +2,32 @@ import datetime
2
2
  import decimal
3
3
  import modal._billing
4
4
  import modal.client
5
+ import modal.types
5
6
  import modal_proto.api_pb2
6
7
  import typing
7
8
  import typing_extensions
8
9
 
10
+ class WorkspaceBillingReportItem(typing.TypedDict):
11
+ """dict() -> new empty dictionary
12
+ dict(mapping) -> new dictionary initialized from a mapping object's
13
+ (key, value) pairs
14
+ dict(iterable) -> new dictionary initialized as if via:
15
+ d = {}
16
+ for k, v in iterable:
17
+ d[k] = v
18
+ dict(**kwargs) -> new dictionary initialized with the name=value pairs
19
+ in the keyword argument list. For example: dict(one=1, two=2)
20
+ """
21
+
22
+ object_id: str
23
+ description: str
24
+ environment_name: str
25
+ interval_start: datetime.datetime
26
+ cost: decimal.Decimal
27
+ tags: dict[str, str]
28
+
9
29
  class BillingReportItem:
10
- """BillingReportItem(object_id: str, description: str, environment_name: str, interval_start: datetime.datetime, cost: decimal.Decimal, cost_by_resource: dict[str, decimal.Decimal], tags: dict[str, str])"""
30
+ """Costs generated by a specific object during a specific time interval."""
11
31
 
12
32
  object_id: str
13
33
  description: str
@@ -18,29 +38,27 @@ class BillingReportItem:
18
38
  tags: dict[str, str]
19
39
 
20
40
  def __getitem__(self, key: str) -> typing.Any:
21
- """mdmd:ignore"""
41
+ """mdmd:hidden"""
22
42
  ...
23
43
 
24
44
  def __setitem__(self, key: str, _: typing.Any):
25
- """mdmd:ignore"""
45
+ """mdmd:hidden"""
26
46
  ...
27
47
 
28
48
  def keys(self) -> typing.Iterable[str]:
29
- """mdmd:ignore"""
49
+ """mdmd:hidden"""
30
50
  ...
31
51
 
32
52
  def values(self) -> typing.Iterable[typing.Any]:
33
- """mdmd:ignore"""
53
+ """mdmd:hidden"""
34
54
  ...
35
55
 
36
56
  def items(self) -> typing.Iterable[tuple[str, typing.Any]]:
37
- """mdmd:ignore"""
57
+ """mdmd:hidden"""
38
58
  ...
39
59
 
40
60
  @classmethod
41
- def _from_proto(
42
- cls, pb_item: modal_proto.api_pb2.WorkspaceBillingReportItem
43
- ) -> modal._billing.BillingReportItem: ...
61
+ def _from_proto(cls, pb_item: modal_proto.api_pb2.WorkspaceBillingReportItem) -> modal.types.BillingReportItem: ...
44
62
  def __init__(
45
63
  self,
46
64
  object_id: str,
@@ -77,25 +95,6 @@ class BillingReportItem:
77
95
  def __getstate__(self): ...
78
96
  def __setstate__(self, state): ...
79
97
 
80
- class WorkspaceBillingReportItem(typing.TypedDict):
81
- """dict() -> new empty dictionary
82
- dict(mapping) -> new dictionary initialized from a mapping object's
83
- (key, value) pairs
84
- dict(iterable) -> new dictionary initialized as if via:
85
- d = {}
86
- for k, v in iterable:
87
- d[k] = v
88
- dict(**kwargs) -> new dictionary initialized with the name=value pairs
89
- in the keyword argument list. For example: dict(one=1, two=2)
90
- """
91
-
92
- object_id: str
93
- description: str
94
- environment_name: str
95
- interval_start: datetime.datetime
96
- cost: decimal.Decimal
97
- tags: dict[str, str]
98
-
99
98
  class __workspace_billing_report_spec(typing_extensions.Protocol):
100
99
  def __call__(
101
100
  self,
@@ -1,36 +1,7 @@
1
1
  # Copyright Modal Labs 2022
2
- from dataclasses import dataclass
3
- from enum import IntEnum
4
-
5
2
  from modal_proto import api_pb2
6
3
 
7
-
8
- class InputStatus(IntEnum):
9
- """Enum representing status of a function input."""
10
-
11
- PENDING = 0
12
- SUCCESS = api_pb2.GenericResult.GENERIC_STATUS_SUCCESS
13
- FAILURE = api_pb2.GenericResult.GENERIC_STATUS_FAILURE
14
- INIT_FAILURE = api_pb2.GenericResult.GENERIC_STATUS_INIT_FAILURE
15
- TERMINATED = api_pb2.GenericResult.GENERIC_STATUS_TERMINATED
16
- TIMEOUT = api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT
17
-
18
- @classmethod
19
- def _missing_(cls, value):
20
- return cls.PENDING
21
-
22
-
23
- @dataclass
24
- class InputInfo:
25
- """Simple data structure storing information about a function input."""
26
-
27
- input_id: str
28
- function_call_id: str
29
- task_id: str
30
- status: InputStatus
31
- function_name: str
32
- module_name: str
33
- children: list["InputInfo"]
4
+ from .types import InputInfo, InputStatus
34
5
 
35
6
 
36
7
  def _reconstruct_call_graph(ser_graph: api_pb2.FunctionGetCallGraphResponse) -> list[InputInfo]:
@@ -13,7 +13,8 @@ from click import UsageError
13
13
  from modal._utils.async_utils import TaskContext
14
14
  from modal.config import logger
15
15
  from modal.network_file_system import _NetworkFileSystem
16
- from modal.volume import FileEntry, FileEntryType, _Volume
16
+ from modal.types import FileEntry, FileEntryType
17
+ from modal.volume import _Volume
17
18
 
18
19
  PIPE_PATH = Path("-")
19
20
 
@@ -59,7 +59,7 @@ async def list_(env: str | None = None, json: bool = False):
59
59
  display_table(column_names, rows, json=json, title=f"Active Multi-node Clusters in environment: {environment_name}")
60
60
 
61
61
 
62
- @cluster_cli.command("shell")
62
+ @cluster_cli.command("shell", no_args_is_help=True)
63
63
  @click.argument("cluster_id")
64
64
  @click.option("--rank", default=0, help="Rank of the node to shell into")
65
65
  @synchronizer.create_blocking
@@ -40,7 +40,7 @@ when running a command that requires an environment.
40
40
  """
41
41
 
42
42
 
43
- @config_cli.command("set-environment", help=SET_DEFAULT_ENV_HELP)
43
+ @config_cli.command("set-environment", help=SET_DEFAULT_ENV_HELP, no_args_is_help=True)
44
44
  @click.argument("environment_name")
45
45
  def set_environment(environment_name: str):
46
46
  # Confirm that the environment exists by looking it up
@@ -49,7 +49,7 @@ def set_environment(environment_name: str):
49
49
  click.echo(f"New default environment for profile {_profile}: {environment_name}")
50
50
 
51
51
 
52
- @config_cli.command("set", hidden=True)
52
+ @config_cli.command("set", hidden=True, no_args_is_help=True)
53
53
  @click.argument("key")
54
54
  @click.argument("value")
55
55
  def set(key: str, value: str):
@@ -317,7 +317,7 @@ async def _exec_impl(
317
317
  ).wait()
318
318
 
319
319
 
320
- @container_cli.command("exec")
320
+ @container_cli.command("exec", no_args_is_help=True)
321
321
  @click.option("--pty/--no-pty", default=None, help="Run the command using a PTY.")
322
322
  @click.argument("container_id")
323
323
  @click.argument("command", nargs=-1, required=True)
@@ -330,7 +330,7 @@ def exec(
330
330
  _exec_impl(pty=pty, container_id=container_id, command=command)
331
331
 
332
332
 
333
- @container_cli.command("stop")
333
+ @container_cli.command("stop", no_args_is_help=True)
334
334
  @click.argument("container_id")
335
335
  @click.option(
336
336
  "--graceful",
@@ -96,7 +96,12 @@ def _run_curl(curl_args: tuple[str, ...], token: str | None = None) -> None:
96
96
  sys.exit(subprocess.call(cmd))
97
97
 
98
98
 
99
- @click.command("curl", cls=ModalCommand, context_settings={"ignore_unknown_options": True, "allow_extra_args": True})
99
+ @click.command(
100
+ "curl",
101
+ cls=ModalCommand,
102
+ no_args_is_help=True,
103
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
104
+ )
100
105
  @click.argument("curl_args", nargs=-1, required=True, type=click.UNPROCESSED)
101
106
  @synchronizer.create_blocking
102
107
  async def curl(curl_args: tuple[str, ...]):
@@ -21,7 +21,7 @@ dict_cli = ModalGroup(
21
21
  )
22
22
 
23
23
 
24
- @dict_cli.command("create", panel="Management")
24
+ @dict_cli.command("create", panel="Management", no_args_is_help=True)
25
25
  @click.argument("name")
26
26
  @env_option
27
27
  @synchronizer.create_blocking
@@ -55,7 +55,7 @@ async def list_(*, json: bool = False, env: str | None = None):
55
55
  display_table(["Name", "Created at", "Created by"], rows, json)
56
56
 
57
57
 
58
- @dict_cli.command("clear", panel="Management")
58
+ @dict_cli.command("clear", panel="Management", no_args_is_help=True)
59
59
  @click.argument("name")
60
60
  @yes_option
61
61
  @env_option
@@ -72,7 +72,7 @@ async def clear(name: str, *, yes: bool = False, env: str | None = None):
72
72
  await d.clear()
73
73
 
74
74
 
75
- @dict_cli.command("delete", panel="Management")
75
+ @dict_cli.command("delete", panel="Management", no_args_is_help=True)
76
76
  @click.argument("name")
77
77
  @click.option("--allow-missing", is_flag=True, default=False, help="Don't error if the Dict doesn't exist.")
78
78
  @yes_option
@@ -95,7 +95,7 @@ async def delete(
95
95
  await _Dict.objects.delete(name, environment_name=env, allow_missing=allow_missing)
96
96
 
97
97
 
98
- @dict_cli.command("get", panel="Inspection")
98
+ @dict_cli.command("get", panel="Inspection", no_args_is_help=True)
99
99
  @click.argument("name")
100
100
  @click.argument("key")
101
101
  @env_option
@@ -115,7 +115,7 @@ def _display(input: str, use_repr: bool) -> str:
115
115
  return val[:80] + "..." if len(val) > 80 else val
116
116
 
117
117
 
118
- @dict_cli.command("items", panel="Inspection")
118
+ @dict_cli.command("items", panel="Inspection", no_args_is_help=True)
119
119
  @click.argument("name")
120
120
  @click.argument("n", default=20, type=int)
121
121
  @click.option(
@@ -63,7 +63,7 @@ def list_(json: bool = False):
63
63
  display_table(["name", "web suffix", "active"], table_data, json=json)
64
64
 
65
65
 
66
- @environment_cli.command("create", help="Create a new environment in the current workspace.")
66
+ @environment_cli.command("create", help="Create a new environment in the current workspace.", no_args_is_help=True)
67
67
  @click.argument("name")
68
68
  @click.option("--restricted", is_flag=True, default=False, help="Enable RBAC restrictions on the new environment")
69
69
  def create(name: str, restricted: bool = False):
@@ -79,7 +79,7 @@ Deletes all apps in the selected environment and deletes the environment irrevoc
79
79
  """
80
80
 
81
81
 
82
- @environment_cli.command("delete", help=ENVIRONMENT_DELETE_HELP)
82
+ @environment_cli.command("delete", help=ENVIRONMENT_DELETE_HELP, no_args_is_help=True)
83
83
  @click.argument("name")
84
84
  @yes_option
85
85
  def delete(
@@ -100,7 +100,7 @@ def delete(
100
100
  rich.print(f"[green]✓[/green] Environment deleted: {name}")
101
101
 
102
102
 
103
- @environment_cli.command("update", help="Update environment-level settings.")
103
+ @environment_cli.command("update", help="Update environment-level settings.", no_args_is_help=True)
104
104
  @click.argument("current_name")
105
105
  @click.option("--set-name", default=None, help="New name of the environment")
106
106
  @click.option("--set-web-suffix", default=None, help="New web suffix of environment (empty string is no suffix)")
@@ -57,14 +57,12 @@ def _history_item_json(item: api_pb2.ImageTagRevisionsItem) -> dict[str, str]:
57
57
  return {
58
58
  "image_id": item.image_id,
59
59
  "published_at": timestamp_to_localized_str(item.created_at, True) or "-",
60
+ "published_by": item.published_by or "-",
60
61
  }
61
62
 
62
63
 
63
- def _history_item_row(item: api_pb2.ImageTagRevisionsItem) -> tuple[str, str]:
64
- return (
65
- item.image_id,
66
- timestamp_to_localized_str(item.created_at, False) or "-",
67
- )
64
+ def _history_item_row(item: api_pb2.ImageTagRevisionsItem) -> tuple[str, str, str]:
65
+ return (item.image_id, timestamp_to_localized_str(item.created_at, False) or "-", item.published_by or "-")
68
66
 
69
67
 
70
68
  async def _iter_tag_pages(
@@ -158,7 +156,9 @@ async def list_(
158
156
  _print_result_summary(count)
159
157
 
160
158
 
161
- @image_names_cli.command("history", help="Show publishing history for a named Image.", hidden=True)
159
+ @image_names_cli.command(
160
+ "history", help="Show publishing history for a named Image.", hidden=True, no_args_is_help=True
161
+ )
162
162
  @click.argument("name")
163
163
  @env_option
164
164
  @click.option("--json", is_flag=True, default=False)
@@ -197,7 +197,7 @@ async def history(
197
197
  if response.items:
198
198
  title = f"History for {response.tag}" if show_title else ""
199
199
  display_table(
200
- ["Image ID", "Published at"],
200
+ ["Image ID", "Published at", "Published by"],
201
201
  [_history_item_row(item) for item in response.items],
202
202
  title=title,
203
203
  )
@@ -56,7 +56,7 @@ def some_func():
56
56
  """
57
57
 
58
58
 
59
- @nfs_cli.command("create", help="Create a named network file system.", panel="Management")
59
+ @nfs_cli.command("create", help="Create a named network file system.", panel="Management", no_args_is_help=True)
60
60
  @click.argument("name")
61
61
  @env_option
62
62
  def create(
@@ -71,7 +71,9 @@ def create(
71
71
  output.print(usage)
72
72
 
73
73
 
74
- @nfs_cli.command("ls", help="List files and directories in a network file system.", panel="File operations")
74
+ @nfs_cli.command(
75
+ "ls", help="List files and directories in a network file system.", panel="File operations", no_args_is_help=True
76
+ )
75
77
  @click.argument("volume_name")
76
78
  @click.argument("path", default="/")
77
79
  @env_option
@@ -102,7 +104,7 @@ async def ls(
102
104
  print(entry.path) # noqa: T201
103
105
 
104
106
 
105
- @nfs_cli.command("put", panel="File operations")
107
+ @nfs_cli.command("put", panel="File operations", no_args_is_help=True)
106
108
  @click.argument("volume_name")
107
109
  @click.argument("local_path")
108
110
  @click.argument("remote_path", default="/")
@@ -147,7 +149,7 @@ class CliError(Exception):
147
149
  self.message = message
148
150
 
149
151
 
150
- @nfs_cli.command("get", panel="File operations")
152
+ @nfs_cli.command("get", panel="File operations", no_args_is_help=True)
151
153
  @click.argument("volume_name")
152
154
  @click.argument("remote_path")
153
155
  @click.argument("local_destination", default=".")
@@ -183,7 +185,9 @@ async def get(
183
185
  output.step_completed("Finished downloading files to local!")
184
186
 
185
187
 
186
- @nfs_cli.command("rm", help="Delete a file or directory from a network file system.", panel="File operations")
188
+ @nfs_cli.command(
189
+ "rm", help="Delete a file or directory from a network file system.", panel="File operations", no_args_is_help=True
190
+ )
187
191
  @click.argument("volume_name")
188
192
  @click.argument("remote_path")
189
193
  @click.option("-r", "--recursive", is_flag=True, default=False, help="Delete directory recursively")
@@ -201,7 +205,9 @@ async def rm(
201
205
  OutputManager.get().step_completed(f"{remote_path} was deleted successfully!")
202
206
 
203
207
 
204
- @nfs_cli.command("delete", help="Delete a named, persistent modal.NetworkFileSystem.", panel="Management")
208
+ @nfs_cli.command(
209
+ "delete", help="Delete a named, persistent modal.NetworkFileSystem.", panel="Management", no_args_is_help=True
210
+ )
205
211
  @click.argument("nfs_name")
206
212
  @yes_option
207
213
  @env_option
@@ -25,7 +25,7 @@ from ._help import ModalGroup
25
25
  profile_cli = ModalGroup(name="profile", help="Switch between Modal profiles.")
26
26
 
27
27
 
28
- @profile_cli.command("activate", help="Change the active Modal profile.")
28
+ @profile_cli.command("activate", help="Change the active Modal profile.", no_args_is_help=True)
29
29
  @click.argument("profile")
30
30
  def activate(profile: str):
31
31
  config_set_active_profile(profile)
@@ -32,7 +32,7 @@ def partition_option(func):
32
32
  )(func)
33
33
 
34
34
 
35
- @queue_cli.command("create", panel="Management")
35
+ @queue_cli.command("create", panel="Management", no_args_is_help=True)
36
36
  @click.argument("name")
37
37
  @env_option
38
38
  @synchronizer.create_blocking
@@ -49,7 +49,7 @@ async def create(name: str, *, env: str | None = None):
49
49
  await resolver.load(q, load_context)
50
50
 
51
51
 
52
- @queue_cli.command("delete", panel="Management")
52
+ @queue_cli.command("delete", panel="Management", no_args_is_help=True)
53
53
  @click.argument("name")
54
54
  @click.option("--allow-missing", is_flag=True, default=False, help="Don't error if the Queue doesn't exist.")
55
55
  @yes_option
@@ -122,7 +122,7 @@ async def list_(*, json: bool = False, env: str | None = None):
122
122
  display_table(["Name", "Created at", "Created by", "Partitions", "Total size"], rows, json)
123
123
 
124
124
 
125
- @queue_cli.command("clear", panel="Management")
125
+ @queue_cli.command("clear", panel="Management", no_args_is_help=True)
126
126
  @click.argument("name")
127
127
  @partition_option
128
128
  @click.option("-a", "--all", "all", is_flag=True, default=False, help="Clear the contents of all partitions.")
@@ -148,7 +148,7 @@ async def clear(
148
148
  await q.clear(partition=partition, all=all)
149
149
 
150
150
 
151
- @queue_cli.command("peek", panel="Inspection")
151
+ @queue_cli.command("peek", panel="Inspection", no_args_is_help=True)
152
152
  @click.argument("name")
153
153
  @click.argument("n", default=1, type=int)
154
154
  @partition_option
@@ -166,7 +166,7 @@ async def peek(name: str, n: int = 1, partition: str | None = None, *, env: str
166
166
  break
167
167
 
168
168
 
169
- @queue_cli.command("len", panel="Inspection")
169
+ @queue_cli.command("len", panel="Inspection", no_args_is_help=True)
170
170
  @click.argument("name")
171
171
  @partition_option
172
172
  @click.option(