modal 1.5.2.dev22__tar.gz → 1.5.3__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 (237) hide show
  1. {modal-1.5.2.dev22 → modal-1.5.3}/PKG-INFO +1 -1
  2. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_clustered_functions.py +11 -0
  3. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_clustered_functions.pyi +6 -2
  4. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_container_entrypoint.py +2 -4
  5. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_environments.py +155 -81
  6. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_function_variants.py +1 -1
  7. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_functions.py +90 -21
  8. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_grpc_client.py +1 -1
  9. modal-1.5.3/modal/_logs_manager.py +674 -0
  10. modal-1.5.3/modal/_logs_manager.pyi +1063 -0
  11. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_object.py +5 -4
  12. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_output/rich.py +3 -3
  13. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_partial_function.py +15 -3
  14. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_resources.py +4 -4
  15. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/container_io_manager.py +6 -30
  16. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/container_io_manager.pyi +5 -16
  17. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/user_code_imports.py +2 -1
  18. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_server.py +21 -1
  19. modal-1.5.3/modal/_supports_logs.py +32 -0
  20. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_traceback.py +1 -1
  21. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/async_utils.py +5 -0
  22. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/blob_utils.py +1 -0
  23. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/bytes_io_segment_payload.py +2 -1
  24. modal-1.5.3/modal/_utils/display_utils.py +15 -0
  25. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/grpc_utils.py +20 -5
  26. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/pattern_utils.py +2 -2
  27. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/task_command_router_client.py +23 -34
  28. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/time_utils.py +106 -14
  29. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_workspace.py +78 -16
  30. {modal-1.5.2.dev22 → modal-1.5.3}/modal/app.py +15 -3
  31. {modal-1.5.2.dev22 → modal-1.5.3}/modal/app.pyi +8 -4
  32. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/billing.py +121 -17
  33. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/changelog.py +7 -7
  34. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/cluster.py +1 -1
  35. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/config.py +1 -1
  36. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/dashboard.py +1 -1
  37. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/endpoint.py +23 -3
  38. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/environment.py +215 -54
  39. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/launch.py +1 -1
  40. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/network_file_system.py +1 -1
  41. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/profile.py +3 -3
  42. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/shell.py +23 -4
  43. {modal-1.5.2.dev22 → modal-1.5.3}/modal/client.py +14 -8
  44. {modal-1.5.2.dev22 → modal-1.5.3}/modal/client.pyi +15 -21
  45. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cloud_bucket_mount.py +18 -5
  46. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cloud_bucket_mount.pyi +2 -2
  47. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cls.py +5 -2
  48. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cls.pyi +4 -4
  49. {modal-1.5.2.dev22 → modal-1.5.3}/modal/config.py +30 -1
  50. {modal-1.5.2.dev22 → modal-1.5.3}/modal/environments.py +3 -0
  51. {modal-1.5.2.dev22 → modal-1.5.3}/modal/environments.pyi +152 -66
  52. {modal-1.5.2.dev22 → modal-1.5.3}/modal/exception.py +5 -1
  53. {modal-1.5.2.dev22 → modal-1.5.3}/modal/experimental/__init__.py +11 -1
  54. {modal-1.5.2.dev22 → modal-1.5.3}/modal/file_pattern_matcher.py +1 -1
  55. {modal-1.5.2.dev22 → modal-1.5.3}/modal/functions.pyi +91 -30
  56. {modal-1.5.2.dev22 → modal-1.5.3}/modal/mount.py +89 -31
  57. {modal-1.5.2.dev22 → modal-1.5.3}/modal/mount.pyi +18 -6
  58. {modal-1.5.2.dev22 → modal-1.5.3}/modal/parallel_map.py +56 -51
  59. {modal-1.5.2.dev22 → modal-1.5.3}/modal/partial_function.pyi +1 -1
  60. {modal-1.5.2.dev22 → modal-1.5.3}/modal/runner.py +31 -9
  61. {modal-1.5.2.dev22 → modal-1.5.3}/modal/runner.pyi +8 -1
  62. {modal-1.5.2.dev22 → modal-1.5.3}/modal/sandbox.py +239 -61
  63. {modal-1.5.2.dev22 → modal-1.5.3}/modal/sandbox.pyi +251 -46
  64. {modal-1.5.2.dev22 → modal-1.5.3}/modal/sandbox_fs.py +1 -2
  65. {modal-1.5.2.dev22 → modal-1.5.3}/modal/sandbox_fs.pyi +2 -6
  66. {modal-1.5.2.dev22 → modal-1.5.3}/modal/schedule.py +1 -1
  67. {modal-1.5.2.dev22 → modal-1.5.3}/modal/secret.py +11 -2
  68. {modal-1.5.2.dev22 → modal-1.5.3}/modal/secret.pyi +18 -0
  69. {modal-1.5.2.dev22 → modal-1.5.3}/modal/server.pyi +24 -1
  70. {modal-1.5.2.dev22 → modal-1.5.3}/modal/serving.py +3 -3
  71. {modal-1.5.2.dev22 → modal-1.5.3}/modal/snapshot.py +17 -1
  72. {modal-1.5.2.dev22 → modal-1.5.3}/modal/snapshot.pyi +22 -0
  73. {modal-1.5.2.dev22 → modal-1.5.3}/modal/types.py +81 -1
  74. {modal-1.5.2.dev22 → modal-1.5.3}/modal/workspace.pyi +95 -20
  75. {modal-1.5.2.dev22 → modal-1.5.3}/modal.egg-info/PKG-INFO +1 -1
  76. {modal-1.5.2.dev22 → modal-1.5.3}/modal.egg-info/SOURCES.txt +5 -0
  77. modal-1.5.3/modal_docs/gen_reference_docs.py +167 -0
  78. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/mdmd/mdmd.py +67 -46
  79. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/mdmd/types.py +1 -0
  80. modal-1.5.3/modal_docs/reference_intro.md +115 -0
  81. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/api_grpc.py +80 -0
  82. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/api_pb2.py +1317 -1125
  83. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/api_pb2.pyi +447 -56
  84. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/api_pb2_grpc.py +165 -0
  85. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/api_pb2_grpc.pyi +50 -0
  86. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/modal_api_grpc.py +5 -0
  87. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/task_command_router_grpc.py +16 -0
  88. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/task_command_router_pb2.py +108 -88
  89. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/task_command_router_pb2.pyi +47 -2
  90. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/task_command_router_pb2_grpc.py +37 -0
  91. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/task_command_router_pb2_grpc.pyi +20 -0
  92. {modal-1.5.2.dev22 → modal-1.5.3}/modal_version/__init__.py +1 -1
  93. {modal-1.5.2.dev22 → modal-1.5.3}/pyproject.toml +49 -2
  94. modal-1.5.2.dev22/modal_docs/gen_reference_docs.py +0 -361
  95. {modal-1.5.2.dev22 → modal-1.5.3}/LICENSE +0 -0
  96. {modal-1.5.2.dev22 → modal-1.5.3}/README.md +0 -0
  97. {modal-1.5.2.dev22 → modal-1.5.3}/modal/__init__.py +0 -0
  98. {modal-1.5.2.dev22 → modal-1.5.3}/modal/__main__.py +0 -0
  99. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_billing.py +0 -0
  100. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_image.py +0 -0
  101. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_ipython.py +0 -0
  102. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_load_context.py +0 -0
  103. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_location.py +0 -0
  104. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_logs.py +0 -0
  105. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_output/__init__.py +0 -0
  106. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_output/manager.py +0 -0
  107. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_output/pty.py +0 -0
  108. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_output/status.py +0 -0
  109. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_resolver.py +0 -0
  110. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/__init__.py +0 -0
  111. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/asgi.py +0 -0
  112. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/execution_context.py +0 -0
  113. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/execution_context.pyi +0 -0
  114. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  115. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/task_lifecycle_manager.py +0 -0
  116. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  117. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/telemetry.py +0 -0
  118. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_runtime/user_code_event_loop.py +0 -0
  119. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_serialization.py +0 -0
  120. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_tunnel.py +0 -0
  121. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_tunnel.pyi +0 -0
  122. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_type_manager.py +0 -0
  123. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/__init__.py +0 -0
  124. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/app_utils.py +0 -0
  125. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/auth_token_manager.py +0 -0
  126. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/browser_utils.py +0 -0
  127. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/curl_utils.py +0 -0
  128. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/deprecation.py +0 -0
  129. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/docker_utils.py +0 -0
  130. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/function_utils.py +0 -0
  131. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/git_utils.py +0 -0
  132. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/grpc_testing.py +0 -0
  133. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/hash_utils.py +0 -0
  134. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/http_utils.py +0 -0
  135. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/jwt_utils.py +0 -0
  136. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/logger.py +0 -0
  137. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/mount_utils.py +0 -0
  138. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/name_utils.py +0 -0
  139. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/package_utils.py +0 -0
  140. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/proxy_support.py +0 -0
  141. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/rand_pb_testing.py +0 -0
  142. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/sandbox_fs_utils.py +0 -0
  143. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_utils/shell_utils.py +0 -0
  144. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_vendor/__init__.py +0 -0
  145. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  146. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_vendor/cloudpickle.py +0 -0
  147. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_vendor/tblib.py +0 -0
  148. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_vendor/version.py +0 -0
  149. {modal-1.5.2.dev22 → modal-1.5.3}/modal/_watcher.py +0 -0
  150. {modal-1.5.2.dev22 → modal-1.5.3}/modal/billing.py +0 -0
  151. {modal-1.5.2.dev22 → modal-1.5.3}/modal/billing.pyi +0 -0
  152. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/2023.12.312.txt +0 -0
  153. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/2023.12.txt +0 -0
  154. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/2024.04.txt +0 -0
  155. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/2024.10.txt +0 -0
  156. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/2025.06.txt +0 -0
  157. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/PREVIEW.txt +0 -0
  158. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/README.md +0 -0
  159. {modal-1.5.2.dev22 → modal-1.5.3}/modal/builder/base-images.json +0 -0
  160. {modal-1.5.2.dev22 → modal-1.5.3}/modal/call_graph.py +0 -0
  161. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/__init__.py +0 -0
  162. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/_download.py +0 -0
  163. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/_help.py +0 -0
  164. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/_traceback.py +0 -0
  165. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/app.py +0 -0
  166. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/bootstrap.py +0 -0
  167. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/container.py +0 -0
  168. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/curl.py +0 -0
  169. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/dict.py +0 -0
  170. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/entry_point.py +0 -0
  171. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/image.py +0 -0
  172. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/import_refs.py +0 -0
  173. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/logo.py +0 -0
  174. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/programs/__init__.py +0 -0
  175. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/programs/run_jupyter.py +0 -0
  176. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/programs/vscode.py +0 -0
  177. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/queues.py +0 -0
  178. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/run.py +0 -0
  179. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/secret.py +0 -0
  180. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/selector.py +0 -0
  181. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/skills.py +0 -0
  182. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/token.py +0 -0
  183. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/utils.py +0 -0
  184. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/volume.py +0 -0
  185. {modal-1.5.2.dev22 → modal-1.5.3}/modal/cli/workspace.py +0 -0
  186. {modal-1.5.2.dev22 → modal-1.5.3}/modal/container_process.py +0 -0
  187. {modal-1.5.2.dev22 → modal-1.5.3}/modal/container_process.pyi +0 -0
  188. {modal-1.5.2.dev22 → modal-1.5.3}/modal/dict.py +0 -0
  189. {modal-1.5.2.dev22 → modal-1.5.3}/modal/dict.pyi +0 -0
  190. {modal-1.5.2.dev22 → modal-1.5.3}/modal/experimental/flash.py +0 -0
  191. {modal-1.5.2.dev22 → modal-1.5.3}/modal/experimental/flash.pyi +0 -0
  192. {modal-1.5.2.dev22 → modal-1.5.3}/modal/experimental/ipython.py +0 -0
  193. {modal-1.5.2.dev22 → modal-1.5.3}/modal/file_io.py +0 -0
  194. {modal-1.5.2.dev22 → modal-1.5.3}/modal/file_io.pyi +0 -0
  195. {modal-1.5.2.dev22 → modal-1.5.3}/modal/functions.py +0 -0
  196. {modal-1.5.2.dev22 → modal-1.5.3}/modal/image.py +0 -0
  197. {modal-1.5.2.dev22 → modal-1.5.3}/modal/image.pyi +0 -0
  198. {modal-1.5.2.dev22 → modal-1.5.3}/modal/io_streams.py +0 -0
  199. {modal-1.5.2.dev22 → modal-1.5.3}/modal/io_streams.pyi +0 -0
  200. {modal-1.5.2.dev22 → modal-1.5.3}/modal/network_file_system.py +0 -0
  201. {modal-1.5.2.dev22 → modal-1.5.3}/modal/network_file_system.pyi +0 -0
  202. {modal-1.5.2.dev22 → modal-1.5.3}/modal/object.py +0 -0
  203. {modal-1.5.2.dev22 → modal-1.5.3}/modal/object.pyi +0 -0
  204. {modal-1.5.2.dev22 → modal-1.5.3}/modal/output.py +0 -0
  205. {modal-1.5.2.dev22 → modal-1.5.3}/modal/parallel_map.pyi +0 -0
  206. {modal-1.5.2.dev22 → modal-1.5.3}/modal/partial_function.py +0 -0
  207. {modal-1.5.2.dev22 → modal-1.5.3}/modal/proxy.py +0 -0
  208. {modal-1.5.2.dev22 → modal-1.5.3}/modal/proxy.pyi +0 -0
  209. {modal-1.5.2.dev22 → modal-1.5.3}/modal/py.typed +0 -0
  210. {modal-1.5.2.dev22 → modal-1.5.3}/modal/queue.py +0 -0
  211. {modal-1.5.2.dev22 → modal-1.5.3}/modal/queue.pyi +0 -0
  212. {modal-1.5.2.dev22 → modal-1.5.3}/modal/retries.py +0 -0
  213. {modal-1.5.2.dev22 → modal-1.5.3}/modal/running_app.py +0 -0
  214. {modal-1.5.2.dev22 → modal-1.5.3}/modal/scheduler_placement.py +0 -0
  215. {modal-1.5.2.dev22 → modal-1.5.3}/modal/server.py +0 -0
  216. {modal-1.5.2.dev22 → modal-1.5.3}/modal/serving.pyi +0 -0
  217. {modal-1.5.2.dev22 → modal-1.5.3}/modal/skills/modal/SKILL.md +0 -0
  218. {modal-1.5.2.dev22 → modal-1.5.3}/modal/stream_type.py +0 -0
  219. {modal-1.5.2.dev22 → modal-1.5.3}/modal/token_flow.py +0 -0
  220. {modal-1.5.2.dev22 → modal-1.5.3}/modal/token_flow.pyi +0 -0
  221. {modal-1.5.2.dev22 → modal-1.5.3}/modal/volume.py +0 -0
  222. {modal-1.5.2.dev22 → modal-1.5.3}/modal/volume.pyi +0 -0
  223. {modal-1.5.2.dev22 → modal-1.5.3}/modal/workspace.py +0 -0
  224. {modal-1.5.2.dev22 → modal-1.5.3}/modal.egg-info/dependency_links.txt +0 -0
  225. {modal-1.5.2.dev22 → modal-1.5.3}/modal.egg-info/entry_points.txt +0 -0
  226. {modal-1.5.2.dev22 → modal-1.5.3}/modal.egg-info/requires.txt +0 -0
  227. {modal-1.5.2.dev22 → modal-1.5.3}/modal.egg-info/top_level.txt +0 -0
  228. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/__init__.py +0 -0
  229. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/gen_cli_docs.py +0 -0
  230. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/gen_cli_docs_main.py +0 -0
  231. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/gen_reference_docs_main.py +0 -0
  232. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/mdmd/__init__.py +0 -0
  233. {modal-1.5.2.dev22 → modal-1.5.3}/modal_docs/mdmd/signatures.py +0 -0
  234. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/__init__.py +0 -0
  235. {modal-1.5.2.dev22 → modal-1.5.3}/modal_proto/py.typed +0 -0
  236. {modal-1.5.2.dev22 → modal-1.5.3}/modal_version/__main__.py +0 -0
  237. {modal-1.5.2.dev22 → modal-1.5.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev22
3
+ Version: 1.5.3
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -15,6 +15,7 @@ class ClusterInfo:
15
15
  cluster_id: str
16
16
  container_ips: list[str]
17
17
  container_ipv4_ips: list[str]
18
+ fabric_ids: list[str]
18
19
 
19
20
 
20
21
  cluster_info: ClusterInfo | None = None
@@ -29,6 +30,14 @@ def get_cluster_info() -> ClusterInfo:
29
30
  return cluster_info
30
31
 
31
32
 
33
+ def get_fabric_peers() -> list[int]:
34
+ info = get_cluster_info()
35
+ if info.rank >= len(info.fabric_ids) or not info.fabric_ids[info.rank]:
36
+ return [info.rank]
37
+ own_fabric_id = info.fabric_ids[info.rank]
38
+ return [rank for rank, fabric_id in enumerate(info.fabric_ids) if fabric_id == own_fabric_id]
39
+
40
+
32
41
  async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
33
42
  global cluster_info
34
43
 
@@ -70,6 +79,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
70
79
  cluster_id=resp.cluster_id,
71
80
  container_ips=resp.container_ips,
72
81
  container_ipv4_ips=resp.container_ipv4_ips,
82
+ fabric_ids=resp.fabric_ids,
73
83
  )
74
84
  else:
75
85
  cluster_info = ClusterInfo(
@@ -77,6 +87,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
77
87
  cluster_id="", # No cluster ID for single-node # TODO(irfansharif): Is this right?
78
88
  container_ips=[container_ip],
79
89
  container_ipv4_ips=[], # No IPv4 IPs for single-node
90
+ fabric_ids=[],
80
91
  )
81
92
 
82
93
 
@@ -3,14 +3,17 @@ import types
3
3
  import typing_extensions
4
4
 
5
5
  class ClusterInfo:
6
- """ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str])"""
6
+ """ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str])"""
7
7
 
8
8
  rank: int
9
9
  cluster_id: str
10
10
  container_ips: list[str]
11
11
  container_ipv4_ips: list[str]
12
+ fabric_ids: list[str]
12
13
 
13
- def __init__(self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str]) -> None:
14
+ def __init__(
15
+ self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str]
16
+ ) -> None:
14
17
  """Initialize self. See help(type(self)) for accurate signature."""
15
18
  ...
16
19
 
@@ -23,6 +26,7 @@ class ClusterInfo:
23
26
  ...
24
27
 
25
28
  def get_cluster_info() -> ClusterInfo: ...
29
+ def get_fabric_peers() -> list[int]: ...
26
30
  async def _initialize_clustered_function(client: modal.client._Client, task_id: str, world_size: int): ...
27
31
 
28
32
  class __initialize_clustered_function_spec(typing_extensions.Protocol):
@@ -145,7 +145,7 @@ def call_function(
145
145
  item_count += 1
146
146
 
147
147
  await container_io_manager._send_outputs.aio(
148
- started_at, io_context.output_items_generator_done(started_at, item_count)
148
+ io_context.output_items_generator_done(started_at, item_count)
149
149
  )
150
150
  else:
151
151
  value = await io_context.call_function_async()
@@ -182,9 +182,7 @@ def call_function(
182
182
  container_io_manager._queue_put(generator_queue, value)
183
183
  item_count += 1
184
184
 
185
- container_io_manager._send_outputs(
186
- started_at, io_context.output_items_generator_done(started_at, item_count)
187
- )
185
+ container_io_manager._send_outputs(io_context.output_items_generator_done(started_at, item_count))
188
186
  else:
189
187
  values = io_context.call_function_sync()
190
188
  container_io_manager.push_outputs(io_context, started_at, values)
@@ -16,11 +16,13 @@ from modal_proto import api_pb2
16
16
  from ._load_context import LoadContext
17
17
  from ._object import _Object
18
18
  from ._resolver import Resolver
19
+ from ._utils.deprecation import deprecation_warning
19
20
  from ._utils.name_utils import check_environment_name
21
+ from ._utils.time_utils import is_utc_month_aligned, parse_billing_cycle
20
22
  from .client import _Client
21
23
  from .config import config, logger
22
24
  from .exception import InvalidError, WorkspaceManagementError
23
- from .types import BillingReportItem
25
+ from .types import BillingReportItem, EnvironmentBillingSummary
24
26
 
25
27
 
26
28
  @dataclass(frozen=True)
@@ -103,63 +105,38 @@ class _EnvironmentManager:
103
105
  await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
104
106
 
105
107
 
106
- def _role_to_proto(role: str) -> "api_pb2.EnvironmentRole.ValueType":
107
- match role:
108
- case "viewer":
109
- return api_pb2.ENVIRONMENT_ROLE_VIEWER
110
- case "contributor":
111
- return api_pb2.ENVIRONMENT_ROLE_CONTRIBUTOR
112
- case _:
113
- raise InvalidError(f"Invalid Environment role: {role!r} (expected 'viewer' or 'contributor')")
114
-
115
-
116
- def _role_from_proto(proto_value: int) -> str:
117
- match proto_value:
118
- case api_pb2.ENVIRONMENT_ROLE_VIEWER:
119
- return "viewer"
120
- case api_pb2.ENVIRONMENT_ROLE_CONTRIBUTOR:
121
- return "contributor"
122
- case _:
123
- raise ValueError(f"Unknown environment role: {proto_value}")
124
-
125
-
126
- class _EnvironmentMembersManager:
127
- """mdmd:namespace
128
- Namespace with methods for managing the membership of a restricted Environment.
129
-
130
- See https://modal.com/docs/guide/rbac for more information on restricted Environments.
131
- """
108
+ class _EnvironmentRolesManager:
109
+ """mdmd:namespace"""
132
110
 
133
111
  def __init__(self, environment: "_Environment"):
134
112
  """mdmd:hidden"""
135
113
  self._environment = environment
136
114
 
137
115
  async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
138
- """Return the members of a restricted Environment with their roles.
116
+ """Enumerate the Environment Role for each user and service user in the workspace.
139
117
 
140
118
  **Examples:**
141
119
 
142
120
  ```python notest
143
- members = modal.Environment.from_name("my-restricted-env").members.list()
144
- print(members)
121
+ roles = modal.Environment.from_name("my-env").roles.list()
122
+ print(roles)
145
123
  # {
146
- # "users": {"alice": "contributor", "bob": "viewer"},
147
- # "service_users": {"alice-bot": "contributor"},
124
+ # "users": {"alice": "contributor", "bob": "viewer", "carol": "contributor"},
125
+ # "service_users": {"alice-bot": "contributor", "ops-bot": "viewer", "ci-bot": "no-access"},
148
126
  # }
149
127
  ```
150
128
  """
151
129
  await self._environment.hydrate()
152
- req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
153
- resp = await self._environment.client.stub.EnvironmentGetManaged(req)
130
+ req = api_pb2.EnvironmentGetRolesRequest(environment_id=self._environment.object_id)
131
+ resp = await self._environment.client.stub.EnvironmentGetRoles(req)
154
132
 
155
133
  users: dict[str, str] = {}
156
134
  service_users: dict[str, str] = {}
157
135
  for principal in resp.principal_roles:
158
- role = _role_from_proto(principal.role)
159
136
  if principal.user_id:
160
- users[principal.user_name] = role
137
+ users[principal.user_name] = principal.role_str
161
138
  elif principal.service_user_id:
162
- service_users[principal.service_user_name] = role
139
+ service_users[principal.service_user_name] = principal.role_str
163
140
 
164
141
  return {"users": users, "service_users": service_users}
165
142
 
@@ -169,16 +146,17 @@ class _EnvironmentMembersManager:
169
146
  users: Mapping[str, str] | None = None,
170
147
  service_users: Mapping[str, str] | None = None,
171
148
  ) -> None:
172
- """Add or modify roles for members of a restricted Environment.
149
+ """Update the Environment Role of users and service users.
173
150
 
174
- Each user or service user will be added to the Environment if not currently a member;
175
- if already a member, the user or service user's role will be updated.
151
+ Each role is one of 'contributor', 'viewer', or 'no-access'. Service users can be
152
+ assigned a role on any Environment, while workspace members can only be assigned a
153
+ role on restricted Environments.
176
154
 
177
155
  **Examples:**
178
156
 
179
157
  ```python notest
180
158
  env = modal.Environment.from_name("my-restricted-env")
181
- env.members.update(
159
+ env.roles.update(
182
160
  users={"alice": "contributor", "bob": "viewer"},
183
161
  service_users={"alice-bot": "contributor"},
184
162
  )
@@ -188,13 +166,13 @@ class _EnvironmentMembersManager:
188
166
  users = users or {}
189
167
  service_users = service_users or {}
190
168
 
191
- req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
192
- resp = await self._environment.client.stub.EnvironmentGetManaged(req)
169
+ req = api_pb2.EnvironmentGetRolesRequest(environment_id=self._environment.object_id)
170
+ resp = await self._environment.client.stub.EnvironmentGetRoles(req)
193
171
 
194
- # Both current members and additional eligible workspace principals can be assigned a role
172
+ # EnvironmentGetRoles returns every workspace principal
195
173
  user_name_to_id: dict[str, str] = {}
196
174
  service_user_name_to_id: dict[str, str] = {}
197
- for principal in [*resp.principal_roles, *resp.additional_roles]:
175
+ for principal in resp.principal_roles:
198
176
  if principal.user_id:
199
177
  user_name_to_id[principal.user_name] = principal.user_id
200
178
  elif principal.service_user_id:
@@ -207,7 +185,7 @@ class _EnvironmentMembersManager:
207
185
  requests[f"User {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
208
186
  environment_id=self._environment.object_id,
209
187
  user_id=user_name_to_id[name],
210
- role=_role_to_proto(role),
188
+ role_str=role,
211
189
  )
212
190
  for name, role in service_users.items():
213
191
  if name not in service_user_name_to_id:
@@ -215,35 +193,70 @@ class _EnvironmentMembersManager:
215
193
  requests[f"Service user {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
216
194
  environment_id=self._environment.object_id,
217
195
  service_user_id=service_user_name_to_id[name],
218
- role=_role_to_proto(role),
196
+ role_str=role,
219
197
  )
220
198
 
221
199
  await self._dispatch_role_updates(requests)
222
200
 
201
+ async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
202
+ """Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
203
+ results = await asyncio.gather(
204
+ *(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
205
+ return_exceptions=True,
206
+ )
207
+ errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
208
+ if errors:
209
+ n = len(errors)
210
+ header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment roles:"
211
+ details = "\n".join(f" - {label}: {e}" for label, e in errors)
212
+ raise WorkspaceManagementError(f"{header}\n{details}")
213
+
214
+
215
+ # Deprecated alias for `_EnvironmentRolesManager`; each method warns and delegates.
216
+ class _EnvironmentMembersManager:
217
+ """mdmd:hidden"""
218
+
219
+ def __init__(self, environment: "_Environment"):
220
+ """mdmd:hidden"""
221
+ self._environment = environment
222
+
223
+ async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
224
+ deprecation_warning(
225
+ (2026, 7, 23),
226
+ "`Environment.members.list()` is deprecated; use `Environment.roles.list()` instead.",
227
+ )
228
+ return await _EnvironmentRolesManager(self._environment).list()
229
+
230
+ async def update(
231
+ self,
232
+ *,
233
+ users: Mapping[str, str] | None = None,
234
+ service_users: Mapping[str, str] | None = None,
235
+ ) -> None:
236
+ deprecation_warning(
237
+ (2026, 7, 23),
238
+ "`Environment.members.update()` is deprecated; use `Environment.roles.update()` instead.",
239
+ )
240
+ await _EnvironmentRolesManager(self._environment).update(users=users, service_users=service_users)
241
+
223
242
  async def remove(
224
243
  self,
225
244
  *,
226
245
  users: Iterable[str] | None = None,
227
246
  service_users: Iterable[str] | None = None,
228
247
  ) -> None:
229
- """Remove members from a restricted Environment.
230
-
231
- **Examples:**
232
-
233
- ```python notest
234
- env = modal.Environment.from_name("my-restricted-env")
235
- env.members.remove(
236
- users=["alice"],
237
- service_users=["alice-bot"],
248
+ """Remove the Environment Role of users and service users, reverting them to the default."""
249
+ deprecation_warning(
250
+ (2026, 7, 23),
251
+ "`Environment.members.remove()` is deprecated. Environment Roles are now explicit; "
252
+ "set a role (e.g. 'no-access') with `Environment.roles.update()` instead.",
238
253
  )
239
- ```
240
- """
241
254
  await self._environment.hydrate()
242
255
  users = users or []
243
256
  service_users = service_users or []
244
257
 
245
- req = api_pb2.EnvironmentGetManagedRequest(environment_id=self._environment.object_id)
246
- resp = await self._environment.client.stub.EnvironmentGetManaged(req)
258
+ req = api_pb2.EnvironmentGetRolesRequest(environment_id=self._environment.object_id)
259
+ resp = await self._environment.client.stub.EnvironmentGetRoles(req)
247
260
 
248
261
  user_name_to_id: dict[str, str] = {}
249
262
  service_user_name_to_id: dict[str, str] = {}
@@ -256,7 +269,7 @@ class _EnvironmentMembersManager:
256
269
  requests: dict[str, api_pb2.EnvironmentRoleSetRequest] = {}
257
270
  for name in users:
258
271
  if name not in user_name_to_id:
259
- raise InvalidError(f"User {name!r} is not a member of this Environment")
272
+ raise InvalidError(f"User {name!r} not found in workspace")
260
273
  requests[f"User {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
261
274
  environment_id=self._environment.object_id,
262
275
  user_id=user_name_to_id[name],
@@ -264,27 +277,14 @@ class _EnvironmentMembersManager:
264
277
  )
265
278
  for name in service_users:
266
279
  if name not in service_user_name_to_id:
267
- raise InvalidError(f"Service user {name!r} is not a member of this Environment")
280
+ raise InvalidError(f"Service user {name!r} not found in workspace")
268
281
  requests[f"Service user {name!r}"] = api_pb2.EnvironmentRoleSetRequest(
269
282
  environment_id=self._environment.object_id,
270
283
  service_user_id=service_user_name_to_id[name],
271
284
  role=api_pb2.ENVIRONMENT_ROLE_UNSPECIFIED,
272
285
  )
273
286
 
274
- await self._dispatch_role_updates(requests)
275
-
276
- async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
277
- """Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
278
- results = await asyncio.gather(
279
- *(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
280
- return_exceptions=True,
281
- )
282
- errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
283
- if errors:
284
- n = len(errors)
285
- header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment members:"
286
- details = "\n".join(f" - {label}: {e}" for label, e in errors)
287
- raise WorkspaceManagementError(f"{header}\n{details}")
287
+ await _EnvironmentRolesManager(self._environment)._dispatch_role_updates(requests)
288
288
 
289
289
 
290
290
  class _Environment(_Object, type_prefix="en"):
@@ -307,8 +307,18 @@ class _Environment(_Object, type_prefix="en"):
307
307
  def objects(cls) -> _EnvironmentManager:
308
308
  return _EnvironmentManager()
309
309
 
310
+ @property
311
+ def roles(self) -> "_EnvironmentRolesManager":
312
+ """Namespace with methods for managing the Environment Roles of users and service users.
313
+
314
+ See https://modal.com/docs/guide/rbac for more information on Environment Roles.
315
+ """
316
+ return _EnvironmentRolesManager(self)
317
+
310
318
  @property
311
319
  def members(self) -> "_EnvironmentMembersManager":
320
+ """mdmd:hidden"""
321
+ # Deprecated alias for `Environment.roles`.
312
322
  return _EnvironmentMembersManager(self)
313
323
 
314
324
  # TODO(michael) Keeping this private for now until we decide what else should be in it
@@ -393,13 +403,12 @@ class _Environment(_Object, type_prefix="en"):
393
403
 
394
404
  @property
395
405
  def billing(self) -> "_EnvironmentBillingManager":
406
+ """Namespace for Environment billing APIs."""
396
407
  return _EnvironmentBillingManager(self)
397
408
 
398
409
 
399
410
  class _EnvironmentBillingManager:
400
- """mdmd:namespace
401
- Namespace for Environment billing APIs
402
- """
411
+ """mdmd:namespace"""
403
412
 
404
413
  def __init__(self, environment: _Environment):
405
414
  """mdmd:ignore"""
@@ -436,7 +445,7 @@ class _EnvironmentBillingManager:
436
445
  - [`modal environment billing report`](https://modal.com/docs/cli/latest/environment#modal-environment-billing-report):
437
446
  An environment report CLI that has convenience features around relative time range queries
438
447
  and JSON/CSV output.
439
- - [`Workspace.billing.report()`](https://modal.com/docs/sdk/py/latest/modal.Workspace#billingreport):
448
+ - [`Workspace.billing.report()`](https://modal.com/docs/sdk/py/latest/Workspace#billingreport):
440
449
  An analogous report API for the entire Workspace.
441
450
 
442
451
  """
@@ -472,6 +481,71 @@ class _EnvironmentBillingManager:
472
481
  async for pb_item in self._environment.client.stub.WorkspaceBillingReport.unary_stream(request)
473
482
  ]
474
483
 
484
+ async def summary(
485
+ self,
486
+ cycle: str | datetime | None = None, # Start of the summary, inclusive
487
+ ) -> EnvironmentBillingSummary:
488
+ """Return a summary of environment cost over a single billing cycle determined by `cycle`.
489
+
490
+ Unlike the analogous `Workspace.billing.summary()`, this API only emits metered cost
491
+ information. This is because billing adjustments due to credits, free storage, etc. are
492
+ applied at the Workspace level, and thus cannot be attributed to individual Environments.
493
+
494
+ Args:
495
+ cycle: Start of the summary, inclusive. Must be the first of a month, and must be in UTC
496
+ or timezone-naive (interpreted as UTC). If provided as a string, it must either be
497
+ formatted as an ISO 8601 month (YYYY-MM), or must be one of the convenience spellings
498
+ "this month" or "last month". If not provided, `cycle` defaults to the first of the
499
+ current month (in which case a summary is generated for the current billing cycle).
500
+
501
+ Returns:
502
+ A single `EnvironmentBillingSummary` dataclass containing the following fields:
503
+ - `metered_cost` representing cost before any adjustments, and
504
+ - `metered_cost_breakdown` containing a breakdown of that cost by the Modal resources
505
+ that generated it. The exact keys of this are subject to change as Modal's billing
506
+ model evolves.
507
+
508
+ All values are reported as `decimal.Decimal`s.
509
+
510
+ See also:
511
+ - [`modal environment billing summary`](https://modal.com/docs/cli/latest/billing#modal-environment-billing-summary):
512
+ An environment summary CLI that has convenience features around relative time range queries.
513
+ - [`Environment.billing.report()`](https://modal.com/docs/sdk/py/latest/Environment#billingreport):
514
+ An analogous report API that is scoped to a specific Environment.
515
+ """
516
+
517
+ if cycle is None:
518
+ cycle = datetime.now(timezone.utc).replace(
519
+ day=1,
520
+ hour=0,
521
+ minute=0,
522
+ second=0,
523
+ microsecond=0,
524
+ )
525
+ elif isinstance(cycle, str):
526
+ cycle = parse_billing_cycle(cycle)
527
+
528
+ if cycle.tzinfo is None:
529
+ cycle = cycle.replace(tzinfo=timezone.utc)
530
+ elif cycle.tzinfo != timezone.utc:
531
+ raise InvalidError("Timezone-aware 'cycle' parameter must be in UTC.")
532
+
533
+ if not is_utc_month_aligned(cycle):
534
+ raise InvalidError("Provided 'cycle' parameter must be the first of a month.")
535
+
536
+ if cycle > datetime.now(timezone.utc):
537
+ raise InvalidError("Provided 'cycle' parameter cannot be in the future.")
538
+
539
+ if not self._environment.is_hydrated:
540
+ await self._environment.hydrate()
541
+
542
+ request = api_pb2.EnvironmentBillingSummaryRequest(environment_id=self._environment.object_id)
543
+ request.start_timestamp.FromDatetime(cycle)
544
+
545
+ return EnvironmentBillingSummary._from_proto(
546
+ await self._environment.client.stub.EnvironmentBillingSummary(request)
547
+ )
548
+
475
549
 
476
550
  ENVIRONMENT_CACHE: dict[str, _Environment] = {}
477
551
 
@@ -547,4 +621,4 @@ def ensure_env(environment_name: str | None = None) -> str:
547
621
  if environment_name is not None:
548
622
  config.override_locally("environment", environment_name)
549
623
 
550
- return config.get("environment")
624
+ return config.get("environment") or ""
@@ -171,7 +171,7 @@ class _FunctionOptions:
171
171
  replace_secret_ids=bool(self.secrets),
172
172
  replace_volume_mounts=len(volume_mounts) > 0,
173
173
  volume_mounts=volume_mounts,
174
- cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts),
174
+ cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts)[0],
175
175
  replace_cloud_bucket_mounts=bool(self.cloud_bucket_mounts),
176
176
  resources=self.resources,
177
177
  retry_policy=self.retry_policy,