modal 1.6.4.dev1__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.dev1 → modal-1.6.4.dev2}/PKG-INFO +1 -1
  2. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/app.py +84 -1
  3. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/app.pyi +154 -0
  4. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/endpoint.py +109 -0
  5. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/client.pyi +2 -2
  6. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/types.py +21 -1
  7. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/PKG-INFO +1 -1
  8. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2.py +535 -534
  9. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2.pyi +35 -57
  10. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_version/__init__.py +1 -1
  11. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/LICENSE +0 -0
  12. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/README.md +0 -0
  13. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/__init__.py +0 -0
  14. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/__main__.py +0 -0
  15. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_billing.py +0 -0
  16. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_cluster.py +0 -0
  17. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_cluster.pyi +0 -0
  18. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_container_entrypoint.py +0 -0
  19. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_environments.py +0 -0
  20. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_function_variants.py +0 -0
  21. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_functions.py +0 -0
  22. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_grpc_client.py +0 -0
  23. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_image.py +0 -0
  24. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_ipython.py +0 -0
  25. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_load_context.py +0 -0
  26. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_location.py +0 -0
  27. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_logs.py +0 -0
  28. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_logs_manager.py +0 -0
  29. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_logs_manager.pyi +0 -0
  30. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_metrics.py +0 -0
  31. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_metrics.pyi +0 -0
  32. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_object.py +0 -0
  33. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_outbound_policy.py +0 -0
  34. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_outbound_policy.pyi +0 -0
  35. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/__init__.py +0 -0
  36. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/manager.py +0 -0
  37. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/pty.py +0 -0
  38. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/rich.py +0 -0
  39. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_output/status.py +0 -0
  40. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_partial_function.py +0 -0
  41. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_resolver.py +0 -0
  42. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_resources.py +0 -0
  43. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/__init__.py +0 -0
  44. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/asgi.py +0 -0
  45. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/container_io_manager.py +0 -0
  46. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
  47. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/execution_context.py +0 -0
  48. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/execution_context.pyi +0 -0
  49. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  50. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/task_lifecycle_manager.py +0 -0
  51. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  52. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/telemetry.py +0 -0
  53. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/user_code_event_loop.py +0 -0
  54. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_runtime/user_code_imports.py +0 -0
  55. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_serialization.py +0 -0
  56. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_server.py +0 -0
  57. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_server.pyi +0 -0
  58. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_supports_logs.py +0 -0
  59. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_traceback.py +0 -0
  60. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_tunnel.py +0 -0
  61. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_tunnel.pyi +0 -0
  62. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_type_manager.py +0 -0
  63. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/__init__.py +0 -0
  64. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/app_utils.py +0 -0
  65. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/async_utils.py +0 -0
  66. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/auth_token_manager.py +0 -0
  67. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/blob_utils.py +0 -0
  68. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/browser_utils.py +0 -0
  69. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
  70. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/curl_utils.py +0 -0
  71. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/deprecation.py +0 -0
  72. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/display_utils.py +0 -0
  73. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/docker_utils.py +0 -0
  74. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/function_utils.py +0 -0
  75. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/git_utils.py +0 -0
  76. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/grpc_testing.py +0 -0
  77. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/grpc_utils.py +0 -0
  78. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/hash_utils.py +0 -0
  79. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/http_utils.py +0 -0
  80. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/idle_countdown.py +0 -0
  81. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/jwt_utils.py +0 -0
  82. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/logger.py +0 -0
  83. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/mount_utils.py +0 -0
  84. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/name_utils.py +0 -0
  85. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/oauth.py +0 -0
  86. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/package_utils.py +0 -0
  87. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/pattern_utils.py +0 -0
  88. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/proxy_support.py +0 -0
  89. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/rand_pb_testing.py +0 -0
  90. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/sandbox_fs_utils.py +0 -0
  91. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/shell_utils.py +0 -0
  92. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_utils/time_utils.py +0 -0
  93. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/__init__.py +0 -0
  94. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  95. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/cloudpickle.py +0 -0
  96. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/tblib.py +0 -0
  97. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_vendor/version.py +0 -0
  98. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_watcher.py +0 -0
  99. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/_workspace.py +0 -0
  100. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/billing.py +0 -0
  101. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/billing.pyi +0 -0
  102. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2023.12.312.txt +0 -0
  103. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2023.12.txt +0 -0
  104. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2024.04.txt +0 -0
  105. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2024.10.txt +0 -0
  106. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2025.06.txt +0 -0
  107. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/2026.10.txt +0 -0
  108. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/PREVIEW.txt +0 -0
  109. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/README.md +0 -0
  110. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/builder/base-images.json +0 -0
  111. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/call_graph.py +0 -0
  112. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/__init__.py +0 -0
  113. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_download.py +0 -0
  114. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_help.py +0 -0
  115. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_logs.py +0 -0
  116. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_stats.py +0 -0
  117. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/_traceback.py +0 -0
  118. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/app.py +0 -0
  119. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/billing.py +0 -0
  120. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/changelog.py +0 -0
  121. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/config.py +0 -0
  122. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/container.py +0 -0
  123. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/curl.py +0 -0
  124. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/dashboard.py +0 -0
  125. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/dict.py +0 -0
  126. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/entry_point.py +0 -0
  127. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/environment.py +0 -0
  128. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/function.py +0 -0
  129. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/image.py +0 -0
  130. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/import_refs.py +0 -0
  131. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/logo.py +0 -0
  132. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/network_file_system.py +0 -0
  133. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/profile.py +0 -0
  134. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/queues.py +0 -0
  135. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/run.py +0 -0
  136. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/secret.py +0 -0
  137. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/selector.py +0 -0
  138. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/server.py +0 -0
  139. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/shell.py +0 -0
  140. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/skills.py +0 -0
  141. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/token.py +0 -0
  142. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/utils.py +0 -0
  143. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/volume.py +0 -0
  144. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cli/workspace.py +0 -0
  145. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/client.py +0 -0
  146. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cloud_bucket_mount.py +0 -0
  147. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cloud_bucket_mount.pyi +0 -0
  148. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cls.py +0 -0
  149. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cls.pyi +0 -0
  150. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cluster.py +0 -0
  151. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/cluster.pyi +0 -0
  152. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/config.py +0 -0
  153. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/container_process.py +0 -0
  154. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/container_process.pyi +0 -0
  155. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/dict.py +0 -0
  156. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/dict.pyi +0 -0
  157. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/environments.py +0 -0
  158. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/environments.pyi +0 -0
  159. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/exception.py +0 -0
  160. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/__init__.py +0 -0
  161. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/_flash_container_entry.py +0 -0
  162. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash.py +0 -0
  163. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash.pyi +0 -0
  164. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash_server.py +0 -0
  165. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/flash_server.pyi +0 -0
  166. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/experimental/ipython.py +0 -0
  167. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/file_pattern_matcher.py +0 -0
  168. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/functions.py +0 -0
  169. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/functions.pyi +0 -0
  170. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/image.py +0 -0
  171. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/image.pyi +0 -0
  172. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/io_streams.py +0 -0
  173. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/io_streams.pyi +0 -0
  174. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/mount.py +0 -0
  175. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/mount.pyi +0 -0
  176. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/network_file_system.py +0 -0
  177. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/network_file_system.pyi +0 -0
  178. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/object.py +0 -0
  179. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/object.pyi +0 -0
  180. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/output.py +0 -0
  181. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/parallel_map.py +0 -0
  182. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/parallel_map.pyi +0 -0
  183. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/partial_function.py +0 -0
  184. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/partial_function.pyi +0 -0
  185. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/proxy.py +0 -0
  186. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/proxy.pyi +0 -0
  187. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/py.typed +0 -0
  188. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/queue.py +0 -0
  189. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/queue.pyi +0 -0
  190. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/retries.py +0 -0
  191. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/runner.py +0 -0
  192. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/runner.pyi +0 -0
  193. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/running_app.py +0 -0
  194. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/__init__.py +0 -0
  195. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_common.py +0 -0
  196. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_filesystem.py +0 -0
  197. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_impl_persistent.py +0 -0
  198. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_impl_v1.py +0 -0
  199. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_impl_v2.py +0 -0
  200. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_sandbox.py +0 -0
  201. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_sidecar.py +0 -0
  202. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_snapshot.py +0 -0
  203. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/_task_command_router_client.py +0 -0
  204. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/sandbox.py +0 -0
  205. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/sandbox/sandbox.pyi +0 -0
  206. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/schedule.py +0 -0
  207. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/scheduler_placement.py +0 -0
  208. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/secret.py +0 -0
  209. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/secret.pyi +0 -0
  210. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/server.py +0 -0
  211. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/server.pyi +0 -0
  212. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/serving.py +0 -0
  213. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/serving.pyi +0 -0
  214. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/skills/modal/SKILL.md +0 -0
  215. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/stream_type.py +0 -0
  216. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/token_flow.py +0 -0
  217. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/token_flow.pyi +0 -0
  218. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/volume.py +0 -0
  219. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/volume.pyi +0 -0
  220. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/workspace.py +0 -0
  221. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal/workspace.pyi +0 -0
  222. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/SOURCES.txt +0 -0
  223. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/dependency_links.txt +0 -0
  224. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/entry_points.txt +0 -0
  225. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/requires.txt +0 -0
  226. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal.egg-info/top_level.txt +0 -0
  227. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/__init__.py +0 -0
  228. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_cli_docs.py +0 -0
  229. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_cli_docs_main.py +0 -0
  230. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_reference_docs.py +0 -0
  231. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/gen_reference_docs_main.py +0 -0
  232. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/__init__.py +0 -0
  233. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/mdmd.py +0 -0
  234. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/signatures.py +0 -0
  235. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/mdmd/types.py +0 -0
  236. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_docs/reference_intro.md +0 -0
  237. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/__init__.py +0 -0
  238. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_grpc.py +0 -0
  239. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2_grpc.py +0 -0
  240. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
  241. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/modal_api_grpc.py +0 -0
  242. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/py.typed +0 -0
  243. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_grpc.py +0 -0
  244. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2.py +0 -0
  245. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
  246. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  247. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  248. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/modal_version/__main__.py +0 -0
  249. {modal-1.6.4.dev1 → modal-1.6.4.dev2}/pyproject.toml +0 -0
  250. {modal-1.6.4.dev1 → 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.dev1
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.dev1",
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.dev1",
307
+ version: str = "1.6.4.dev2",
308
308
  *,
309
309
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
310
310
  ):
@@ -215,7 +215,10 @@ class StatsPercentileDistribution:
215
215
 
216
216
  @classmethod
217
217
  def _from_proto(cls, proto: api_pb2.StatsPercentileDistribution) -> "StatsPercentileDistribution":
218
- 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
+ )
219
222
 
220
223
 
221
224
  @dataclass(frozen=True)
@@ -571,6 +574,23 @@ class BillingReportItem:
571
574
  )
572
575
 
573
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
+
574
594
  @dataclass(slots=True, frozen=True)
575
595
  class WorkspaceBillingSummary:
576
596
  start: datetime
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.6.4.dev1
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