modal 1.5.1.dev7__tar.gz → 1.5.1.dev8__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 (226) hide show
  1. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/PKG-INFO +1 -1
  2. modal-1.5.1.dev8/modal/_billing.py +155 -0
  3. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_environments.py +76 -1
  4. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_grpc_client.py +3 -3
  5. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/time_utils.py +23 -0
  6. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_workspace.py +75 -1
  7. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/billing.py +3 -2
  8. modal-1.5.1.dev8/modal/billing.pyi +184 -0
  9. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/billing.py +100 -69
  10. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/environment.py +140 -1
  11. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/client.pyi +2 -2
  12. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/environments.py +2 -0
  13. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/environments.pyi +36 -1
  14. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/functions.pyi +6 -6
  15. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/workspace.py +2 -1
  16. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/workspace.pyi +34 -0
  17. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal.egg-info/PKG-INFO +1 -1
  18. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal.egg-info/SOURCES.txt +1 -0
  19. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/api_grpc.py +3 -3
  20. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/api_pb2.py +1113 -1103
  21. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/api_pb2.pyi +21 -0
  22. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/api_pb2_grpc.py +3 -3
  23. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/api_pb2_grpc.pyi +2 -2
  24. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_version/__init__.py +1 -1
  25. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/pyproject.toml +1 -1
  26. modal-1.5.1.dev7/modal/_billing.py +0 -94
  27. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/LICENSE +0 -0
  28. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/README.md +0 -0
  29. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/__init__.py +0 -0
  30. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/__main__.py +0 -0
  31. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_clustered_functions.py +0 -0
  32. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_clustered_functions.pyi +0 -0
  33. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_container_entrypoint.py +0 -0
  34. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_function_variants.py +0 -0
  35. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_functions.py +0 -0
  36. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_image.py +0 -0
  37. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_ipython.py +0 -0
  38. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_load_context.py +0 -0
  39. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_location.py +0 -0
  40. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_logs.py +0 -0
  41. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_object.py +0 -0
  42. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_output/__init__.py +0 -0
  43. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_output/manager.py +0 -0
  44. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_output/pty.py +0 -0
  45. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_output/rich.py +0 -0
  46. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_output/status.py +0 -0
  47. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_partial_function.py +0 -0
  48. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_resolver.py +0 -0
  49. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_resources.py +0 -0
  50. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/__init__.py +0 -0
  51. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/asgi.py +0 -0
  52. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/container_io_manager.py +0 -0
  53. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/container_io_manager.pyi +0 -0
  54. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/execution_context.py +0 -0
  55. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/execution_context.pyi +0 -0
  56. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  57. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/task_lifecycle_manager.py +0 -0
  58. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  59. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/telemetry.py +0 -0
  60. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/user_code_event_loop.py +0 -0
  61. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_runtime/user_code_imports.py +0 -0
  62. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_serialization.py +0 -0
  63. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_server.py +0 -0
  64. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_traceback.py +0 -0
  65. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_tunnel.py +0 -0
  66. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_tunnel.pyi +0 -0
  67. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_type_manager.py +0 -0
  68. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/__init__.py +0 -0
  69. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/app_utils.py +0 -0
  70. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/async_utils.py +0 -0
  71. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/auth_token_manager.py +0 -0
  72. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/blob_utils.py +0 -0
  73. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/browser_utils.py +0 -0
  74. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/bytes_io_segment_payload.py +0 -0
  75. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/deprecation.py +0 -0
  76. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/docker_utils.py +0 -0
  77. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/function_utils.py +0 -0
  78. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/git_utils.py +0 -0
  79. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/grpc_testing.py +0 -0
  80. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/grpc_utils.py +0 -0
  81. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/hash_utils.py +0 -0
  82. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/http_utils.py +0 -0
  83. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/jwt_utils.py +0 -0
  84. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/logger.py +0 -0
  85. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/mount_utils.py +0 -0
  86. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/name_utils.py +0 -0
  87. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/package_utils.py +0 -0
  88. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/pattern_utils.py +0 -0
  89. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/rand_pb_testing.py +0 -0
  90. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/sandbox_fs_utils.py +0 -0
  91. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/shell_utils.py +0 -0
  92. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_utils/task_command_router_client.py +0 -0
  93. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_vendor/__init__.py +0 -0
  94. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  95. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_vendor/cloudpickle.py +0 -0
  96. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_vendor/tblib.py +0 -0
  97. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_vendor/version.py +0 -0
  98. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/_watcher.py +0 -0
  99. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/app.py +0 -0
  100. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/app.pyi +0 -0
  101. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/2023.12.312.txt +0 -0
  102. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/2023.12.txt +0 -0
  103. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/2024.04.txt +0 -0
  104. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/2024.10.txt +0 -0
  105. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/2025.06.txt +0 -0
  106. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/PREVIEW.txt +0 -0
  107. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/README.md +0 -0
  108. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/builder/base-images.json +0 -0
  109. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/call_graph.py +0 -0
  110. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/__init__.py +0 -0
  111. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/_download.py +0 -0
  112. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/_help.py +0 -0
  113. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/_traceback.py +0 -0
  114. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/app.py +0 -0
  115. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/bootstrap.py +0 -0
  116. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/changelog.py +0 -0
  117. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/cluster.py +0 -0
  118. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/config.py +0 -0
  119. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/container.py +0 -0
  120. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/dashboard.py +0 -0
  121. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/dict.py +0 -0
  122. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/entry_point.py +0 -0
  123. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/image.py +0 -0
  124. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/import_refs.py +0 -0
  125. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/launch.py +0 -0
  126. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/logo.py +0 -0
  127. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/network_file_system.py +0 -0
  128. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/profile.py +0 -0
  129. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/programs/__init__.py +0 -0
  130. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/programs/run_jupyter.py +0 -0
  131. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/programs/vscode.py +0 -0
  132. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/queues.py +0 -0
  133. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/run.py +0 -0
  134. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/secret.py +0 -0
  135. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/selector.py +0 -0
  136. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/shell.py +0 -0
  137. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/skills.py +0 -0
  138. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/token.py +0 -0
  139. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/utils.py +0 -0
  140. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cli/volume.py +0 -0
  141. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/client.py +0 -0
  142. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cloud_bucket_mount.py +0 -0
  143. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cloud_bucket_mount.pyi +0 -0
  144. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cls.py +0 -0
  145. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/cls.pyi +0 -0
  146. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/config.py +0 -0
  147. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/container_process.py +0 -0
  148. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/container_process.pyi +0 -0
  149. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/dict.py +0 -0
  150. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/dict.pyi +0 -0
  151. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/exception.py +0 -0
  152. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/experimental/__init__.py +0 -0
  153. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/experimental/flash.py +0 -0
  154. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/experimental/flash.pyi +0 -0
  155. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/experimental/ipython.py +0 -0
  156. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/file_io.py +0 -0
  157. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/file_io.pyi +0 -0
  158. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/file_pattern_matcher.py +0 -0
  159. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/functions.py +0 -0
  160. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/image.py +0 -0
  161. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/image.pyi +0 -0
  162. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/io_streams.py +0 -0
  163. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/io_streams.pyi +0 -0
  164. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/mount.py +0 -0
  165. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/mount.pyi +0 -0
  166. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/network_file_system.py +0 -0
  167. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/network_file_system.pyi +0 -0
  168. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/object.py +0 -0
  169. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/object.pyi +0 -0
  170. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/output.py +0 -0
  171. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/parallel_map.py +0 -0
  172. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/parallel_map.pyi +0 -0
  173. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/partial_function.py +0 -0
  174. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/partial_function.pyi +0 -0
  175. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/proxy.py +0 -0
  176. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/proxy.pyi +0 -0
  177. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/py.typed +0 -0
  178. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/queue.py +0 -0
  179. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/queue.pyi +0 -0
  180. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/retries.py +0 -0
  181. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/runner.py +0 -0
  182. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/runner.pyi +0 -0
  183. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/running_app.py +0 -0
  184. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/sandbox.py +0 -0
  185. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/sandbox.pyi +0 -0
  186. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/sandbox_fs.py +0 -0
  187. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/sandbox_fs.pyi +0 -0
  188. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/schedule.py +0 -0
  189. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/scheduler_placement.py +0 -0
  190. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/secret.py +0 -0
  191. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/secret.pyi +0 -0
  192. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/server.py +0 -0
  193. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/server.pyi +0 -0
  194. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/serving.py +0 -0
  195. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/serving.pyi +0 -0
  196. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/skills/modal/SKILL.md +0 -0
  197. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/snapshot.py +0 -0
  198. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/snapshot.pyi +0 -0
  199. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/stream_type.py +0 -0
  200. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/token_flow.py +0 -0
  201. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/token_flow.pyi +0 -0
  202. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/volume.py +0 -0
  203. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal/volume.pyi +0 -0
  204. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal.egg-info/dependency_links.txt +0 -0
  205. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal.egg-info/entry_points.txt +0 -0
  206. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal.egg-info/requires.txt +0 -0
  207. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal.egg-info/top_level.txt +0 -0
  208. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/__init__.py +0 -0
  209. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/gen_cli_docs.py +0 -0
  210. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/gen_cli_docs_main.py +0 -0
  211. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/gen_reference_docs.py +0 -0
  212. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/gen_reference_docs_main.py +0 -0
  213. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/mdmd/__init__.py +0 -0
  214. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/mdmd/mdmd.py +0 -0
  215. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/mdmd/signatures.py +0 -0
  216. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_docs/mdmd/types.py +0 -0
  217. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/__init__.py +0 -0
  218. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/modal_api_grpc.py +0 -0
  219. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/py.typed +0 -0
  220. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/task_command_router_grpc.py +0 -0
  221. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/task_command_router_pb2.py +0 -0
  222. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/task_command_router_pb2.pyi +0 -0
  223. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  224. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  225. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/modal_version/__main__.py +0 -0
  226. {modal-1.5.1.dev7 → modal-1.5.1.dev8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.1.dev7
3
+ Version: 1.5.1.dev8
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -0,0 +1,155 @@
1
+ # Copyright Modal Labs 2025
2
+ from dataclasses import FrozenInstanceError, dataclass
3
+ from datetime import datetime, timezone
4
+ from decimal import Decimal
5
+ from typing import Any, Iterable, TypedDict
6
+
7
+ from modal_proto import api_pb2
8
+
9
+ from ._utils.deprecation import deprecation_warning
10
+ from .client import _Client
11
+
12
+ BILLING_DOCSTRING = """
13
+ The `start` and `end` parameters are required to either have a UTC timezone or to be
14
+ timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
15
+ be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
16
+ parameters are partial: `start` will be rounded to the beginning of its interval, while
17
+ partial `end` intervals will be excluded.
18
+
19
+ Additional user-provided metadata can be included in the report if the objects have tags
20
+ and `tag_names` (i.e., keys) are specified in the request. Alternatively, pass `tag_names=["*"]`
21
+ to include all tags in the report. Note that tags will be attributed to the entire interval even
22
+ if they were added or removed at some point within it. If the tag name was not in use during an
23
+ interval, it will be absent from the tags dictionary in that output row.
24
+
25
+ In most cases, billing data will be available in the database that this API queries within
26
+ minutes, although there may be collection delays. If completeness is important for your use
27
+ case, we recommend leaving a buffer after the end of the query interval.
28
+ """
29
+
30
+
31
+ class WorkspaceBillingReportItem(TypedDict):
32
+ object_id: str
33
+ description: str
34
+ environment_name: str
35
+ interval_start: datetime
36
+ cost: Decimal
37
+ tags: dict[str, str]
38
+
39
+
40
+ @dataclass(slots=True, frozen=True)
41
+ class BillingReportItem:
42
+ object_id: str
43
+ description: str
44
+ environment_name: str
45
+ interval_start: datetime
46
+ cost: Decimal
47
+ cost_by_resource: dict[str, Decimal]
48
+ tags: dict[str, str]
49
+
50
+ def __getitem__(self, key: str) -> Any:
51
+ """mdmd:ignore"""
52
+ if key not in self.__slots__:
53
+ raise KeyError(key)
54
+
55
+ return getattr(self, key)
56
+
57
+ def __setitem__(self, key: str, _: Any):
58
+ """mdmd:ignore"""
59
+ raise FrozenInstanceError(f"cannot assign to field {key!r}")
60
+
61
+ def keys(self) -> Iterable[str]:
62
+ """mdmd:ignore"""
63
+ yield from self.__slots__
64
+
65
+ def values(self) -> Iterable[Any]:
66
+ """mdmd:ignore"""
67
+ for k in self.__slots__:
68
+ yield getattr(self, k)
69
+
70
+ def items(self) -> Iterable[tuple[str, Any]]:
71
+ """mdmd:ignore"""
72
+ for k in self.__slots__:
73
+ yield k, getattr(self, k)
74
+
75
+ @classmethod
76
+ def _from_proto(cls, pb_item: api_pb2.WorkspaceBillingReportItem) -> "BillingReportItem":
77
+ return cls(
78
+ object_id=pb_item.object_id,
79
+ description=pb_item.description,
80
+ environment_name=pb_item.environment_name,
81
+ interval_start=pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
82
+ cost=Decimal(pb_item.cost),
83
+ cost_by_resource={k: Decimal(str(v)) for k, v in pb_item.cost_by_resource.items()},
84
+ tags=dict(pb_item.tags),
85
+ )
86
+
87
+
88
+ async def _workspace_billing_report(
89
+ *,
90
+ start: datetime, # Start of the report, inclusive
91
+ end: datetime | None = None, # End of the report, exclusive
92
+ resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
93
+ tag_names: list[str] | None = None, # Optional additional metadata to include
94
+ client: _Client | None = None,
95
+ ) -> list[WorkspaceBillingReportItem]:
96
+ """Generate a tabular report of workspace usage by object and time.
97
+
98
+ The result will be a list of dictionaries for each interval (determined by `resolution`)
99
+ between the `start` and `end` limits. The dictionary represents a single Modal object
100
+ that billing can be attributed to (e.g., an App) along with metadata (including user-defined
101
+ tags) for identifying that object. The dictionary also contains a breakdown of the cost value
102
+ attributed to individual resources (for an App, this can be CPU, Memory, specific GPU types,
103
+ etc.). The specific resource types included in the breakdown are subject to change as
104
+ Modal's billing model evolves.
105
+
106
+ The `start` and `end` parameters are required to either have a UTC timezone or to be
107
+ timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
108
+ be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
109
+ parameters are partial: `start` will be rounded to the beginning of its interval, while
110
+ partial `end` intervals will be excluded.
111
+
112
+ Additional user-provided metadata can be included in the report if the objects have tags
113
+ and `tag_names` (i.e., keys) are specified in the request. Alternatively, pass `tag_names=["*"]`
114
+ to include all tags in the report. Note that tags will be attributed to the entire interval even
115
+ if they were added or removed at some point within it. If the tag name was not in use during an
116
+ interval, it will be absent from the tags dictionary in that output row.
117
+
118
+ In most cases, billing data will be available in the database that this API queries within
119
+ minutes, although there may be collection delays. If completeness is important for your use
120
+ case, we recommend leaving a buffer after the end of the query interval.
121
+
122
+ It's also possible to generate reports using the
123
+ [`modal billing report`](https://modal.com/docs/reference/cli/billing) CLI command. The CLI
124
+ has a few convenience features for generating reports across relative time ranges.
125
+
126
+ """
127
+
128
+ deprecation_warning(
129
+ (2026, 6, 18),
130
+ "`workspace_billing_report()` is deprecated. Use `Workspace.billing.report()` instead.",
131
+ )
132
+
133
+ from ._workspace import _Workspace
134
+
135
+ data = await _Workspace.from_context(client=client).billing.report(
136
+ start=start,
137
+ end=end,
138
+ resolution=resolution,
139
+ tag_names=tag_names,
140
+ )
141
+
142
+ res: list[WorkspaceBillingReportItem] = []
143
+ for datum in data:
144
+ item: WorkspaceBillingReportItem = {
145
+ "object_id": datum.object_id,
146
+ "description": datum.description,
147
+ "environment_name": datum.environment_name,
148
+ "interval_start": datum.interval_start,
149
+ "cost": Decimal(datum.cost),
150
+ "tags": dict(datum.tags),
151
+ }
152
+
153
+ res.append(item)
154
+
155
+ return res
@@ -3,6 +3,7 @@ import asyncio
3
3
  import builtins
4
4
  from collections.abc import Iterable, Mapping
5
5
  from dataclasses import dataclass
6
+ from datetime import datetime, timezone
6
7
  from typing import Literal
7
8
 
8
9
  from google.protobuf.empty_pb2 import Empty
@@ -12,6 +13,7 @@ from synchronicity import classproperty
12
13
 
13
14
  from modal_proto import api_pb2
14
15
 
16
+ from ._billing import BILLING_DOCSTRING, BillingReportItem
15
17
  from ._load_context import LoadContext
16
18
  from ._object import _Object
17
19
  from ._resolver import Resolver
@@ -318,7 +320,7 @@ class _Environment(_Object, type_prefix="en"):
318
320
  # def settings(self) -> EnvironmentSettings:
319
321
  # return self._settings
320
322
 
321
- def _hydrate_metadata(self, metadata: Message):
323
+ def _hydrate_metadata(self, metadata: Message | None):
322
324
  # Overridden concrete implementation of base class method
323
325
  assert metadata and isinstance(metadata, api_pb2.EnvironmentMetadata)
324
326
  self._name = metadata.name or None
@@ -392,6 +394,79 @@ class _Environment(_Object, type_prefix="en"):
392
394
  client=client,
393
395
  )
394
396
 
397
+ @property
398
+ def billing(self) -> "_EnvironmentBillingManager":
399
+ return _EnvironmentBillingManager(self)
400
+
401
+
402
+ class _EnvironmentBillingManager:
403
+ """mdmd:namespace
404
+ Namespace for Environment billing APIs
405
+ """
406
+
407
+ def __init__(self, environment: _Environment):
408
+ """mdmd:ignore"""
409
+ self._environment = environment
410
+
411
+ async def report(
412
+ self,
413
+ *,
414
+ start: datetime, # Start of the report, inclusive
415
+ end: datetime | None = None, # End of the report, exclusive
416
+ resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
417
+ tag_names: list[str] | None = None, # Optional additional metadata to include
418
+ ) -> list[BillingReportItem]:
419
+ (
420
+ """Return a report of workspace usage by object and time, scoped to the calling Environment.
421
+
422
+ The result will be a list of dataclasses for each interval (determined by `resolution`)
423
+ between the `start` and `end` limits. Each item represents a single (Modal object, time interval)
424
+ pair that billing can be attributed to (e.g., an App) along with metadata (including user-defined
425
+ tags) to identify that object. The dataclass also contains a breakdown of the cost value
426
+ attributed to individual resources (for an App, this can be CPU, Memory, specific GPU types,
427
+ etc.). The specific resource types included in the breakdown are subject to change as
428
+ Modal's billing model evolves.
429
+
430
+ It's also possible to generate reports using the
431
+ [`modal environment billing report`](https://modal.com/docs/reference/cli/environment) CLI command.
432
+ The CLI has a few convenience features for generating reports across relative time ranges.
433
+
434
+ """
435
+ + BILLING_DOCSTRING
436
+ )
437
+
438
+ if tag_names is None:
439
+ tag_names = []
440
+
441
+ if end is None:
442
+ end = datetime.now(timezone.utc)
443
+
444
+ if start.tzinfo is None:
445
+ start = start.replace(tzinfo=timezone.utc)
446
+ elif start.tzinfo != timezone.utc:
447
+ raise InvalidError("Timezone-aware 'start' parameter must be in UTC.")
448
+
449
+ if end.tzinfo is None:
450
+ end = end.replace(tzinfo=timezone.utc)
451
+ elif end.tzinfo != timezone.utc:
452
+ raise InvalidError("Timezone-aware 'end' parameter must be in UTC.")
453
+
454
+ if not self._environment.is_hydrated:
455
+ await self._environment.hydrate()
456
+
457
+ request = api_pb2.WorkspaceBillingReportRequest(
458
+ resolution=resolution,
459
+ tag_names=tag_names,
460
+ environment_ids=[self._environment.object_id],
461
+ )
462
+ request.start_timestamp.FromDatetime(start)
463
+ request.end_timestamp.FromDatetime(end)
464
+
465
+ return [
466
+ BillingReportItem._from_proto(pb_item)
467
+ async for pb_item in self._environment.client.stub.WorkspaceBillingReport.unary_stream(request)
468
+ ]
469
+
395
470
 
396
471
  ENVIRONMENT_CACHE: dict[str, _Environment] = {}
397
472
 
@@ -1,6 +1,6 @@
1
1
  # Copyright Modal Labs 2025
2
2
  from collections.abc import Collection, Mapping
3
- from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, overload
3
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Generic, Literal, TypeVar, overload
4
4
 
5
5
  import grpclib.client
6
6
  from google.protobuf.message import Message
@@ -178,9 +178,9 @@ class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
178
178
 
179
179
  async def unary_stream(
180
180
  self,
181
- request,
181
+ request: RequestType,
182
182
  metadata: Any | None = None,
183
- ):
183
+ ) -> AsyncGenerator[ResponseType, None]:
184
184
  from .client import _Client
185
185
 
186
186
  if self.client._snapshotted:
@@ -256,3 +256,26 @@ def timestamp_to_localized_str(ts: float, isotz: bool = True) -> str | None:
256
256
  return f"{dt:%Y-%m-%d %H:%M %Z}"
257
257
  else:
258
258
  return None
259
+
260
+
261
+ def format_interval(
262
+ dt: datetime,
263
+ *,
264
+ tz: tzinfo | None = None,
265
+ isoformat: bool = False,
266
+ show_date_only: bool = False,
267
+ ) -> str:
268
+ if tz is not None:
269
+ dt = dt.astimezone(tz)
270
+ else:
271
+ # Strip UTC tzinfo so isoformat() doesn't append +00:00
272
+ dt = dt.replace(tzinfo=None)
273
+ if isoformat:
274
+ # Full ISO format for machine-readable output
275
+ return dt.isoformat()
276
+ elif show_date_only:
277
+ # Just the date for daily resolution
278
+ return dt.strftime("%Y-%m-%d")
279
+ else:
280
+ # Date and time without seconds for hourly resolution
281
+ return dt.strftime("%Y-%m-%dT%H:%M")
@@ -1,13 +1,15 @@
1
1
  # Copyright Modal Labs 2025
2
2
  import builtins
3
3
  from dataclasses import dataclass
4
- from datetime import datetime
4
+ from datetime import datetime, timezone
5
5
  from typing import Literal, Optional
6
6
 
7
7
  from google.protobuf.empty_pb2 import Empty
8
8
 
9
+ from modal.exception import InvalidError
9
10
  from modal_proto import api_pb2
10
11
 
12
+ from ._billing import BILLING_DOCSTRING, BillingReportItem
11
13
  from ._load_context import LoadContext
12
14
  from ._object import _Object
13
15
  from ._resolver import Resolver
@@ -114,3 +116,75 @@ class _Workspace(_Object, type_prefix="ac"):
114
116
  hydrate_lazily=True,
115
117
  load_context_overrides=LoadContext(client=client),
116
118
  )
119
+
120
+ @property
121
+ def billing(self) -> "_WorkspaceBillingManager":
122
+ return _WorkspaceBillingManager(self)
123
+
124
+
125
+ class _WorkspaceBillingManager:
126
+ """mdmd:namespace
127
+ Namespace for Workspace billing APIs
128
+ """
129
+
130
+ def __init__(self, workspace: _Workspace):
131
+ """mdmd:hidden"""
132
+ self._workspace = workspace
133
+
134
+ async def report(
135
+ self,
136
+ *,
137
+ start: datetime, # Start of the report, inclusive
138
+ end: datetime | None = None, # End of the report, exclusive
139
+ resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
140
+ tag_names: list[str] | None = None, # Optional additional metadata to include
141
+ ) -> list[BillingReportItem]:
142
+ (
143
+ """Return a report of workspace usage by object and time.
144
+
145
+ The result will be a list of dataclasses for each interval (determined by `resolution`)
146
+ between the `start` and `end` limits. Each item represents a single (Modal object, time interval)
147
+ pair that billing can be attributed to (e.g., an App) along with metadata (including user-defined
148
+ tags) to identify that object. The dataclass also contains a breakdown of the cost value
149
+ attributed to individual resources (for an App, this can be CPU, Memory, specific GPU types,
150
+ etc.). The specific resource types included in the breakdown are subject to change as
151
+ Modal's billing model evolves.
152
+
153
+ It's also possible to generate reports using the
154
+ [`modal billing report`](https://modal.com/docs/reference/cli/billing) CLI command. The CLI
155
+ has a few convenience features for generating reports across relative time ranges.
156
+
157
+ """
158
+ + BILLING_DOCSTRING
159
+ )
160
+
161
+ if tag_names is None:
162
+ tag_names = []
163
+
164
+ if end is None:
165
+ end = datetime.now(timezone.utc)
166
+
167
+ if start.tzinfo is None:
168
+ start = start.replace(tzinfo=timezone.utc)
169
+ elif start.tzinfo != timezone.utc:
170
+ raise InvalidError("Timezone-aware 'start' parameter must be in UTC.")
171
+
172
+ if end.tzinfo is None:
173
+ end = end.replace(tzinfo=timezone.utc)
174
+ elif end.tzinfo != timezone.utc:
175
+ raise InvalidError("Timezone-aware 'end' parameter must be in UTC.")
176
+
177
+ if not self._workspace.is_hydrated:
178
+ await self._workspace.hydrate()
179
+
180
+ request = api_pb2.WorkspaceBillingReportRequest(
181
+ resolution=resolution,
182
+ tag_names=tag_names,
183
+ )
184
+ request.start_timestamp.FromDatetime(start)
185
+ request.end_timestamp.FromDatetime(end)
186
+
187
+ return [
188
+ BillingReportItem._from_proto(pb_item)
189
+ async for pb_item in self._workspace.client.stub.WorkspaceBillingReport.unary_stream(request)
190
+ ]
@@ -1,10 +1,11 @@
1
1
  # Copyright Modal Labs 2025
2
- from ._billing import WorkspaceBillingReportItem, _workspace_billing_report
2
+ from ._billing import BillingReportItem, WorkspaceBillingReportItem, _workspace_billing_report
3
3
  from ._utils.async_utils import synchronize_api
4
4
 
5
- workspace_billing_report = synchronize_api(_workspace_billing_report)
5
+ workspace_billing_report = synchronize_api(_workspace_billing_report, target_module=__name__)
6
6
 
7
7
  __all__ = [
8
8
  "workspace_billing_report",
9
9
  "WorkspaceBillingReportItem",
10
+ "BillingReportItem",
10
11
  ]
@@ -0,0 +1,184 @@
1
+ import datetime
2
+ import decimal
3
+ import modal._billing
4
+ import modal.client
5
+ import modal_proto.api_pb2
6
+ import typing
7
+ import typing_extensions
8
+
9
+ class BillingReportItem:
10
+ """BillingReportItem(object_id: str, description: str, environment_name: str, interval_start: datetime.datetime, cost: decimal.Decimal, cost_by_resource: dict[str, decimal.Decimal], tags: dict[str, str])"""
11
+
12
+ object_id: str
13
+ description: str
14
+ environment_name: str
15
+ interval_start: datetime.datetime
16
+ cost: decimal.Decimal
17
+ cost_by_resource: dict[str, decimal.Decimal]
18
+ tags: dict[str, str]
19
+
20
+ def __getitem__(self, key: str) -> typing.Any:
21
+ """mdmd:ignore"""
22
+ ...
23
+
24
+ def __setitem__(self, key: str, _: typing.Any):
25
+ """mdmd:ignore"""
26
+ ...
27
+
28
+ def keys(self) -> typing.Iterable[str]:
29
+ """mdmd:ignore"""
30
+ ...
31
+
32
+ def values(self) -> typing.Iterable[typing.Any]:
33
+ """mdmd:ignore"""
34
+ ...
35
+
36
+ def items(self) -> typing.Iterable[tuple[str, typing.Any]]:
37
+ """mdmd:ignore"""
38
+ ...
39
+
40
+ @classmethod
41
+ def _from_proto(
42
+ cls, pb_item: modal_proto.api_pb2.WorkspaceBillingReportItem
43
+ ) -> modal._billing.BillingReportItem: ...
44
+ def __init__(
45
+ self,
46
+ object_id: str,
47
+ description: str,
48
+ environment_name: str,
49
+ interval_start: datetime.datetime,
50
+ cost: decimal.Decimal,
51
+ cost_by_resource: dict[str, decimal.Decimal],
52
+ tags: dict[str, str],
53
+ ) -> None:
54
+ """Initialize self. See help(type(self)) for accurate signature."""
55
+ ...
56
+
57
+ def __repr__(self):
58
+ """Return repr(self)."""
59
+ ...
60
+
61
+ def __eq__(self, other):
62
+ """Return self==value."""
63
+ ...
64
+
65
+ def __setattr__(self, name, value):
66
+ """Implement setattr(self, name, value)."""
67
+ ...
68
+
69
+ def __delattr__(self, name):
70
+ """Implement delattr(self, name)."""
71
+ ...
72
+
73
+ def __hash__(self):
74
+ """Return hash(self)."""
75
+ ...
76
+
77
+ def __getstate__(self): ...
78
+ def __setstate__(self, state): ...
79
+
80
+ class WorkspaceBillingReportItem(typing.TypedDict):
81
+ """dict() -> new empty dictionary
82
+ dict(mapping) -> new dictionary initialized from a mapping object's
83
+ (key, value) pairs
84
+ dict(iterable) -> new dictionary initialized as if via:
85
+ d = {}
86
+ for k, v in iterable:
87
+ d[k] = v
88
+ dict(**kwargs) -> new dictionary initialized with the name=value pairs
89
+ in the keyword argument list. For example: dict(one=1, two=2)
90
+ """
91
+
92
+ object_id: str
93
+ description: str
94
+ environment_name: str
95
+ interval_start: datetime.datetime
96
+ cost: decimal.Decimal
97
+ tags: dict[str, str]
98
+
99
+ class __workspace_billing_report_spec(typing_extensions.Protocol):
100
+ def __call__(
101
+ self,
102
+ /,
103
+ *,
104
+ start: datetime.datetime,
105
+ end: typing.Optional[datetime.datetime] = None,
106
+ resolution: str = "d",
107
+ tag_names: typing.Optional[list[str]] = None,
108
+ client: typing.Optional[modal.client.Client] = None,
109
+ ) -> list[modal._billing.WorkspaceBillingReportItem]:
110
+ """Generate a tabular report of workspace usage by object and time.
111
+
112
+ The result will be a list of dictionaries for each interval (determined by `resolution`)
113
+ between the `start` and `end` limits. The dictionary represents a single Modal object
114
+ that billing can be attributed to (e.g., an App) along with metadata (including user-defined
115
+ tags) for identifying that object. The dictionary also contains a breakdown of the cost value
116
+ attributed to individual resources (for an App, this can be CPU, Memory, specific GPU types,
117
+ etc.). The specific resource types included in the breakdown are subject to change as
118
+ Modal's billing model evolves.
119
+
120
+ The `start` and `end` parameters are required to either have a UTC timezone or to be
121
+ timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
122
+ be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
123
+ parameters are partial: `start` will be rounded to the beginning of its interval, while
124
+ partial `end` intervals will be excluded.
125
+
126
+ Additional user-provided metadata can be included in the report if the objects have tags
127
+ and `tag_names` (i.e., keys) are specified in the request. Alternatively, pass `tag_names=["*"]`
128
+ to include all tags in the report. Note that tags will be attributed to the entire interval even
129
+ if they were added or removed at some point within it. If the tag name was not in use during an
130
+ interval, it will be absent from the tags dictionary in that output row.
131
+
132
+ In most cases, billing data will be available in the database that this API queries within
133
+ minutes, although there may be collection delays. If completeness is important for your use
134
+ case, we recommend leaving a buffer after the end of the query interval.
135
+
136
+ It's also possible to generate reports using the
137
+ [`modal billing report`](https://modal.com/docs/reference/cli/billing) CLI command. The CLI
138
+ has a few convenience features for generating reports across relative time ranges.
139
+ """
140
+ ...
141
+
142
+ async def aio(
143
+ self,
144
+ /,
145
+ *,
146
+ start: datetime.datetime,
147
+ end: typing.Optional[datetime.datetime] = None,
148
+ resolution: str = "d",
149
+ tag_names: typing.Optional[list[str]] = None,
150
+ client: typing.Optional[modal.client.Client] = None,
151
+ ) -> list[modal._billing.WorkspaceBillingReportItem]:
152
+ """Generate a tabular report of workspace usage by object and time.
153
+
154
+ The result will be a list of dictionaries for each interval (determined by `resolution`)
155
+ between the `start` and `end` limits. The dictionary represents a single Modal object
156
+ that billing can be attributed to (e.g., an App) along with metadata (including user-defined
157
+ tags) for identifying that object. The dictionary also contains a breakdown of the cost value
158
+ attributed to individual resources (for an App, this can be CPU, Memory, specific GPU types,
159
+ etc.). The specific resource types included in the breakdown are subject to change as
160
+ Modal's billing model evolves.
161
+
162
+ The `start` and `end` parameters are required to either have a UTC timezone or to be
163
+ timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
164
+ be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
165
+ parameters are partial: `start` will be rounded to the beginning of its interval, while
166
+ partial `end` intervals will be excluded.
167
+
168
+ Additional user-provided metadata can be included in the report if the objects have tags
169
+ and `tag_names` (i.e., keys) are specified in the request. Alternatively, pass `tag_names=["*"]`
170
+ to include all tags in the report. Note that tags will be attributed to the entire interval even
171
+ if they were added or removed at some point within it. If the tag name was not in use during an
172
+ interval, it will be absent from the tags dictionary in that output row.
173
+
174
+ In most cases, billing data will be available in the database that this API queries within
175
+ minutes, although there may be collection delays. If completeness is important for your use
176
+ case, we recommend leaving a buffer after the end of the query interval.
177
+
178
+ It's also possible to generate reports using the
179
+ [`modal billing report`](https://modal.com/docs/reference/cli/billing) CLI command. The CLI
180
+ has a few convenience features for generating reports across relative time ranges.
181
+ """
182
+ ...
183
+
184
+ workspace_billing_report: __workspace_billing_report_spec