modal 1.6.4.dev0__tar.gz → 1.6.4.dev2__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 (250) hide show
  1. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/PKG-INFO +1 -1
  2. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/app.py +84 -1
  3. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/app.pyi +154 -0
  4. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/endpoint.py +109 -0
  5. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/client.pyi +2 -2
  6. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/functions.pyi +6 -6
  7. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/secret.py +15 -7
  8. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/secret.pyi +3 -3
  9. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/types.py +22 -1
  10. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal.egg-info/PKG-INFO +1 -1
  11. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/api_pb2.py +535 -534
  12. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/api_pb2.pyi +42 -58
  13. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_version/__init__.py +1 -1
  14. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/pyproject.toml +2 -2
  15. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/LICENSE +0 -0
  16. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/README.md +0 -0
  17. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/__init__.py +0 -0
  18. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/__main__.py +0 -0
  19. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_billing.py +0 -0
  20. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_cluster.py +0 -0
  21. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_cluster.pyi +0 -0
  22. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_container_entrypoint.py +0 -0
  23. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_environments.py +0 -0
  24. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_function_variants.py +0 -0
  25. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_functions.py +0 -0
  26. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_grpc_client.py +0 -0
  27. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_image.py +0 -0
  28. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_ipython.py +0 -0
  29. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_load_context.py +0 -0
  30. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_location.py +0 -0
  31. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_logs.py +0 -0
  32. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_logs_manager.py +0 -0
  33. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_logs_manager.pyi +0 -0
  34. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_metrics.py +0 -0
  35. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_metrics.pyi +0 -0
  36. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_object.py +0 -0
  37. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_outbound_policy.py +0 -0
  38. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_outbound_policy.pyi +0 -0
  39. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_output/__init__.py +0 -0
  40. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_output/manager.py +0 -0
  41. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_output/pty.py +0 -0
  42. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_output/rich.py +0 -0
  43. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_output/status.py +0 -0
  44. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_partial_function.py +0 -0
  45. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_resolver.py +0 -0
  46. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_resources.py +0 -0
  47. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/__init__.py +0 -0
  48. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/asgi.py +0 -0
  49. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/container_io_manager.py +0 -0
  50. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
  51. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/execution_context.py +0 -0
  52. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/execution_context.pyi +0 -0
  53. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  54. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/task_lifecycle_manager.py +0 -0
  55. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  56. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/telemetry.py +0 -0
  57. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/user_code_event_loop.py +0 -0
  58. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_runtime/user_code_imports.py +0 -0
  59. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_serialization.py +0 -0
  60. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_server.py +0 -0
  61. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_server.pyi +0 -0
  62. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_supports_logs.py +0 -0
  63. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_traceback.py +0 -0
  64. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_tunnel.py +0 -0
  65. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_tunnel.pyi +0 -0
  66. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_type_manager.py +0 -0
  67. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/__init__.py +0 -0
  68. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/app_utils.py +0 -0
  69. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/async_utils.py +0 -0
  70. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/auth_token_manager.py +0 -0
  71. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/blob_utils.py +0 -0
  72. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/browser_utils.py +0 -0
  73. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
  74. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/curl_utils.py +0 -0
  75. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/deprecation.py +0 -0
  76. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/display_utils.py +0 -0
  77. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/docker_utils.py +0 -0
  78. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/function_utils.py +0 -0
  79. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/git_utils.py +0 -0
  80. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/grpc_testing.py +0 -0
  81. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/grpc_utils.py +0 -0
  82. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/hash_utils.py +0 -0
  83. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/http_utils.py +0 -0
  84. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/idle_countdown.py +0 -0
  85. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/jwt_utils.py +0 -0
  86. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/logger.py +0 -0
  87. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/mount_utils.py +0 -0
  88. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/name_utils.py +0 -0
  89. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/oauth.py +0 -0
  90. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/package_utils.py +0 -0
  91. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/pattern_utils.py +0 -0
  92. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/proxy_support.py +0 -0
  93. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/rand_pb_testing.py +0 -0
  94. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/sandbox_fs_utils.py +0 -0
  95. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/shell_utils.py +0 -0
  96. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_utils/time_utils.py +0 -0
  97. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_vendor/__init__.py +0 -0
  98. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  99. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_vendor/cloudpickle.py +0 -0
  100. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_vendor/tblib.py +0 -0
  101. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_vendor/version.py +0 -0
  102. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_watcher.py +0 -0
  103. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/_workspace.py +0 -0
  104. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/billing.py +0 -0
  105. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/billing.pyi +0 -0
  106. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/2023.12.312.txt +0 -0
  107. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/2023.12.txt +0 -0
  108. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/2024.04.txt +0 -0
  109. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/2024.10.txt +0 -0
  110. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/2025.06.txt +0 -0
  111. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/2026.10.txt +0 -0
  112. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/PREVIEW.txt +0 -0
  113. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/README.md +0 -0
  114. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/builder/base-images.json +0 -0
  115. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/call_graph.py +0 -0
  116. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/__init__.py +0 -0
  117. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/_download.py +0 -0
  118. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/_help.py +0 -0
  119. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/_logs.py +0 -0
  120. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/_stats.py +0 -0
  121. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/_traceback.py +0 -0
  122. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/app.py +0 -0
  123. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/billing.py +0 -0
  124. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/changelog.py +0 -0
  125. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/config.py +0 -0
  126. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/container.py +0 -0
  127. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/curl.py +0 -0
  128. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/dashboard.py +0 -0
  129. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/dict.py +0 -0
  130. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/entry_point.py +0 -0
  131. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/environment.py +0 -0
  132. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/function.py +0 -0
  133. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/image.py +0 -0
  134. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/import_refs.py +0 -0
  135. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/logo.py +0 -0
  136. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/network_file_system.py +0 -0
  137. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/profile.py +0 -0
  138. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/queues.py +0 -0
  139. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/run.py +0 -0
  140. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/secret.py +0 -0
  141. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/selector.py +0 -0
  142. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/server.py +0 -0
  143. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/shell.py +0 -0
  144. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/skills.py +0 -0
  145. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/token.py +0 -0
  146. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/utils.py +0 -0
  147. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/volume.py +0 -0
  148. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cli/workspace.py +0 -0
  149. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/client.py +0 -0
  150. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cloud_bucket_mount.py +0 -0
  151. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cloud_bucket_mount.pyi +0 -0
  152. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cls.py +0 -0
  153. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cls.pyi +0 -0
  154. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cluster.py +0 -0
  155. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/cluster.pyi +0 -0
  156. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/config.py +0 -0
  157. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/container_process.py +0 -0
  158. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/container_process.pyi +0 -0
  159. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/dict.py +0 -0
  160. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/dict.pyi +0 -0
  161. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/environments.py +0 -0
  162. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/environments.pyi +0 -0
  163. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/exception.py +0 -0
  164. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/__init__.py +0 -0
  165. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/_flash_container_entry.py +0 -0
  166. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/flash.py +0 -0
  167. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/flash.pyi +0 -0
  168. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/flash_server.py +0 -0
  169. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/flash_server.pyi +0 -0
  170. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/experimental/ipython.py +0 -0
  171. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/file_pattern_matcher.py +0 -0
  172. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/functions.py +0 -0
  173. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/image.py +0 -0
  174. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/image.pyi +0 -0
  175. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/io_streams.py +0 -0
  176. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/io_streams.pyi +0 -0
  177. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/mount.py +0 -0
  178. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/mount.pyi +0 -0
  179. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/network_file_system.py +0 -0
  180. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/network_file_system.pyi +0 -0
  181. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/object.py +0 -0
  182. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/object.pyi +0 -0
  183. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/output.py +0 -0
  184. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/parallel_map.py +0 -0
  185. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/parallel_map.pyi +0 -0
  186. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/partial_function.py +0 -0
  187. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/partial_function.pyi +0 -0
  188. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/proxy.py +0 -0
  189. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/proxy.pyi +0 -0
  190. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/py.typed +0 -0
  191. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/queue.py +0 -0
  192. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/queue.pyi +0 -0
  193. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/retries.py +0 -0
  194. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/runner.py +0 -0
  195. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/runner.pyi +0 -0
  196. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/running_app.py +0 -0
  197. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/__init__.py +0 -0
  198. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_common.py +0 -0
  199. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_filesystem.py +0 -0
  200. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_impl_persistent.py +0 -0
  201. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_impl_v1.py +0 -0
  202. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_impl_v2.py +0 -0
  203. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_sandbox.py +0 -0
  204. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_sidecar.py +0 -0
  205. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_snapshot.py +0 -0
  206. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/_task_command_router_client.py +0 -0
  207. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/sandbox.py +0 -0
  208. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/sandbox/sandbox.pyi +0 -0
  209. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/schedule.py +0 -0
  210. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/scheduler_placement.py +0 -0
  211. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/server.py +0 -0
  212. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/server.pyi +0 -0
  213. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/serving.py +0 -0
  214. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/serving.pyi +0 -0
  215. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/skills/modal/SKILL.md +0 -0
  216. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/stream_type.py +0 -0
  217. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/token_flow.py +0 -0
  218. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/token_flow.pyi +0 -0
  219. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/volume.py +0 -0
  220. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/volume.pyi +0 -0
  221. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/workspace.py +0 -0
  222. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal/workspace.pyi +0 -0
  223. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal.egg-info/SOURCES.txt +0 -0
  224. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal.egg-info/dependency_links.txt +0 -0
  225. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal.egg-info/entry_points.txt +0 -0
  226. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal.egg-info/requires.txt +0 -0
  227. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal.egg-info/top_level.txt +0 -0
  228. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/__init__.py +0 -0
  229. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/gen_cli_docs.py +0 -0
  230. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/gen_cli_docs_main.py +0 -0
  231. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/gen_reference_docs.py +0 -0
  232. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/gen_reference_docs_main.py +0 -0
  233. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/mdmd/__init__.py +0 -0
  234. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/mdmd/mdmd.py +0 -0
  235. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/mdmd/signatures.py +0 -0
  236. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/mdmd/types.py +0 -0
  237. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_docs/reference_intro.md +0 -0
  238. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/__init__.py +0 -0
  239. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/api_grpc.py +0 -0
  240. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/api_pb2_grpc.py +0 -0
  241. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
  242. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/modal_api_grpc.py +0 -0
  243. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/py.typed +0 -0
  244. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/task_command_router_grpc.py +0 -0
  245. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2.py +0 -0
  246. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
  247. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  248. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  249. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/modal_version/__main__.py +0 -0
  250. {modal-1.6.4.dev0 → modal-1.6.4.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.6.4.dev0
3
+ Version: 1.6.4.dev2
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -3,6 +3,7 @@ import inspect
3
3
  import typing
4
4
  from collections.abc import AsyncGenerator, Callable, Collection, Coroutine, Mapping, Sequence
5
5
  from dataclasses import dataclass
6
+ from datetime import datetime, timezone
6
7
  from pathlib import PurePosixPath
7
8
  from textwrap import dedent
8
9
  from typing import (
@@ -56,7 +57,7 @@ from .retries import Retries
56
57
  from .running_app import RunningApp
57
58
  from .schedule import Schedule
58
59
  from .secret import _Secret
59
- from .types import AppInfo
60
+ from .types import AppInfo, SandboxGroupStats
60
61
  from .volume import _Volume
61
62
 
62
63
  _default_image: _Image = _Image.debian_slim()
@@ -135,6 +136,82 @@ class _LocalAppState:
135
136
  volumes_default: dict[str | PurePosixPath, _Volume]
136
137
 
137
138
 
139
+ class _AppSandboxesManager:
140
+ """mdmd:namespace"""
141
+
142
+ def __init__(self, app: "_App"):
143
+ """mdmd:hidden"""
144
+ self._app = app
145
+
146
+ async def stats(
147
+ self,
148
+ *,
149
+ since: datetime,
150
+ until: datetime | None = None,
151
+ group_by_tags: Sequence[str] | None = None,
152
+ tags: dict[str, str] | None = None,
153
+ ) -> AsyncGenerator[SandboxGroupStats, None]:
154
+ """Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
155
+
156
+ The maximum time range is 7 days.
157
+
158
+ Args:
159
+ since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
160
+ timezone are interpreted as local time.
161
+ until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
162
+ the current time. Values without a timezone are interpreted as local time.
163
+ group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
164
+ a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
165
+ as a single group.
166
+ tags: Only include Sandboxes that have at least these tags.
167
+
168
+ Yields:
169
+ `SandboxGroupStats` objects, one per combination of tag values, with the following fields:
170
+ - `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
171
+ - `cost`: total cost in USD, the sum of `cost_by_resource`.
172
+ - `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
173
+ `H100`. The resource names are subject to change as Modal's billing model evolves.
174
+
175
+ Raises:
176
+ InvalidError: If the time range is invalid or there are more than 10,000 groups.
177
+
178
+ Examples:
179
+ ```python notest
180
+ app = modal.App.lookup("my-app")
181
+ since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
182
+ for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
183
+ print(group.tags, group.cost, group.cost_by_resource)
184
+ ```
185
+ """
186
+ if self._app._app_id is None:
187
+ raise InvalidError("`App.sandboxes.stats` requires a running, deployed or looked-up App.")
188
+ if until is not None:
189
+ if until.tzinfo is None:
190
+ until = until.astimezone()
191
+ until = until.astimezone(timezone.utc)
192
+ if since.tzinfo is None:
193
+ since = since.astimezone()
194
+ since = since.astimezone(timezone.utc)
195
+ if since >= datetime.now(timezone.utc):
196
+ raise InvalidError("`since` must be in the past.")
197
+ if until is not None and since >= until:
198
+ raise InvalidError("`since` must be before `until`.")
199
+ client = self._app._client or await _Client.from_env()
200
+ request = api_pb2.SandboxGetTimeRangeStatsRequest(
201
+ group_by_tags=list(group_by_tags or []), tags=tags or {}, app_id=self._app._app_id
202
+ )
203
+ request.since.FromDatetime(since)
204
+ if until is not None:
205
+ request.until.FromDatetime(until)
206
+ while True:
207
+ response = await client._stub.SandboxGetTimeRangeStats(request)
208
+ for group in response.groups:
209
+ yield SandboxGroupStats._from_proto(group)
210
+ if not response.next_page_token:
211
+ return
212
+ request.page_token = response.next_page_token
213
+
214
+
138
215
  class _App:
139
216
  """A Modal App is a group of functions and classes that are deployed together.
140
217
 
@@ -1679,6 +1756,11 @@ class _App:
1679
1756
 
1680
1757
  return _AppLogsManager(self)
1681
1758
 
1759
+ @property
1760
+ def sandboxes(self) -> _AppSandboxesManager:
1761
+ """Namespace for the Sandboxes belonging to this App."""
1762
+ return _AppSandboxesManager(self)
1763
+
1682
1764
  async def info(self, refresh: bool = False) -> AppInfo:
1683
1765
  """Return information for a modal `App`.
1684
1766
 
@@ -1711,4 +1793,5 @@ class _App:
1711
1793
  return self._info
1712
1794
 
1713
1795
 
1796
+ AppSandboxesManager = synchronize_api(_AppSandboxesManager)
1714
1797
  App = synchronize_api(_App)
@@ -1,4 +1,5 @@
1
1
  import collections.abc
2
+ import datetime
2
3
  import modal._functions
3
4
  import modal._image
4
5
  import modal._load_context
@@ -115,6 +116,54 @@ class _LocalAppState:
115
116
  """Return self==value."""
116
117
  ...
117
118
 
119
+ class _AppSandboxesManager:
120
+ """mdmd:namespace"""
121
+ def __init__(self, app: _App):
122
+ """mdmd:hidden"""
123
+ ...
124
+
125
+ def stats(
126
+ self,
127
+ *,
128
+ since: datetime.datetime,
129
+ until: typing.Optional[datetime.datetime] = None,
130
+ group_by_tags: typing.Optional[collections.abc.Sequence[str]] = None,
131
+ tags: typing.Optional[dict[str, str]] = None,
132
+ ) -> collections.abc.AsyncGenerator[modal.types.SandboxGroupStats, None]:
133
+ """Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
134
+
135
+ The maximum time range is 7 days.
136
+
137
+ Args:
138
+ since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
139
+ timezone are interpreted as local time.
140
+ until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
141
+ the current time. Values without a timezone are interpreted as local time.
142
+ group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
143
+ a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
144
+ as a single group.
145
+ tags: Only include Sandboxes that have at least these tags.
146
+
147
+ Yields:
148
+ `SandboxGroupStats` objects, one per combination of tag values, with the following fields:
149
+ - `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
150
+ - `cost`: total cost in USD, the sum of `cost_by_resource`.
151
+ - `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
152
+ `H100`. The resource names are subject to change as Modal's billing model evolves.
153
+
154
+ Raises:
155
+ InvalidError: If the time range is invalid or there are more than 10,000 groups.
156
+
157
+ Examples:
158
+ ```python notest
159
+ app = modal.App.lookup("my-app")
160
+ since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
161
+ for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
162
+ print(group.tags, group.cost, group.cost_by_resource)
163
+ ```
164
+ """
165
+ ...
166
+
118
167
  class _App:
119
168
  """A Modal App is a group of functions and classes that are deployed together.
120
169
 
@@ -953,6 +1002,11 @@ class _App:
953
1002
  """
954
1003
  ...
955
1004
 
1005
+ @property
1006
+ def sandboxes(self) -> _AppSandboxesManager:
1007
+ """Namespace for the Sandboxes belonging to this App."""
1008
+ ...
1009
+
956
1010
  async def info(self, refresh: bool = False) -> modal.types.AppInfo:
957
1011
  """Return information for a modal `App`.
958
1012
 
@@ -971,6 +1025,101 @@ class _App:
971
1025
  """
972
1026
  ...
973
1027
 
1028
+ class AppSandboxesManager:
1029
+ """mdmd:namespace"""
1030
+ def __init__(self, app: App):
1031
+ """mdmd:hidden"""
1032
+ ...
1033
+
1034
+ class __stats_spec(typing_extensions.Protocol):
1035
+ def __call__(
1036
+ self,
1037
+ /,
1038
+ *,
1039
+ since: datetime.datetime,
1040
+ until: typing.Optional[datetime.datetime] = None,
1041
+ group_by_tags: typing.Optional[collections.abc.Sequence[str]] = None,
1042
+ tags: typing.Optional[dict[str, str]] = None,
1043
+ ) -> typing.Generator[modal.types.SandboxGroupStats, None, None]:
1044
+ """Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
1045
+
1046
+ The maximum time range is 7 days.
1047
+
1048
+ Args:
1049
+ since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
1050
+ timezone are interpreted as local time.
1051
+ until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
1052
+ the current time. Values without a timezone are interpreted as local time.
1053
+ group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
1054
+ a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
1055
+ as a single group.
1056
+ tags: Only include Sandboxes that have at least these tags.
1057
+
1058
+ Yields:
1059
+ `SandboxGroupStats` objects, one per combination of tag values, with the following fields:
1060
+ - `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
1061
+ - `cost`: total cost in USD, the sum of `cost_by_resource`.
1062
+ - `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
1063
+ `H100`. The resource names are subject to change as Modal's billing model evolves.
1064
+
1065
+ Raises:
1066
+ InvalidError: If the time range is invalid or there are more than 10,000 groups.
1067
+
1068
+ Examples:
1069
+ ```python notest
1070
+ app = modal.App.lookup("my-app")
1071
+ since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
1072
+ for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
1073
+ print(group.tags, group.cost, group.cost_by_resource)
1074
+ ```
1075
+ """
1076
+ ...
1077
+
1078
+ def aio(
1079
+ self,
1080
+ /,
1081
+ *,
1082
+ since: datetime.datetime,
1083
+ until: typing.Optional[datetime.datetime] = None,
1084
+ group_by_tags: typing.Optional[collections.abc.Sequence[str]] = None,
1085
+ tags: typing.Optional[dict[str, str]] = None,
1086
+ ) -> collections.abc.AsyncGenerator[modal.types.SandboxGroupStats, None]:
1087
+ """Return statistics for this App's Sandboxes over a time range, optionally grouped by tag values.
1088
+
1089
+ The maximum time range is 7 days.
1090
+
1091
+ Args:
1092
+ since: The beginning of the time range, inclusive, on a UTC hour boundary. Values without a
1093
+ timezone are interpreted as local time.
1094
+ until: The end of the time range, exclusive, on a UTC hour boundary. If omitted, this defaults to
1095
+ the current time. Values without a timezone are interpreted as local time.
1096
+ group_by_tags: Tag names to group by. Sandboxes with the same values for these names form one group;
1097
+ a name not present on a Sandbox counts as `None`. If omitted, all matching Sandboxes are returned
1098
+ as a single group.
1099
+ tags: Only include Sandboxes that have at least these tags.
1100
+
1101
+ Yields:
1102
+ `SandboxGroupStats` objects, one per combination of tag values, with the following fields:
1103
+ - `tags`: the value of each name in `group_by_tags` for the group's Sandboxes, `None` where not present.
1104
+ - `cost`: total cost in USD, the sum of `cost_by_resource`.
1105
+ - `cost_by_resource`: cost in USD by billed resource: `CPU`, `Memory`, and GPU types such as
1106
+ `H100`. The resource names are subject to change as Modal's billing model evolves.
1107
+
1108
+ Raises:
1109
+ InvalidError: If the time range is invalid or there are more than 10,000 groups.
1110
+
1111
+ Examples:
1112
+ ```python notest
1113
+ app = modal.App.lookup("my-app")
1114
+ since = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
1115
+ for group in app.sandboxes.stats(since=since, group_by_tags=["team"]):
1116
+ print(group.tags, group.cost, group.cost_by_resource)
1117
+ ```
1118
+ """
1119
+ ...
1120
+
1121
+ stats: __stats_spec
1122
+
974
1123
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
975
1124
 
976
1125
  class App:
@@ -2073,6 +2222,11 @@ class App:
2073
2222
  """
2074
2223
  ...
2075
2224
 
2225
+ @property
2226
+ def sandboxes(self) -> AppSandboxesManager:
2227
+ """Namespace for the Sandboxes belonging to this App."""
2228
+ ...
2229
+
2076
2230
  class __info_spec(typing_extensions.Protocol):
2077
2231
  def __call__(self, /, refresh: bool = False) -> modal.types.AppInfo:
2078
2232
  """Return information for a modal `App`.
@@ -5,6 +5,7 @@ from json import dumps
5
5
  from typing import Optional
6
6
 
7
7
  import click
8
+ from rich.box import SIMPLE
8
9
  from rich.table import Column, Table
9
10
  from rich.text import Text
10
11
 
@@ -21,6 +22,7 @@ from modal_proto import api_pb2
21
22
 
22
23
  from ._help import ModalGroup
23
24
  from ._logs import _validate_logs_args
25
+ from ._stats import STATS_METADATA_STYLE
24
26
  from .server import _run_server_logs, _run_server_stats, _server_stats_time_range
25
27
 
26
28
  _ENDPOINT_HELP = """
@@ -41,6 +43,11 @@ _SERVING_MODE_LABELS = {
41
43
  api_pb2.ENDPOINT_SERVING_MODE_DEDICATED: "dedicated",
42
44
  api_pb2.ENDPOINT_SERVING_MODE_SHARED: "shared",
43
45
  }
46
+ _INPUT_MODALITY_LABELS = {
47
+ api_pb2.ENDPOINT_INPUT_MODALITY_TEXT: "text",
48
+ api_pb2.ENDPOINT_INPUT_MODALITY_IMAGE: "image",
49
+ api_pb2.ENDPOINT_INPUT_MODALITY_AUDIO: "audio",
50
+ }
44
51
  _ENDPOINT_STATUS_LABELS = {
45
52
  api_pb2.EndpointGetInfoResponse.ENDPOINT_STATUS_PROVISIONING: "provisioning",
46
53
  api_pb2.EndpointGetInfoResponse.ENDPOINT_STATUS_LIVE: "live",
@@ -502,6 +509,108 @@ async def stop(
502
509
  output.print(f"[green]✓[/green] Stopped Endpoint {endpoint_id}.")
503
510
 
504
511
 
512
+ @endpoint_cli.command("library", panel="Inspection")
513
+ @click.option(
514
+ "--input-modality",
515
+ type=click.Choice(["text", "image", "audio"]),
516
+ multiple=True,
517
+ help="Only show models that accept any of these inputs. Can be repeated.",
518
+ )
519
+ @click.option("--json", "json_output", is_flag=True, help="Output models as JSON.")
520
+ @synchronizer.create_blocking
521
+ async def library(*, input_modality: tuple[str, ...] = (), json_output: bool = False) -> None:
522
+ """List models you can serve with Modal Endpoints.
523
+
524
+ Models are listed by their Hugging Face repo ID, with the input types they
525
+ accept and whether they're available as shared endpoints, dedicated
526
+ endpoints, or both.
527
+
528
+ This is Modal's catalog of supported models, not the Endpoints in your
529
+ workspace. To see those, run `modal endpoint list`.
530
+
531
+ Examples:
532
+
533
+ List models that support text input modality:
534
+
535
+ ```
536
+ modal endpoint library --input-modality text
537
+ ```
538
+
539
+ Pick a model ID and then create a shared endpoint:
540
+
541
+ ```
542
+ modal endpoint create --model Qwen/Qwen3.8-27B --shared
543
+ ```
544
+ """
545
+ client = await _Client.from_env()
546
+ response = await client._stub.EndpointGetLibrary(api_pb2.EndpointGetLibraryRequest())
547
+ modalities = {modality for modality, label in _INPUT_MODALITY_LABELS.items() if label in input_modality}
548
+
549
+ models = sorted(
550
+ (
551
+ model
552
+ for model in response.models
553
+ if (not modalities or modalities.intersection(model.supported_input_modalities))
554
+ ),
555
+ key=lambda model: model.base_model_repo_id,
556
+ )
557
+ output = OutputManager.get()
558
+ if json_output:
559
+ output.print_json(
560
+ dumps(
561
+ [
562
+ {
563
+ "base_model_repo_id": model.base_model_repo_id,
564
+ "input_modalities": [
565
+ _INPUT_MODALITY_LABELS.get(modality, "unknown")
566
+ for modality in model.supported_input_modalities
567
+ ],
568
+ "shared": api_pb2.ENDPOINT_SERVING_MODE_SHARED in model.available_serving_modes,
569
+ "dedicated": api_pb2.ENDPOINT_SERVING_MODE_DEDICATED in model.available_serving_modes,
570
+ }
571
+ for model in models
572
+ ]
573
+ )
574
+ )
575
+ return
576
+ output.print(
577
+ Text(
578
+ f"Displaying {len(models):,} {'model' if len(models) == 1 else 'models'}",
579
+ style=STATS_METADATA_STYLE,
580
+ )
581
+ )
582
+ if not models:
583
+ output.print(
584
+ "No endpoint-compatible models match the filters."
585
+ if input_modality
586
+ else "No endpoint-compatible models are available."
587
+ )
588
+ return
589
+ display_table(
590
+ [
591
+ Column("Model ID", overflow="fold"),
592
+ "Input Modalities",
593
+ Column("Shared", justify="center"),
594
+ Column("Dedicated", justify="center"),
595
+ ],
596
+ [
597
+ (
598
+ Text.assemble(
599
+ (model.base_model_repo_id, f"link https://huggingface.co/{model.base_model_repo_id}"),
600
+ (" ↗", "bright_blue not underline"),
601
+ ),
602
+ ", ".join(
603
+ _INPUT_MODALITY_LABELS.get(modality, "unknown") for modality in model.supported_input_modalities
604
+ ),
605
+ "✓" if api_pb2.ENDPOINT_SERVING_MODE_SHARED in model.available_serving_modes else "—",
606
+ "✓" if api_pb2.ENDPOINT_SERVING_MODE_DEDICATED in model.available_serving_modes else "—",
607
+ )
608
+ for model in models
609
+ ],
610
+ table_box=SIMPLE,
611
+ )
612
+
613
+
505
614
  @endpoint_cli.command("info", panel="Inspection", no_args_is_help=True)
506
615
  @click.argument("endpoint_identifier")
507
616
  @env_option
@@ -87,7 +87,7 @@ class _Client:
87
87
  server_url: str,
88
88
  client_type: int,
89
89
  credentials: typing.Optional[tuple[str, str]],
90
- version: str = "1.6.4.dev0",
90
+ version: str = "1.6.4.dev2",
91
91
  *,
92
92
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
93
93
  ):
@@ -304,7 +304,7 @@ class Client:
304
304
  server_url: str,
305
305
  client_type: int,
306
306
  credentials: typing.Optional[tuple[str, str]],
307
- version: str = "1.6.4.dev0",
307
+ version: str = "1.6.4.dev2",
308
308
  *,
309
309
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
310
310
  ):
@@ -619,7 +619,7 @@ class Function(
619
619
 
620
620
  _call_generator: ___call_generator_spec
621
621
 
622
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
622
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
623
623
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
624
624
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
625
625
 
@@ -644,7 +644,7 @@ class Function(
644
644
  """
645
645
  ...
646
646
 
647
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
647
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
648
648
 
649
649
  class __remote_gen_spec(typing_extensions.Protocol):
650
650
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -694,7 +694,7 @@ class Function(
694
694
  """
695
695
  ...
696
696
 
697
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
697
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
698
698
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
699
699
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
700
700
 
@@ -727,7 +727,7 @@ class Function(
727
727
  """
728
728
  ...
729
729
 
730
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
730
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
731
731
 
732
732
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
733
733
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -735,7 +735,7 @@ class Function(
735
735
 
736
736
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
737
737
 
738
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
738
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
739
739
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
740
740
  """Calls the function with the given arguments, without waiting for the results.
741
741
 
@@ -768,7 +768,7 @@ class Function(
768
768
  """
769
769
  ...
770
770
 
771
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
771
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
772
772
 
773
773
  @property
774
774
  def _raw_f_(self) -> collections.abc.Callable[..., typing.Any]: ...
@@ -2,7 +2,7 @@
2
2
  import builtins
3
3
  import os
4
4
  from collections.abc import Collection
5
- from datetime import datetime
5
+ from datetime import datetime, timezone
6
6
  from pathlib import Path
7
7
  from typing import Callable
8
8
 
@@ -316,13 +316,15 @@ class _Secret(_Object, type_prefix="st"):
316
316
  ```
317
317
  """
318
318
  if not isinstance(env_dict, dict):
319
- raise InvalidError(ENV_DICT_WRONG_TYPE_ERR)
319
+ raise InvalidError(f"{ENV_DICT_WRONG_TYPE_ERR}, got {type(env_dict).__name__}")
320
320
 
321
321
  env_dict_filtered: dict[str, str] = {k: v for k, v in env_dict.items() if v is not None}
322
- if not all(isinstance(k, str) for k in env_dict_filtered.keys()):
323
- raise InvalidError(ENV_DICT_WRONG_TYPE_ERR)
324
- if not all(isinstance(v, str) for v in env_dict_filtered.values()):
325
- raise InvalidError(ENV_DICT_WRONG_TYPE_ERR)
322
+ for key in env_dict_filtered.keys():
323
+ if not isinstance(key, str):
324
+ raise InvalidError(f"{ENV_DICT_WRONG_TYPE_ERR}, key has type {type(key).__name__}")
325
+ for key, value in env_dict_filtered.items():
326
+ if not isinstance(value, str):
327
+ raise InvalidError(f"{ENV_DICT_WRONG_TYPE_ERR}, value for key {key!r} has type {type(value).__name__}")
326
328
 
327
329
  def _load_env_dict() -> dict[str, str]:
328
330
  return env_dict_filtered
@@ -551,15 +553,21 @@ class _Secret(_Object, type_prefix="st"):
551
553
  return resp.secret_id
552
554
 
553
555
  @live_method
554
- async def info(self) -> SecretInfo:
556
+ async def info(self, *, refresh: bool = False) -> SecretInfo:
555
557
  """Return information about the Secret object."""
558
+ if refresh:
559
+ await self._refresh_metadata()
560
+
556
561
  metadata = self._get_metadata()
557
562
  creation_info = metadata.creation_info
563
+ last_used = datetime.fromtimestamp(metadata.last_used_at, tz=timezone.utc)
564
+
558
565
  return SecretInfo(
559
566
  name=metadata.name or None,
560
567
  created_at=timestamp_to_localized_dt(creation_info.created_at),
561
568
  created_by=creation_info.created_by or None,
562
569
  environment_name=metadata.environment_name,
570
+ last_used=last_used if last_used.timestamp() > 0 else None,
563
571
  )
564
572
 
565
573
  @live_method
@@ -588,7 +588,7 @@ class _Secret(modal._object._Object):
588
588
  """mdmd:hidden"""
589
589
  ...
590
590
 
591
- async def info(self) -> modal.types.SecretInfo:
591
+ async def info(self, *, refresh: bool = False) -> modal.types.SecretInfo:
592
592
  """Return information about the Secret object."""
593
593
  ...
594
594
 
@@ -820,11 +820,11 @@ class Secret(modal.object.Object):
820
820
  _create_deployed: typing.ClassVar[___create_deployed_spec]
821
821
 
822
822
  class __info_spec(typing_extensions.Protocol):
823
- def __call__(self, /) -> modal.types.SecretInfo:
823
+ def __call__(self, /, *, refresh: bool = False) -> modal.types.SecretInfo:
824
824
  """Return information about the Secret object."""
825
825
  ...
826
826
 
827
- async def aio(self, /) -> modal.types.SecretInfo:
827
+ async def aio(self, /, *, refresh: bool = False) -> modal.types.SecretInfo:
828
828
  """Return information about the Secret object."""
829
829
  ...
830
830
 
@@ -165,6 +165,7 @@ class SecretInfo:
165
165
  environment_name: str
166
166
  created_at: datetime
167
167
  created_by: str | None
168
+ last_used: datetime | None
168
169
 
169
170
 
170
171
  class VolumeCreateOptions(TypedDict, total=False):
@@ -214,7 +215,10 @@ class StatsPercentileDistribution:
214
215
 
215
216
  @classmethod
216
217
  def _from_proto(cls, proto: api_pb2.StatsPercentileDistribution) -> "StatsPercentileDistribution":
217
- return cls(unit=proto.unit, percentiles=[StatsPercentile._from_proto(p) for p in proto.percentiles])
218
+ return cls(
219
+ unit=proto.unit,
220
+ percentiles=[StatsPercentile._from_proto(p) for p in proto.percentiles],
221
+ )
218
222
 
219
223
 
220
224
  @dataclass(frozen=True)
@@ -570,6 +574,23 @@ class BillingReportItem:
570
574
  )
571
575
 
572
576
 
577
+ @dataclass(slots=True, frozen=True)
578
+ class SandboxGroupStats:
579
+ """Statistics for a group of Sandboxes over a time range."""
580
+
581
+ tags: dict[str, str | None]
582
+ cost: float
583
+ cost_by_resource: dict[str, float]
584
+
585
+ @classmethod
586
+ def _from_proto(cls, pb: "api_pb2.SandboxStatsGroup") -> "SandboxGroupStats":
587
+ return cls(
588
+ tags={name: tag.value if tag.HasField("value") else None for name, tag in pb.tags.items()},
589
+ cost=pb.cost,
590
+ cost_by_resource=dict(pb.cost_by_resource),
591
+ )
592
+
593
+
573
594
  @dataclass(slots=True, frozen=True)
574
595
  class WorkspaceBillingSummary:
575
596
  start: datetime
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.6.4.dev0
3
+ Version: 1.6.4.dev2
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0