modal 1.5.6.dev16__tar.gz → 1.5.6.dev18__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 (239) hide show
  1. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_logs.py +6 -0
  3. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_output/pty.py +2 -0
  4. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/blob_utils.py +26 -22
  5. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/function_utils.py +3 -4
  6. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_workspace.py +8 -3
  7. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/_logs.py +2 -0
  8. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/function.py +11 -7
  9. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/server.py +3 -6
  10. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/utils.py +8 -36
  11. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/workspace.py +52 -8
  12. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/client.pyi +2 -2
  13. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/types.py +2 -0
  14. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/workspace.pyi +15 -5
  15. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal.egg-info/PKG-INFO +1 -1
  16. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/api_pb2.py +1404 -1380
  17. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/api_pb2.pyi +63 -5
  18. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_version/__init__.py +1 -1
  19. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/LICENSE +0 -0
  20. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/README.md +0 -0
  21. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/__init__.py +0 -0
  22. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/__main__.py +0 -0
  23. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_billing.py +0 -0
  24. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_clustered_functions.py +0 -0
  25. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_clustered_functions.pyi +0 -0
  26. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_container_entrypoint.py +0 -0
  27. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_environments.py +0 -0
  28. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_function_variants.py +0 -0
  29. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_functions.py +0 -0
  30. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_grpc_client.py +0 -0
  31. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_image.py +0 -0
  32. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_ipython.py +0 -0
  33. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_load_context.py +0 -0
  34. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_location.py +0 -0
  35. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_logs_manager.py +0 -0
  36. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_logs_manager.pyi +0 -0
  37. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_object.py +0 -0
  38. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_output/__init__.py +0 -0
  39. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_output/manager.py +0 -0
  40. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_output/rich.py +0 -0
  41. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_output/status.py +0 -0
  42. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_partial_function.py +0 -0
  43. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_resolver.py +0 -0
  44. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_resources.py +0 -0
  45. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/__init__.py +0 -0
  46. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/asgi.py +0 -0
  47. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/container_io_manager.py +0 -0
  48. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/container_io_manager.pyi +0 -0
  49. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/execution_context.py +0 -0
  50. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/execution_context.pyi +0 -0
  51. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  52. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/task_lifecycle_manager.py +0 -0
  53. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  54. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/telemetry.py +0 -0
  55. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/user_code_event_loop.py +0 -0
  56. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_runtime/user_code_imports.py +0 -0
  57. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_serialization.py +0 -0
  58. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_server.py +0 -0
  59. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_supports_logs.py +0 -0
  60. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_traceback.py +0 -0
  61. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_tunnel.py +0 -0
  62. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_tunnel.pyi +0 -0
  63. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_type_manager.py +0 -0
  64. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/__init__.py +0 -0
  65. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/app_utils.py +0 -0
  66. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/async_utils.py +0 -0
  67. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/auth_token_manager.py +0 -0
  68. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/browser_utils.py +0 -0
  69. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/bytes_io_segment_payload.py +0 -0
  70. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/curl_utils.py +0 -0
  71. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/deprecation.py +0 -0
  72. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/display_utils.py +0 -0
  73. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/docker_utils.py +0 -0
  74. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/git_utils.py +0 -0
  75. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/grpc_testing.py +0 -0
  76. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/grpc_utils.py +0 -0
  77. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/hash_utils.py +0 -0
  78. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/http_utils.py +0 -0
  79. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/idle_countdown.py +0 -0
  80. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/jwt_utils.py +0 -0
  81. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/logger.py +0 -0
  82. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/mount_utils.py +0 -0
  83. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/name_utils.py +0 -0
  84. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/package_utils.py +0 -0
  85. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/pattern_utils.py +0 -0
  86. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/proxy_support.py +0 -0
  87. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/rand_pb_testing.py +0 -0
  88. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/sandbox_fs_utils.py +0 -0
  89. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/shell_utils.py +0 -0
  90. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/task_command_router_client.py +0 -0
  91. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_utils/time_utils.py +0 -0
  92. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_vendor/__init__.py +0 -0
  93. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  94. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_vendor/cloudpickle.py +0 -0
  95. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_vendor/tblib.py +0 -0
  96. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_vendor/version.py +0 -0
  97. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/_watcher.py +0 -0
  98. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/app.py +0 -0
  99. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/app.pyi +0 -0
  100. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/billing.py +0 -0
  101. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/billing.pyi +0 -0
  102. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/2023.12.312.txt +0 -0
  103. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/2023.12.txt +0 -0
  104. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/2024.04.txt +0 -0
  105. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/2024.10.txt +0 -0
  106. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/2025.06.txt +0 -0
  107. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/PREVIEW.txt +0 -0
  108. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/README.md +0 -0
  109. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/builder/base-images.json +0 -0
  110. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/call_graph.py +0 -0
  111. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/__init__.py +0 -0
  112. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/_download.py +0 -0
  113. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/_help.py +0 -0
  114. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/_stats.py +0 -0
  115. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/_traceback.py +0 -0
  116. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/app.py +0 -0
  117. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/billing.py +0 -0
  118. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/bootstrap.py +0 -0
  119. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/changelog.py +0 -0
  120. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/cluster.py +0 -0
  121. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/config.py +0 -0
  122. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/container.py +0 -0
  123. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/curl.py +0 -0
  124. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/dashboard.py +0 -0
  125. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/dict.py +0 -0
  126. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/endpoint.py +0 -0
  127. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/entry_point.py +0 -0
  128. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/environment.py +0 -0
  129. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/image.py +0 -0
  130. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/import_refs.py +0 -0
  131. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/launch.py +0 -0
  132. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/logo.py +0 -0
  133. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/network_file_system.py +0 -0
  134. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/profile.py +0 -0
  135. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/programs/__init__.py +0 -0
  136. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/programs/run_jupyter.py +0 -0
  137. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/programs/vscode.py +0 -0
  138. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/queues.py +0 -0
  139. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/run.py +0 -0
  140. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/secret.py +0 -0
  141. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/selector.py +0 -0
  142. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/shell.py +0 -0
  143. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/skills.py +0 -0
  144. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/token.py +0 -0
  145. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cli/volume.py +0 -0
  146. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/client.py +0 -0
  147. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cloud_bucket_mount.py +0 -0
  148. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cloud_bucket_mount.pyi +0 -0
  149. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cls.py +0 -0
  150. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/cls.pyi +0 -0
  151. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/config.py +0 -0
  152. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/container_process.py +0 -0
  153. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/container_process.pyi +0 -0
  154. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/dict.py +0 -0
  155. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/dict.pyi +0 -0
  156. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/environments.py +0 -0
  157. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/environments.pyi +0 -0
  158. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/exception.py +0 -0
  159. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/experimental/__init__.py +0 -0
  160. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/experimental/flash.py +0 -0
  161. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/experimental/flash.pyi +0 -0
  162. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/experimental/ipython.py +0 -0
  163. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/file_pattern_matcher.py +0 -0
  164. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/functions.py +0 -0
  165. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/functions.pyi +0 -0
  166. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/image.py +0 -0
  167. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/image.pyi +0 -0
  168. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/io_streams.py +0 -0
  169. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/io_streams.pyi +0 -0
  170. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/mount.py +0 -0
  171. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/mount.pyi +0 -0
  172. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/network_file_system.py +0 -0
  173. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/network_file_system.pyi +0 -0
  174. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/object.py +0 -0
  175. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/object.pyi +0 -0
  176. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/output.py +0 -0
  177. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/parallel_map.py +0 -0
  178. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/parallel_map.pyi +0 -0
  179. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/partial_function.py +0 -0
  180. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/partial_function.pyi +0 -0
  181. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/proxy.py +0 -0
  182. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/proxy.pyi +0 -0
  183. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/py.typed +0 -0
  184. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/queue.py +0 -0
  185. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/queue.pyi +0 -0
  186. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/retries.py +0 -0
  187. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/runner.py +0 -0
  188. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/runner.pyi +0 -0
  189. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/running_app.py +0 -0
  190. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/sandbox.py +0 -0
  191. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/sandbox.pyi +0 -0
  192. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/sandbox_fs.py +0 -0
  193. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/sandbox_fs.pyi +0 -0
  194. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/schedule.py +0 -0
  195. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/scheduler_placement.py +0 -0
  196. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/secret.py +0 -0
  197. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/secret.pyi +0 -0
  198. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/server.py +0 -0
  199. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/server.pyi +0 -0
  200. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/serving.py +0 -0
  201. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/serving.pyi +0 -0
  202. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/skills/modal/SKILL.md +0 -0
  203. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/snapshot.py +0 -0
  204. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/snapshot.pyi +0 -0
  205. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/stream_type.py +0 -0
  206. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/token_flow.py +0 -0
  207. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/token_flow.pyi +0 -0
  208. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/volume.py +0 -0
  209. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/volume.pyi +0 -0
  210. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal/workspace.py +0 -0
  211. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal.egg-info/SOURCES.txt +0 -0
  212. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal.egg-info/dependency_links.txt +0 -0
  213. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal.egg-info/entry_points.txt +0 -0
  214. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal.egg-info/requires.txt +0 -0
  215. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal.egg-info/top_level.txt +0 -0
  216. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/__init__.py +0 -0
  217. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/gen_cli_docs.py +0 -0
  218. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/gen_cli_docs_main.py +0 -0
  219. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/gen_reference_docs.py +0 -0
  220. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/gen_reference_docs_main.py +0 -0
  221. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/mdmd/__init__.py +0 -0
  222. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/mdmd/mdmd.py +0 -0
  223. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/mdmd/signatures.py +0 -0
  224. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/mdmd/types.py +0 -0
  225. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_docs/reference_intro.md +0 -0
  226. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/__init__.py +0 -0
  227. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/api_grpc.py +0 -0
  228. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/api_pb2_grpc.py +0 -0
  229. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/api_pb2_grpc.pyi +0 -0
  230. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/modal_api_grpc.py +0 -0
  231. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/py.typed +0 -0
  232. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/task_command_router_grpc.py +0 -0
  233. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/task_command_router_pb2.py +0 -0
  234. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/task_command_router_pb2.pyi +0 -0
  235. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  236. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  237. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/modal_version/__main__.py +0 -0
  238. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/pyproject.toml +0 -0
  239. {modal-1.5.6.dev16 → modal-1.5.6.dev18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev16
3
+ Version: 1.5.6.dev18
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
21
21
  class LogsFilters:
22
22
  source: "api_pb2.FileDescriptor.ValueType" = api_pb2.FILE_DESCRIPTOR_UNSPECIFIED
23
23
  function_id: str = ""
24
+ parametrized_function_id: str = ""
24
25
  function_call_id: str = ""
25
26
  task_id: str = ""
26
27
  sandbox_id: str = ""
@@ -249,6 +250,7 @@ async def _refine_dense_ranges(
249
250
  bucket_secs=smaller_secs,
250
251
  source=filters.source,
251
252
  function_id=filters.function_id,
253
+ parametrized_function_id=filters.parametrized_function_id,
252
254
  function_call_id=filters.function_call_id,
253
255
  task_id=filters.task_id,
254
256
  sandbox_id=filters.sandbox_id,
@@ -303,6 +305,7 @@ async def _fetch_interval(
303
305
  limit=limit,
304
306
  source=filters.source,
305
307
  function_id=filters.function_id,
308
+ parametrized_function_id=filters.parametrized_function_id,
306
309
  function_call_id=filters.function_call_id,
307
310
  task_id=filters.task_id,
308
311
  sandbox_id=filters.sandbox_id,
@@ -356,6 +359,7 @@ async def tail_logs(
356
359
  limit=n,
357
360
  source=filters.source,
358
361
  function_id=filters.function_id,
362
+ parametrized_function_id=filters.parametrized_function_id,
359
363
  function_call_id=filters.function_call_id,
360
364
  task_id=filters.task_id,
361
365
  sandbox_id=filters.sandbox_id,
@@ -377,6 +381,7 @@ async def tail_logs(
377
381
  limit=n,
378
382
  source=filters.source,
379
383
  function_id=filters.function_id,
384
+ parametrized_function_id=filters.parametrized_function_id,
380
385
  function_call_id=filters.function_call_id,
381
386
  task_id=filters.task_id,
382
387
  sandbox_id=filters.sandbox_id,
@@ -422,6 +427,7 @@ async def fetch_logs(
422
427
  bucket_secs=bucket_secs,
423
428
  source=filters.source,
424
429
  function_id=filters.function_id,
430
+ parametrized_function_id=filters.parametrized_function_id,
425
431
  function_call_id=filters.function_call_id,
426
432
  task_id=filters.task_id,
427
433
  sandbox_id=filters.sandbox_id,
@@ -101,6 +101,7 @@ async def get_app_logs_loop(
101
101
  function_id: str = "",
102
102
  function_call_id: str = "",
103
103
  search_text: str = "",
104
+ parametrized_function_id: str = "",
104
105
  ):
105
106
  last_log_batch_entry_id = last_entry_id
106
107
  _prefixes = prefix_fields or []
@@ -172,6 +173,7 @@ async def get_app_logs_loop(
172
173
  last_entry_id=last_log_batch_entry_id,
173
174
  file_descriptor=file_descriptor,
174
175
  function_id=function_id,
176
+ parametrized_function_id=parametrized_function_id,
175
177
  function_call_id=function_call_id,
176
178
  )
177
179
  log_batch: api_pb2.TaskLogsBatch
@@ -254,26 +254,28 @@ def get_content_length(data: BinaryIO) -> int:
254
254
 
255
255
  async def _blob_upload_with_fallback(
256
256
  items, blob_ids: list[str], callback, content_length: int
257
- ) -> tuple[str, bool, int]:
257
+ ) -> tuple[str, list[api_pb2.BlobUploadResult]]:
258
258
  """Try uploading to each provider in order, with fallback on failure."""
259
- r2_throughput_bytes_s = 0
260
- r2_failed = False
259
+ results: list[api_pb2.BlobUploadResult] = []
261
260
  for idx, (item, blob_id) in enumerate(zip(items, blob_ids)):
261
+ t0 = time.monotonic_ns()
262
262
  try:
263
- if blob_id.endswith(":r2"):
264
- t0 = time.monotonic_ns()
265
- await callback(item)
266
- dt_ns = time.monotonic_ns() - t0
267
- r2_throughput_bytes_s = (content_length * 1_000_000_000) // max(dt_ns, 1)
268
- else:
269
- await callback(item)
270
- return blob_id, r2_failed, r2_throughput_bytes_s
263
+ await callback(item)
271
264
  except Exception:
272
- if blob_id.endswith(":r2"):
273
- r2_failed = True
265
+ results.append(api_pb2.BlobUploadResult(blob_id=blob_id, outcome=api_pb2.BlobUploadResult.OUTCOME_FAILURE))
274
266
  # Ignore all errors except the last one, since we're out of fallback options.
275
267
  if idx == len(items) - 1:
276
268
  raise
269
+ continue
270
+ dt_ns = time.monotonic_ns() - t0
271
+ results.append(
272
+ api_pb2.BlobUploadResult(
273
+ blob_id=blob_id,
274
+ outcome=api_pb2.BlobUploadResult.OUTCOME_SUCCESS,
275
+ throughput_bytes_s=(content_length * 1_000_000_000) // max(dt_ns, 1),
276
+ )
277
+ )
278
+ return blob_id, results
277
279
  raise ExecutionError("Failed to upload blob")
278
280
 
279
281
 
@@ -283,7 +285,7 @@ async def _blob_upload(
283
285
  stub,
284
286
  progress_report_cb: Callable | None = None,
285
287
  byte_budget: _ByteBudget | None = None,
286
- ) -> tuple[str, bool, int]:
288
+ ) -> tuple[str, list[api_pb2.BlobUploadResult]]:
287
289
  if isinstance(data, bytes):
288
290
  data = BytesIO(data)
289
291
 
@@ -310,7 +312,7 @@ async def _blob_upload(
310
312
  byte_budget=byte_budget,
311
313
  )
312
314
 
313
- blob_id, r2_failed, r2_throughput_bytes_s = await _blob_upload_with_fallback(
315
+ blob_id, upload_results = await _blob_upload_with_fallback(
314
316
  resp.multiparts.items,
315
317
  resp.blob_ids,
316
318
  upload_multipart_upload,
@@ -331,7 +333,7 @@ async def _blob_upload(
331
333
  content_md5_b64=upload_hashes.md5_base64,
332
334
  )
333
335
 
334
- blob_id, r2_failed, r2_throughput_bytes_s = await _blob_upload_with_fallback(
336
+ blob_id, upload_results = await _blob_upload_with_fallback(
335
337
  resp.upload_urls.items,
336
338
  resp.blob_ids,
337
339
  upload_to_s3_url,
@@ -341,10 +343,12 @@ async def _blob_upload(
341
343
  if progress_report_cb:
342
344
  progress_report_cb(complete=True)
343
345
 
344
- return blob_id, r2_failed, r2_throughput_bytes_s
346
+ return blob_id, upload_results
345
347
 
346
348
 
347
- async def blob_upload_with_r2_failure_info(payload: bytes, stub: ModalClientModal) -> tuple[str, bool, int]:
349
+ async def blob_upload_with_results(
350
+ payload: bytes, stub: ModalClientModal
351
+ ) -> tuple[str, list[api_pb2.BlobUploadResult]]:
348
352
  size_mib = len(payload) / 1024 / 1024
349
353
  logger.debug(f"Uploading large blob of size {size_mib:.2f} MiB")
350
354
  t0 = time.time()
@@ -352,17 +356,17 @@ async def blob_upload_with_r2_failure_info(payload: bytes, stub: ModalClientModa
352
356
  logger.debug("Blob uploading string, not bytes - auto-encoding as utf8")
353
357
  payload = payload.encode("utf8")
354
358
  upload_hashes = get_upload_hashes(payload)
355
- blob_id, r2_failed, r2_throughput_bytes_s = await _blob_upload(upload_hashes, payload, stub)
359
+ blob_id, upload_results = await _blob_upload(upload_hashes, payload, stub)
356
360
  dur_s = max(time.time() - t0, 0.001) # avoid division by zero
357
361
  throughput_mib_s = (size_mib) / dur_s
358
362
  logger.debug(
359
363
  f"Uploaded large blob of size {size_mib:.2f} MiB ({throughput_mib_s:.2f} MiB/s, total {dur_s:.2f}s). {blob_id}"
360
364
  )
361
- return blob_id, r2_failed, r2_throughput_bytes_s
365
+ return blob_id, upload_results
362
366
 
363
367
 
364
368
  async def blob_upload(payload: bytes, stub: ModalClientModal) -> str:
365
- blob_id, _, _ = await blob_upload_with_r2_failure_info(payload, stub)
369
+ blob_id, _ = await blob_upload_with_results(payload, stub)
366
370
  return blob_id
367
371
 
368
372
 
@@ -381,7 +385,7 @@ async def blob_upload_file(
381
385
  byte_budget: _ByteBudget | None = None,
382
386
  ) -> str:
383
387
  upload_hashes = get_upload_hashes(file_obj, sha256_hex=sha256_hex, md5_hex=md5_hex)
384
- blob_id, _, _ = await _blob_upload(upload_hashes, file_obj, stub, progress_report_cb, byte_budget=byte_budget)
388
+ blob_id, _ = await _blob_upload(upload_hashes, file_obj, stub, progress_report_cb, byte_budget=byte_budget)
385
389
  return blob_id
386
390
 
387
391
 
@@ -41,7 +41,7 @@ from .blob_utils import (
41
41
  MAX_ASYNC_OBJECT_SIZE_BYTES,
42
42
  MAX_OBJECT_SIZE_BYTES,
43
43
  blob_download,
44
- blob_upload_with_r2_failure_info,
44
+ blob_upload_with_results,
45
45
  )
46
46
 
47
47
  if typing.TYPE_CHECKING:
@@ -631,7 +631,7 @@ async def _create_input(
631
631
  if should_upload(
632
632
  len(args_serialized), max_object_size_bytes, max_async_object_size_bytes, function_call_invocation_type
633
633
  ):
634
- args_blob_id, r2_failed, r2_throughput_bytes_s = await blob_upload_with_r2_failure_info(args_serialized, stub)
634
+ args_blob_id, blob_upload_results = await blob_upload_with_results(args_serialized, stub)
635
635
  return api_pb2.FunctionPutInputsItem(
636
636
  input=api_pb2.FunctionInput(
637
637
  args_blob_id=args_blob_id,
@@ -639,8 +639,7 @@ async def _create_input(
639
639
  method_name=method_name,
640
640
  ),
641
641
  idx=idx,
642
- r2_failed=r2_failed,
643
- r2_throughput_bytes_s=r2_throughput_bytes_s,
642
+ blob_upload_results=blob_upload_results,
644
643
  )
645
644
  else:
646
645
  return api_pb2.FunctionPutInputsItem(
@@ -137,17 +137,20 @@ class _WorkspaceProxyTokenManager:
137
137
  """mdmd:hidden"""
138
138
  self._workspace = workspace
139
139
 
140
- async def create(self) -> TokenData:
140
+ async def create(self, name: str = "") -> TokenData:
141
141
  """Create a new proxy token for the Workspace.
142
142
 
143
+ Args:
144
+ name: An optional name to help identify the token.
145
+
143
146
  Examples:
144
147
  ```python notest
145
- token = modal.Workspace.from_context().proxy_tokens.create()
148
+ token = modal.Workspace.from_context().proxy_tokens.create(name="production-webhooks")
146
149
  print(token.token_id, token.token_secret)
147
150
  ```
148
151
  """
149
152
  await self._workspace.hydrate()
150
- resp = await self._workspace.client.stub.WebhookTokenCreate(api_pb2.WebhookTokenCreateRequest())
153
+ resp = await self._workspace.client.stub.WebhookTokenCreate(api_pb2.WebhookTokenCreateRequest(name=name))
151
154
  return TokenData(token_id=resp.token_id, token_secret=resp.token_secret)
152
155
 
153
156
  async def list(self, environment_name: Optional[str] = None) -> builtins.list[ProxyTokenInfo]:
@@ -180,6 +183,8 @@ class _WorkspaceProxyTokenManager:
180
183
  token_id=token.token_id,
181
184
  created_at=timestamp_to_localized_dt(token.created_at),
182
185
  scoped=token.scoped,
186
+ name=token.name,
187
+ created_by=token.created_by.username,
183
188
  )
184
189
  for token in resp.tokens
185
190
  ]
@@ -61,6 +61,7 @@ async def _run_logs_command(
61
61
  tail: int | None,
62
62
  search: str | None,
63
63
  function_id: str = "",
64
+ parametrized_function_id: str = "",
64
65
  function_call_id: str = "",
65
66
  container_id: str = "",
66
67
  source: str | None,
@@ -77,6 +78,7 @@ async def _run_logs_command(
77
78
  log_filters = LogsFilters(
78
79
  source=source_fd,
79
80
  function_id=function_id,
81
+ parametrized_function_id=parametrized_function_id,
80
82
  function_call_id=function_call_id,
81
83
  task_id=container_id,
82
84
  search_text=search or "",
@@ -193,9 +193,7 @@ async def stats(
193
193
 
194
194
  environment_name = _get_environment_name(ensure_env(env))
195
195
  client = await _Client.from_env()
196
- function_id = await _resolve_function_id(
197
- client, function_identifier, environment_name, command="stats", include_app_id=False
198
- )
196
+ function_id, _ = await _resolve_function_id(client, function_identifier, environment_name, command="stats")
199
197
  req = api_pb2.FunctionGetTimeRangeStatsRequest(
200
198
  function_id=function_id,
201
199
  since=_timestamp(since_dt),
@@ -322,11 +320,13 @@ async def stats(
322
320
  @click.option("--show-function-id", is_flag=True, default=False, help="Prefix each line with its Function ID")
323
321
  @click.option("--show-function-call-id", is_flag=True, default=False, help="Prefix each line with its FunctionCall ID")
324
322
  @click.option("--show-container-id", is_flag=True, default=False, help="Prefix each line with its Container ID")
323
+ @click.option("--all-variants", is_flag=True, default=False, help="Include logs from the base and all its variants.")
325
324
  @env_option
326
325
  @synchronizer.create_blocking
327
326
  async def logs(
328
327
  function_ref: str,
329
328
  follow: bool = False,
329
+ all_variants: bool = False,
330
330
  since: str | None = None,
331
331
  until: str | None = None,
332
332
  tail: int | None = None,
@@ -346,6 +346,9 @@ async def logs(
346
346
  By default, this command fetches the last 100 log entries and exits. Use ``-f`` to
347
347
  live-stream logs from a running function instead. Fetch and follow are mutually exclusive.
348
348
 
349
+ By default, logs are limited to the specified ID. Pass ``--all-variants`` to
350
+ include the base and all its variants, even when specifying a variant ID.
351
+
349
352
  Examples:
350
353
 
351
354
  Get recent logs based on a function ID:
@@ -400,8 +403,8 @@ async def logs(
400
403
  _validate_logs_args(follow=follow, since=since, until=until, tail=tail)
401
404
 
402
405
  client = await _Client.from_env()
403
- function_id, app_id = await _resolve_function_id(
404
- client, function_ref, env, object_type="Function", command="logs", include_app_id=True
406
+ function_id, metadata = await _resolve_function_id(
407
+ client, function_ref, env, object_type="Function", command="logs"
405
408
  )
406
409
 
407
410
  prefix_fields: list[str] = []
@@ -413,13 +416,14 @@ async def logs(
413
416
  prefix_fields.append("ta")
414
417
 
415
418
  await _run_logs_command(
416
- app_id,
419
+ metadata.app_id,
417
420
  follow=follow,
418
421
  since=since,
419
422
  until=until,
420
423
  tail=tail,
421
424
  search=search,
422
- function_id=function_id,
425
+ function_id=metadata.base_function_id or function_id,
426
+ parametrized_function_id="" if all_variants else function_id,
423
427
  function_call_id=function_call_id,
424
428
  container_id=container_id,
425
429
  source=source,
@@ -133,9 +133,7 @@ async def logs(
133
133
  _validate_logs_args(follow=follow, since=since, until=until, tail=tail)
134
134
  client = await _Client.from_env()
135
135
 
136
- function_id, app_id = await _resolve_function_id(
137
- client, server_ref, env, object_type="Server", command="logs", include_app_id=True
138
- )
136
+ function_id, metadata = await _resolve_function_id(client, server_ref, env, object_type="Server", command="logs")
139
137
 
140
138
  prefix_fields: list[str] = []
141
139
  if show_server_id:
@@ -144,7 +142,7 @@ async def logs(
144
142
  prefix_fields.append("ta")
145
143
 
146
144
  await _run_logs_command(
147
- app_id,
145
+ metadata.app_id,
148
146
  follow=follow,
149
147
  since=since,
150
148
  until=until,
@@ -419,13 +417,12 @@ async def stats(
419
417
 
420
418
  environment_name = _get_environment_name(ensure_env(env))
421
419
  client = await _Client.from_env()
422
- function_id = await _resolve_function_id(
420
+ function_id, _ = await _resolve_function_id(
423
421
  client,
424
422
  server_identifier,
425
423
  environment_name,
426
424
  object_type="Server",
427
425
  command="stats",
428
- include_app_id=False,
429
426
  )
430
427
  req = api_pb2.ServerGetTimeRangeStatsRequest(
431
428
  function_id=function_id,
@@ -8,7 +8,7 @@ from contextlib import nullcontext
8
8
  from csv import writer as csv_writer
9
9
  from datetime import datetime
10
10
  from json import dumps
11
- from typing import Literal, overload
11
+ from typing import Literal
12
12
 
13
13
  import click
14
14
  from click.exceptions import UsageError
@@ -64,6 +64,7 @@ async def _stream_app_logs(
64
64
  prefix_fields=prefix_fields or [],
65
65
  file_descriptor=filters.source,
66
66
  function_id=filters.function_id,
67
+ parametrized_function_id=filters.parametrized_function_id,
67
68
  function_call_id=filters.function_call_id,
68
69
  search_text=filters.search_text,
69
70
  )
@@ -209,7 +210,6 @@ def _is_function_id(ref: str) -> bool:
209
210
  return bool(re.match(r"^fu-[0-9a-zA-Z]+$", ref))
210
211
 
211
212
 
212
- @overload
213
213
  async def _resolve_function_id(
214
214
  client: _Client,
215
215
  function_identifier: str,
@@ -217,31 +217,7 @@ async def _resolve_function_id(
217
217
  *,
218
218
  object_type: Literal["Function", "Server"] = "Function",
219
219
  command: Literal["logs", "stats"],
220
- include_app_id: Literal[True],
221
- ) -> tuple[str, str]: ...
222
-
223
-
224
- @overload
225
- async def _resolve_function_id(
226
- client: _Client,
227
- function_identifier: str,
228
- environment_name: str,
229
- *,
230
- object_type: Literal["Function", "Server"] = "Function",
231
- command: Literal["logs", "stats"],
232
- include_app_id: Literal[False],
233
- ) -> str: ...
234
-
235
-
236
- async def _resolve_function_id(
237
- client: _Client,
238
- function_identifier: str,
239
- environment_name: str,
240
- *,
241
- object_type: Literal["Function", "Server"] = "Function",
242
- command: Literal["logs", "stats"],
243
- include_app_id: bool,
244
- ) -> str | tuple[str, str]:
220
+ ) -> tuple[str, api_pb2.FunctionHandleMetadata]:
245
221
  identifier_label = object_type.upper()
246
222
  usage = (
247
223
  f"{identifier_label} must be a Function ID (fu-…) "
@@ -272,16 +248,12 @@ async def _resolve_function_id(
272
248
  raise
273
249
 
274
250
  print_server_warnings(response.server_warnings)
275
- if include_app_id:
276
- return response.function_id, response.handle_metadata.app_id
277
- return response.function_id
251
+ return response.function_id, response.handle_metadata
278
252
 
279
253
  if _is_function_id(function_identifier):
280
- if include_app_id:
281
- get_by_id_response = await client.stub.FunctionGetById(
282
- api_pb2.FunctionGetByIdRequest(function_id=function_identifier)
283
- )
284
- return function_identifier, get_by_id_response.handle_metadata.app_id
285
- return function_identifier
254
+ get_by_id_response = await client.stub.FunctionGetById(
255
+ api_pb2.FunctionGetByIdRequest(function_id=function_identifier)
256
+ )
257
+ return function_identifier, get_by_id_response.handle_metadata
286
258
 
287
259
  raise UsageError(usage)
@@ -76,15 +76,23 @@ proxy_tokens_cli = ModalGroup(name="proxy-tokens", help=PROXY_TOKENS_HELP_TEXT)
76
76
  workspace_cli.add_command(proxy_tokens_cli)
77
77
 
78
78
 
79
- @proxy_tokens_cli.command("create", help="Create a proxy token in the current Workspace.")
79
+ @proxy_tokens_cli.command("create")
80
+ @click.option("--name", default="", help="Name to help identify the token.")
80
81
  @click.option("--json", is_flag=True, default=False)
81
- def proxy_tokens_create(*, json: bool = False):
82
+ def proxy_tokens_create(*, name: str = "", json: bool = False):
82
83
  """Create a proxy token in the current Workspace.
83
84
 
84
85
  The new token's ID and secret will be printed to stdout. The secret is only
85
86
  shown at creation time and cannot be retrieved later.
87
+
88
+ Examples:
89
+
90
+ ```
91
+ modal workspace proxy-tokens create --name production-webhooks
92
+ modal workspace proxy-tokens create --json
93
+ ```
86
94
  """
87
- token = Workspace.from_context().proxy_tokens.create()
95
+ token = Workspace.from_context().proxy_tokens.create(name=name)
88
96
  output_manager = OutputManager.get()
89
97
  bearer = f"Bearer {token.token_id}.{token.token_secret}"
90
98
  if json:
@@ -104,7 +112,7 @@ def proxy_tokens_create(*, json: bool = False):
104
112
  output_manager.print(table)
105
113
 
106
114
 
107
- @proxy_tokens_cli.command("list", help="List the proxy tokens of the current Workspace.")
115
+ @proxy_tokens_cli.command("list")
108
116
  @click.option(
109
117
  "-e",
110
118
  "--environment",
@@ -113,39 +121,75 @@ def proxy_tokens_create(*, json: bool = False):
113
121
  )
114
122
  @click.option("--json", is_flag=True, default=False)
115
123
  def proxy_tokens_list(environment: str | None = None, json: bool = False):
124
+ """List the proxy tokens of the current Workspace.
125
+
126
+ Examples:
127
+
128
+ ```
129
+ modal workspace proxy-tokens list
130
+ modal workspace proxy-tokens list --environment prod
131
+ modal workspace proxy-tokens list --json
132
+ ```
133
+ """
116
134
  tokens = Workspace.from_context().proxy_tokens.list(environment_name=environment)
117
135
  # Emit a real boolean for JSON output, but a string for the rich table (which can't render a bare bool).
118
136
  rows = [
119
137
  [
138
+ token.name,
120
139
  token.token_id,
121
140
  timestamp_to_localized_str(token.created_at.timestamp(), json),
141
+ token.created_by,
122
142
  token.scoped if json else str(token.scoped),
123
143
  ]
124
144
  for token in tokens
125
145
  ]
126
- display_table(["Token ID", "Created at", "Scoped"], rows, json=json)
146
+ display_table(["Name", "Token ID", "Created at", "Created by", "Scoped"], rows, json=json)
127
147
 
128
148
 
129
- @proxy_tokens_cli.command("allow", help="Allow a proxy token to authenticate to an environment.", no_args_is_help=True)
149
+ @proxy_tokens_cli.command("allow", no_args_is_help=True)
130
150
  @click.argument("token_id")
131
151
  @click.argument("environment_name")
132
152
  def proxy_tokens_allow(token_id: str, environment_name: str):
153
+ """Allow a proxy token to authenticate to an environment.
154
+
155
+ Example:
156
+
157
+ ```
158
+ modal workspace proxy-tokens allow wk-123 prod
159
+ ```
160
+ """
133
161
  Workspace.from_context().proxy_tokens.allow(token_id, environment_name)
134
162
  rich.print(f"[green]✓[/green] Allowed proxy token {token_id!r} to authenticate to environment {environment_name!r}")
135
163
 
136
164
 
137
- @proxy_tokens_cli.command("revoke", help="Revoke a proxy token's access to an environment.", no_args_is_help=True)
165
+ @proxy_tokens_cli.command("revoke", no_args_is_help=True)
138
166
  @click.argument("token_id")
139
167
  @click.argument("environment_name")
140
168
  def proxy_tokens_revoke(token_id: str, environment_name: str):
169
+ """Revoke a proxy token's access to an environment.
170
+
171
+ Example:
172
+
173
+ ```
174
+ modal workspace proxy-tokens revoke wk-123 prod
175
+ ```
176
+ """
141
177
  Workspace.from_context().proxy_tokens.revoke(token_id, environment_name)
142
178
  rich.print(f"[green]✓[/green] Revoked proxy token {token_id!r} access to environment {environment_name!r}")
143
179
 
144
180
 
145
- @proxy_tokens_cli.command("delete", help="Delete a proxy token from the current Workspace.", no_args_is_help=True)
181
+ @proxy_tokens_cli.command("delete", no_args_is_help=True)
146
182
  @click.argument("token_id")
147
183
  @yes_option
148
184
  def proxy_tokens_delete(token_id: str, yes: bool = False):
185
+ """Delete a proxy token from the current Workspace.
186
+
187
+ Example:
188
+
189
+ ```
190
+ modal workspace proxy-tokens delete wk-123
191
+ ```
192
+ """
149
193
  if not yes:
150
194
  message = (
151
195
  f"Are you sure you want to delete proxy token {token_id!r}? Requests using this token will be rejected."
@@ -77,7 +77,7 @@ class _Client:
77
77
  server_url: str,
78
78
  client_type: int,
79
79
  credentials: typing.Optional[tuple[str, str]],
80
- version: str = "1.5.6.dev16",
80
+ version: str = "1.5.6.dev18",
81
81
  *,
82
82
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
83
83
  ):
@@ -277,7 +277,7 @@ class Client:
277
277
  server_url: str,
278
278
  client_type: int,
279
279
  credentials: typing.Optional[tuple[str, str]],
280
- version: str = "1.5.6.dev16",
280
+ version: str = "1.5.6.dev18",
281
281
  *,
282
282
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
283
283
  ):
@@ -310,6 +310,8 @@ class ProxyTokenInfo:
310
310
  token_id: str
311
311
  created_at: datetime
312
312
  scoped: bool
313
+ name: str = ""
314
+ created_by: str = ""
313
315
 
314
316
 
315
317
  @dataclass(frozen=True)
@@ -12,8 +12,12 @@ class ProxyTokenInfo:
12
12
  token_id: str
13
13
  created_at: datetime.datetime
14
14
  scoped: bool
15
+ name: str
16
+ created_by: str
15
17
 
16
- def __init__(self, token_id: str, created_at: datetime.datetime, scoped: bool) -> None:
18
+ def __init__(
19
+ self, token_id: str, created_at: datetime.datetime, scoped: bool, name: str = "", created_by: str = ""
20
+ ) -> None:
17
21
  """Initialize self. See help(type(self)) for accurate signature."""
18
22
  ...
19
23
 
@@ -395,23 +399,29 @@ class WorkspaceProxyTokenManager:
395
399
  ...
396
400
 
397
401
  class __create_spec(typing_extensions.Protocol):
398
- def __call__(self, /) -> modal.types.TokenData:
402
+ def __call__(self, /, name: str = "") -> modal.types.TokenData:
399
403
  """Create a new proxy token for the Workspace.
400
404
 
405
+ Args:
406
+ name: An optional name to help identify the token.
407
+
401
408
  Examples:
402
409
  ```python notest
403
- token = modal.Workspace.from_context().proxy_tokens.create()
410
+ token = modal.Workspace.from_context().proxy_tokens.create(name="production-webhooks")
404
411
  print(token.token_id, token.token_secret)
405
412
  ```
406
413
  """
407
414
  ...
408
415
 
409
- async def aio(self, /) -> modal.types.TokenData:
416
+ async def aio(self, /, name: str = "") -> modal.types.TokenData:
410
417
  """Create a new proxy token for the Workspace.
411
418
 
419
+ Args:
420
+ name: An optional name to help identify the token.
421
+
412
422
  Examples:
413
423
  ```python notest
414
- token = modal.Workspace.from_context().proxy_tokens.create()
424
+ token = modal.Workspace.from_context().proxy_tokens.create(name="production-webhooks")
415
425
  print(token.token_id, token.token_secret)
416
426
  ```
417
427
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev16
3
+ Version: 1.5.6.dev18
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0