modal 1.5.2.dev13__tar.gz → 1.5.2.dev14__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 (230) hide show
  1. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/PKG-INFO +1 -1
  2. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/grpc_utils.py +49 -9
  3. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/task_command_router_client.py +17 -5
  4. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_workspace.py +80 -0
  5. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/container.py +16 -5
  6. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/workspace.py +46 -0
  7. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/client.pyi +2 -2
  8. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/config.py +1 -1
  9. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/functions.pyi +6 -6
  10. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox.py +11 -10
  11. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox.pyi +24 -3
  12. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/workspace.py +5 -0
  13. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/workspace.pyi +129 -0
  14. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/PKG-INFO +1 -1
  15. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_version/__init__.py +1 -1
  16. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/LICENSE +0 -0
  17. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/README.md +0 -0
  18. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/__init__.py +0 -0
  19. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/__main__.py +0 -0
  20. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_billing.py +0 -0
  21. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_clustered_functions.py +0 -0
  22. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_clustered_functions.pyi +0 -0
  23. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_container_entrypoint.py +0 -0
  24. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_environments.py +0 -0
  25. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_function_variants.py +0 -0
  26. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_functions.py +0 -0
  27. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_grpc_client.py +0 -0
  28. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_image.py +0 -0
  29. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_ipython.py +0 -0
  30. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_load_context.py +0 -0
  31. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_location.py +0 -0
  32. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_logs.py +0 -0
  33. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_object.py +0 -0
  34. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/__init__.py +0 -0
  35. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/manager.py +0 -0
  36. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/pty.py +0 -0
  37. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/rich.py +0 -0
  38. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_output/status.py +0 -0
  39. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_partial_function.py +0 -0
  40. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_resolver.py +0 -0
  41. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_resources.py +0 -0
  42. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/__init__.py +0 -0
  43. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/asgi.py +0 -0
  44. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/container_io_manager.py +0 -0
  45. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
  46. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/execution_context.py +0 -0
  47. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/execution_context.pyi +0 -0
  48. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  49. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/task_lifecycle_manager.py +0 -0
  50. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  51. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/telemetry.py +0 -0
  52. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
  53. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_runtime/user_code_imports.py +0 -0
  54. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_serialization.py +0 -0
  55. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_server.py +0 -0
  56. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_traceback.py +0 -0
  57. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_tunnel.py +0 -0
  58. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_tunnel.pyi +0 -0
  59. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_type_manager.py +0 -0
  60. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/__init__.py +0 -0
  61. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/app_utils.py +0 -0
  62. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/async_utils.py +0 -0
  63. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/auth_token_manager.py +0 -0
  64. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/blob_utils.py +0 -0
  65. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/browser_utils.py +0 -0
  66. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
  67. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/curl_utils.py +0 -0
  68. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/deprecation.py +0 -0
  69. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/docker_utils.py +0 -0
  70. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/function_utils.py +0 -0
  71. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/git_utils.py +0 -0
  72. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/grpc_testing.py +0 -0
  73. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/hash_utils.py +0 -0
  74. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/http_utils.py +0 -0
  75. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/jwt_utils.py +0 -0
  76. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/logger.py +0 -0
  77. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/mount_utils.py +0 -0
  78. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/name_utils.py +0 -0
  79. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/package_utils.py +0 -0
  80. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/pattern_utils.py +0 -0
  81. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/proxy_support.py +0 -0
  82. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/rand_pb_testing.py +0 -0
  83. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
  84. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/shell_utils.py +0 -0
  85. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_utils/time_utils.py +0 -0
  86. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/__init__.py +0 -0
  87. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  88. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/cloudpickle.py +0 -0
  89. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/tblib.py +0 -0
  90. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_vendor/version.py +0 -0
  91. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/_watcher.py +0 -0
  92. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/app.py +0 -0
  93. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/app.pyi +0 -0
  94. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/billing.py +0 -0
  95. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/billing.pyi +0 -0
  96. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2023.12.312.txt +0 -0
  97. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2023.12.txt +0 -0
  98. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2024.04.txt +0 -0
  99. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2024.10.txt +0 -0
  100. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/2025.06.txt +0 -0
  101. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/PREVIEW.txt +0 -0
  102. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/README.md +0 -0
  103. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/builder/base-images.json +0 -0
  104. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/call_graph.py +0 -0
  105. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/__init__.py +0 -0
  106. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/_download.py +0 -0
  107. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/_help.py +0 -0
  108. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/_traceback.py +0 -0
  109. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/app.py +0 -0
  110. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/billing.py +0 -0
  111. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/bootstrap.py +0 -0
  112. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/changelog.py +0 -0
  113. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/cluster.py +0 -0
  114. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/config.py +0 -0
  115. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/curl.py +0 -0
  116. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/dashboard.py +0 -0
  117. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/dict.py +0 -0
  118. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/endpoint.py +0 -0
  119. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/entry_point.py +0 -0
  120. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/environment.py +0 -0
  121. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/image.py +0 -0
  122. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/import_refs.py +0 -0
  123. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/launch.py +0 -0
  124. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/logo.py +0 -0
  125. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/network_file_system.py +0 -0
  126. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/profile.py +0 -0
  127. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/programs/__init__.py +0 -0
  128. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/programs/run_jupyter.py +0 -0
  129. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/programs/vscode.py +0 -0
  130. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/queues.py +0 -0
  131. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/run.py +0 -0
  132. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/secret.py +0 -0
  133. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/selector.py +0 -0
  134. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/shell.py +0 -0
  135. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/skills.py +0 -0
  136. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/token.py +0 -0
  137. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/utils.py +0 -0
  138. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cli/volume.py +0 -0
  139. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/client.py +0 -0
  140. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cloud_bucket_mount.py +0 -0
  141. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cloud_bucket_mount.pyi +0 -0
  142. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cls.py +0 -0
  143. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/cls.pyi +0 -0
  144. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/container_process.py +0 -0
  145. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/container_process.pyi +0 -0
  146. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/dict.py +0 -0
  147. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/dict.pyi +0 -0
  148. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/environments.py +0 -0
  149. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/environments.pyi +0 -0
  150. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/exception.py +0 -0
  151. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/__init__.py +0 -0
  152. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/flash.py +0 -0
  153. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/flash.pyi +0 -0
  154. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/experimental/ipython.py +0 -0
  155. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/file_io.py +0 -0
  156. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/file_io.pyi +0 -0
  157. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/file_pattern_matcher.py +0 -0
  158. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/functions.py +0 -0
  159. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/image.py +0 -0
  160. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/image.pyi +0 -0
  161. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/io_streams.py +0 -0
  162. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/io_streams.pyi +0 -0
  163. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/mount.py +0 -0
  164. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/mount.pyi +0 -0
  165. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/network_file_system.py +0 -0
  166. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/network_file_system.pyi +0 -0
  167. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/object.py +0 -0
  168. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/object.pyi +0 -0
  169. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/output.py +0 -0
  170. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/parallel_map.py +0 -0
  171. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/parallel_map.pyi +0 -0
  172. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/partial_function.py +0 -0
  173. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/partial_function.pyi +0 -0
  174. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/proxy.py +0 -0
  175. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/proxy.pyi +0 -0
  176. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/py.typed +0 -0
  177. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/queue.py +0 -0
  178. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/queue.pyi +0 -0
  179. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/retries.py +0 -0
  180. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/runner.py +0 -0
  181. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/runner.pyi +0 -0
  182. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/running_app.py +0 -0
  183. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox_fs.py +0 -0
  184. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/sandbox_fs.pyi +0 -0
  185. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/schedule.py +0 -0
  186. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/scheduler_placement.py +0 -0
  187. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/secret.py +0 -0
  188. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/secret.pyi +0 -0
  189. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/server.py +0 -0
  190. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/server.pyi +0 -0
  191. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/serving.py +0 -0
  192. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/serving.pyi +0 -0
  193. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/skills/modal/SKILL.md +0 -0
  194. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/snapshot.py +0 -0
  195. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/snapshot.pyi +0 -0
  196. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/stream_type.py +0 -0
  197. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/token_flow.py +0 -0
  198. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/token_flow.pyi +0 -0
  199. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/volume.py +0 -0
  200. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal/volume.pyi +0 -0
  201. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/SOURCES.txt +0 -0
  202. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/dependency_links.txt +0 -0
  203. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/entry_points.txt +0 -0
  204. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/requires.txt +0 -0
  205. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal.egg-info/top_level.txt +0 -0
  206. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/__init__.py +0 -0
  207. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_cli_docs.py +0 -0
  208. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
  209. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_reference_docs.py +0 -0
  210. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
  211. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/__init__.py +0 -0
  212. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/mdmd.py +0 -0
  213. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/signatures.py +0 -0
  214. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_docs/mdmd/types.py +0 -0
  215. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/__init__.py +0 -0
  216. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_grpc.py +0 -0
  217. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2.py +0 -0
  218. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2.pyi +0 -0
  219. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2_grpc.py +0 -0
  220. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/api_pb2_grpc.pyi +0 -0
  221. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/modal_api_grpc.py +0 -0
  222. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/py.typed +0 -0
  223. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_grpc.py +0 -0
  224. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2.py +0 -0
  225. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
  226. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  227. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  228. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/modal_version/__main__.py +0 -0
  229. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/pyproject.toml +0 -0
  230. {modal-1.5.2.dev13 → modal-1.5.2.dev14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev13
3
+ Version: 1.5.2.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -31,7 +31,7 @@ from modal_version import __version__
31
31
 
32
32
  from .._traceback import suppress_tb_frame
33
33
  from ..config import config
34
- from .async_utils import retry
34
+ from .async_utils import TaskContext, retry
35
35
  from .logger import logger
36
36
 
37
37
  RequestType = TypeVar("RequestType", bound=Message)
@@ -190,14 +190,7 @@ class ConnectionManager:
190
190
 
191
191
  async def get_or_create_channel(self, server_url: str) -> grpclib.client.Channel:
192
192
  if server_url not in self._channels:
193
- self._channels[server_url] = create_channel(
194
- server_url,
195
- self._metadata,
196
- )
197
- try:
198
- await connect_channel(self._channels[server_url])
199
- except (OSError, asyncio.TimeoutError) as exc:
200
- raise ConnectionError("Could not connect to the Modal server.") from exc
193
+ self._channels[server_url] = await create_channel_with_fallbacks(server_url, self._metadata)
201
194
  return self._channels[server_url]
202
195
 
203
196
  def close(self):
@@ -296,6 +289,12 @@ def create_channel(
296
289
  for k, v in metadata.items():
297
290
  event.metadata[k] = v
298
291
 
292
+ if o.hostname is not None:
293
+ # GRPC actually injects a :authority pseudo-header containing the hostname.
294
+ # But this gets stripped out by the C/C++ extension server-side and is hard to fix.
295
+ # So let's just add a separate header we can parse easily.
296
+ event.metadata["x-modal-host"] = o.hostname
297
+
299
298
  idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
300
299
  if idempotency_key is None:
301
300
  logger.debug(f"Sending request to {event.method_name}")
@@ -314,6 +313,47 @@ async def connect_channel(channel: grpclib.client.Channel):
314
313
  await channel.__connect__()
315
314
 
316
315
 
316
+ async def create_channel_with_fallbacks(
317
+ server_url: str,
318
+ metadata: dict[str, str] = {},
319
+ stagger_delay: float = 1.0,
320
+ ) -> grpclib.client.Channel:
321
+ """Create a connected channel, supporting comma-separated fallback URLs.
322
+
323
+ `server_url` may be a single URL or a comma-separated list of URLs. The connection attempts
324
+ are raced against each other and the first one to connect wins, which lets the client fail
325
+ over to a secondary endpoint when the primary is unreachable (e.g. a DNS outage on the
326
+ primary's domain). Attempts are staggered: the candidate at index `i` waits `i * stagger_delay`
327
+ seconds before starting, so earlier URLs (e.g. the primary) get a head start and are preferred
328
+ when reachable. Losing attempts are cancelled and their channels closed.
329
+ """
330
+ urls = [url.strip() for url in server_url.split(",")]
331
+
332
+ async def connect(index: int, url: str) -> grpclib.client.Channel:
333
+ # Stagger the start so earlier URLs are preferred; once started, all attempts race.
334
+ await asyncio.sleep(index * stagger_delay)
335
+ channel = create_channel(url, metadata)
336
+ try:
337
+ await connect_channel(channel)
338
+ except BaseException:
339
+ # Close the channel of a losing/cancelled attempt so its socket doesn't leak.
340
+ channel.close()
341
+ raise
342
+ return channel
343
+
344
+ first_exc: BaseException | None = None
345
+ async with TaskContext() as tc:
346
+ tasks = [tc.create_task(connect(i, url)) for i, url in enumerate(urls)]
347
+ for attempt in asyncio.as_completed(tasks):
348
+ try:
349
+ return await attempt
350
+ except Exception as exc:
351
+ if first_exc is None:
352
+ first_exc = exc
353
+
354
+ raise ConnectionError("Could not connect to the Modal server.") from first_exc
355
+
356
+
317
357
  if typing.TYPE_CHECKING:
318
358
  import modal.client
319
359
 
@@ -811,13 +811,25 @@ class TaskCommandRouterClient:
811
811
  lambda: self._call_with_auth_retry(self._stub.TaskSetNetworkAccess, request)
812
812
  )
813
813
 
814
- async def reload_volumes(self, task_id: str) -> sr_pb2.TaskReloadVolumesResponse:
815
- """Reload all Volumes mounted in the task to reflect their latest committed state."""
814
+ async def reload_volumes(self, task_id: str, timeout: float) -> sr_pb2.TaskReloadVolumesResponse:
815
+ """Reload all Volumes mounted in the task to reflect their latest committed state.
816
+
817
+ Args:
818
+ task_id: The task whose mounted Volumes should be reloaded.
819
+ timeout: Client-side deadline in seconds. If the reload does not complete within this
820
+ window, the call is cancelled and a `modal.exception.TimeoutError` is raised.
821
+ """
816
822
  request = sr_pb2.TaskReloadVolumesRequest(task_id=task_id)
817
823
  with grpc_error_converter():
818
- return await call_with_retries_on_transient_errors(
819
- lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request)
820
- )
824
+ try:
825
+ return await asyncio.wait_for(
826
+ call_with_retries_on_transient_errors(
827
+ lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request, timeout=timeout),
828
+ ),
829
+ timeout=timeout,
830
+ )
831
+ except asyncio.TimeoutError:
832
+ raise ModalTimeoutError("Timeout expired")
821
833
 
822
834
  async def _snapshot_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
823
835
  # helper method for snapshot_directory and snapshot_filesystem to handle grpc
@@ -125,6 +125,10 @@ class _Workspace(_Object, type_prefix="ac"):
125
125
  def proxy_tokens(self) -> "_WorkspaceProxyTokenManager":
126
126
  return _WorkspaceProxyTokenManager(self)
127
127
 
128
+ @property
129
+ def settings(self) -> "_WorkspaceSettingsManager":
130
+ return _WorkspaceSettingsManager(self)
131
+
128
132
 
129
133
  @dataclass(frozen=True)
130
134
  class TokenData:
@@ -340,3 +344,79 @@ class _WorkspaceBillingManager:
340
344
  BillingReportItem._from_proto(pb_item)
341
345
  async for pb_item in self._workspace.client.stub.WorkspaceBillingReport.unary_stream(request)
342
346
  ]
347
+
348
+
349
+ @dataclass(frozen=True)
350
+ class WorkspaceSettings:
351
+ """Current settings for the workspace."""
352
+
353
+ default_environment: str
354
+ image_builder_version: str
355
+
356
+
357
+ class _WorkspaceSettingsManager:
358
+ """mdmd:namespace
359
+ Namespace for Workspace settings APIs.
360
+ """
361
+
362
+ @classmethod
363
+ def valid_settings(cls):
364
+ return ("default-environment", "image-builder-version")
365
+
366
+ def __init__(self, workspace: _Workspace):
367
+ """mdmd:hidden"""
368
+ self._workspace = workspace
369
+
370
+ async def list(self):
371
+ """Return a the current workspace settings.
372
+
373
+ Returns:
374
+ A `WorkspaceSettings` dataclass.
375
+ """
376
+ if not self._workspace.is_hydrated:
377
+ await self._workspace.hydrate()
378
+ resp = await self._workspace.client.stub.WorkspaceSettings(Empty())
379
+ return WorkspaceSettings(
380
+ default_environment=resp.default_environment_name, image_builder_version=resp.image_builder_version
381
+ )
382
+
383
+ async def _set_image_builder_version(self, version: str) -> None:
384
+ """mdmd:hidden
385
+ Set the image builder version for the Workspace.
386
+ """
387
+ if not self._workspace.is_hydrated:
388
+ await self._workspace.hydrate()
389
+ req = api_pb2.WorkspaceSetImageBuilderVersionRequest(new_image_builder_version=version)
390
+ await self._workspace.client.stub.WorkspaceSetImageBuilderVersion(req)
391
+
392
+ async def _set_default_environment(self, name: str) -> None:
393
+ """Set the default environment for the Workspace."""
394
+ if not self._workspace.is_hydrated:
395
+ await self._workspace.hydrate()
396
+ req = api_pb2.WorkspaceSetDefaultEnvironmentRequest(environment_name=name)
397
+ await self._workspace.client.stub.WorkspaceSetDefaultEnvironment(req)
398
+
399
+ async def set(self, name: str, value: str) -> None:
400
+ """Set a workspace setting to a new value. Must be workspace manager or owner.
401
+
402
+ The following settings can be updated:
403
+
404
+ - image-builder-version: The image builder version determines the software included in our base images.
405
+ - default-environment: The default environment when the environment is omitted from SDK or CLI methods.
406
+
407
+ Args:
408
+ name: The name of the setting.
409
+ value: The new value of the setting.
410
+
411
+ Examples:
412
+ ```python notest
413
+ modal.Workspace.from_context().settings.set("default-environment", "dev")
414
+ ```
415
+ """
416
+ match name:
417
+ case "image-builder-version":
418
+ await self._set_image_builder_version(value)
419
+ case "default-environment":
420
+ await self._set_default_environment(value)
421
+ case _:
422
+ raise ValueError(f"Unknown setting {name!r}. Valid settings: {', '.join(self.valid_settings())}")
@@ -332,14 +332,25 @@ def exec(
332
332
 
333
333
  @container_cli.command("stop")
334
334
  @click.argument("container_id")
335
+ @click.option(
336
+ "--graceful",
337
+ is_flag=True,
338
+ default=False,
339
+ help="Let the container finish its current inputs before exiting, instead of cancelling them.",
340
+ )
335
341
  @yes_option
336
342
  @synchronizer.create_blocking
337
- async def stop(container_id: str = "", *, yes: bool = False):
343
+ async def stop(container_id: str = "", *, graceful: bool = False, yes: bool = False):
338
344
  """Terminate a running container.
339
345
 
340
- This will send the container a SIGINT signal that Modal will handle.
341
- Any inputs that are currently running on the container will be cancelled and rescheduled
342
- on other containers.
346
+ By default, this will send the container a SIGINT signal that Modal will handle.
347
+ For Functions, any inputs that are currently running on the container will be cancelled
348
+ and rescheduled on other containers.
349
+
350
+ With `--graceful`, the container will be allowed to finish the inputs it is currently
351
+ running, exiting once they complete. Graceful stops are only supported for containers
352
+ running a Modal Function.
353
+
343
354
  """
344
355
  client = await _Client.from_env()
345
356
  resp = await client.stub.TaskGetInfo(api_pb2.TaskGetInfoRequest(task_id=container_id))
@@ -347,5 +358,5 @@ async def stop(container_id: str = "", *, yes: bool = False):
347
358
  raise SystemExit(f"Container '{container_id}' is already stopped.")
348
359
  if not yes:
349
360
  confirm_or_suggest_yes(f"Are you sure you want to stop container '{container_id}'?")
350
- request = api_pb2.ContainerStopRequest(task_id=container_id)
361
+ request = api_pb2.ContainerStopRequest(task_id=container_id, graceful=graceful)
351
362
  await client.stub.ContainerStop(request)
@@ -127,3 +127,49 @@ def proxy_tokens_delete(token_id: str, yes: bool = False):
127
127
  confirm_or_suggest_yes(message)
128
128
  Workspace.from_context().proxy_tokens.delete(token_id)
129
129
  rich.print(f"[green]✓[/green] Deleted proxy token {token_id!r}")
130
+
131
+
132
+ settings_cli = ModalGroup(name="settings", help="Manage workspace settings. Must be workspace manager or owner.")
133
+ workspace_cli.add_command(settings_cli)
134
+
135
+
136
+ @settings_cli.command("list")
137
+ @click.option("--json", is_flag=True, default=False)
138
+ @click.pass_context
139
+ def settings_group(
140
+ ctx: click.Context,
141
+ json: bool,
142
+ ):
143
+ """View the current settings for the workspace."""
144
+ if ctx.invoked_subcommand is not None:
145
+ return
146
+ s = Workspace.from_context().settings.list()
147
+ rows = [
148
+ ["default-environment", s.default_environment],
149
+ ["image-builder-version", s.image_builder_version],
150
+ ]
151
+ display_table(["Setting", "Value"], rows, json=json)
152
+
153
+
154
+ @settings_cli.command("set", no_args_is_help=True)
155
+ @click.argument("setting")
156
+ @click.argument("value")
157
+ def settings_set(setting: str, value: str):
158
+ """Update a workspace setting. Must be workspace manager or owner.
159
+
160
+ The following settings can be updated:
161
+ - `image-builder-version`: The image builder version determines the software included in our base images.
162
+ - `default-environment`: The default environment to use when the environment is omitted from SDK or CLI methods.
163
+
164
+ Usage:
165
+ - `modal workspace settings set image-builder-version 2025.06`
166
+ - `modal workspace settings set default-environment main`
167
+ """
168
+ ws = Workspace.from_context().hydrate()
169
+ s = ws.settings.list()
170
+ try:
171
+ ws.settings.set(setting, value)
172
+ rich.print(f"[green]✓[/green] {setting}: updated from {getattr(s, setting.replace('-', '_'))} to {value}")
173
+ except Exception as e:
174
+ rich.print(f"[red]✗[/red] {setting}: {e}")
175
+ raise SystemExit(1)
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.5.2.dev13",
38
+ version: str = "1.5.2.dev14",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -205,7 +205,7 @@ class Client:
205
205
  server_url: str,
206
206
  client_type: int,
207
207
  credentials: typing.Optional[tuple[str, str]],
208
- version: str = "1.5.2.dev13",
208
+ version: str = "1.5.2.dev14",
209
209
  ):
210
210
  """mdmd:hidden
211
211
  The Modal client object is not intended to be instantiated directly by users.
@@ -106,7 +106,7 @@ from modal_proto import api_pb2
106
106
  from ._utils.logger import configure_logger
107
107
  from .exception import InvalidError, NotFoundError
108
108
 
109
- DEFAULT_SERVER_URL = "https://api.modal.com"
109
+ DEFAULT_SERVER_URL = "https://api.modal.com" # TODO(erikbern): add modal2.com
110
110
 
111
111
 
112
112
  # Locate config file and read it
@@ -491,7 +491,7 @@ class Function(
491
491
 
492
492
  _call_generator: ___call_generator_spec
493
493
 
494
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
494
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
495
495
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
496
496
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
497
497
 
@@ -516,7 +516,7 @@ class Function(
516
516
  """
517
517
  ...
518
518
 
519
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
519
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
520
520
 
521
521
  class __remote_gen_spec(typing_extensions.Protocol):
522
522
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -566,7 +566,7 @@ class Function(
566
566
  """
567
567
  ...
568
568
 
569
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
569
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
570
570
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
571
571
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
572
572
 
@@ -599,7 +599,7 @@ class Function(
599
599
  """
600
600
  ...
601
601
 
602
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
602
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
603
603
 
604
604
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
605
605
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -607,7 +607,7 @@ class Function(
607
607
 
608
608
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
609
609
 
610
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
610
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
611
611
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
612
612
  """Calls the function with the given arguments, without waiting for the results.
613
613
 
@@ -640,7 +640,7 @@ class Function(
640
640
  """
641
641
  ...
642
642
 
643
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
643
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
644
644
 
645
645
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
646
646
  """Return the inner Python object wrapped by this Modal Function.
@@ -1637,22 +1637,23 @@ class _Sandbox(_Object, type_prefix="sb"):
1637
1637
  resp = await self._client.stub.SandboxCreateConnectToken(req)
1638
1638
  return SandboxConnectCredentials(resp.url, resp.token)
1639
1639
 
1640
- async def reload_volumes(self) -> None:
1640
+ async def reload_volumes(self, *, timeout: int = 55) -> None:
1641
1641
  """Reload all Volumes mounted in the Sandbox.
1642
1642
 
1643
1643
  Added in v1.1.0.
1644
1644
 
1645
+ Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
1646
+ does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
1647
+ still complete in the background.
1648
+
1649
+ Args:
1650
+ timeout: Maximum time in seconds to wait for the reload. Must be positive.
1645
1651
  """
1652
+ if timeout <= 0:
1653
+ raise InvalidError("The `timeout` argument to `Sandbox.reload_volumes` must be positive.")
1646
1654
  task_id = await self._get_task_id()
1647
- if self._is_v2:
1648
- command_router_client = await self._get_command_router_client(task_id)
1649
- await command_router_client.reload_volumes(task_id)
1650
- else:
1651
- await self._client.stub.ContainerReloadVolumes(
1652
- api_pb2.ContainerReloadVolumesRequest(
1653
- task_id=task_id,
1654
- ),
1655
- )
1655
+ command_router_client = await self._get_command_router_client(task_id)
1656
+ await command_router_client.reload_volumes(task_id, timeout=float(timeout))
1656
1657
 
1657
1658
  @overload
1658
1659
  async def terminate(
@@ -711,10 +711,17 @@ class _Sandbox(modal._object._Object):
711
711
  """
712
712
  ...
713
713
 
714
- async def reload_volumes(self) -> None:
714
+ async def reload_volumes(self, *, timeout: int = 55) -> None:
715
715
  """Reload all Volumes mounted in the Sandbox.
716
716
 
717
717
  Added in v1.1.0.
718
+
719
+ Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
720
+ does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
721
+ still complete in the background.
722
+
723
+ Args:
724
+ timeout: Maximum time in seconds to wait for the reload. Must be positive.
718
725
  """
719
726
  ...
720
727
 
@@ -2389,17 +2396,31 @@ class Sandbox(modal.object.Object):
2389
2396
  create_connect_token: __create_connect_token_spec
2390
2397
 
2391
2398
  class __reload_volumes_spec(typing_extensions.Protocol):
2392
- def __call__(self, /) -> None:
2399
+ def __call__(self, /, *, timeout: int = 55) -> None:
2393
2400
  """Reload all Volumes mounted in the Sandbox.
2394
2401
 
2395
2402
  Added in v1.1.0.
2403
+
2404
+ Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
2405
+ does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
2406
+ still complete in the background.
2407
+
2408
+ Args:
2409
+ timeout: Maximum time in seconds to wait for the reload. Must be positive.
2396
2410
  """
2397
2411
  ...
2398
2412
 
2399
- async def aio(self, /) -> None:
2413
+ async def aio(self, /, *, timeout: int = 55) -> None:
2400
2414
  """Reload all Volumes mounted in the Sandbox.
2401
2415
 
2402
2416
  Added in v1.1.0.
2417
+
2418
+ Blocks until the Volumes have been reloaded, bounded by `timeout` (55 seconds by default). If the reload
2419
+ does not complete within that window, `modal.exception.TimeoutError` is raised; note that the reload may
2420
+ still complete in the background.
2421
+
2422
+ Args:
2423
+ timeout: Maximum time in seconds to wait for the reload. Must be positive.
2403
2424
  """
2404
2425
  ...
2405
2426
 
@@ -4,16 +4,19 @@ from ._workspace import (
4
4
  ProxyTokenInfo,
5
5
  TokenData,
6
6
  WorkspaceMemberInfo,
7
+ WorkspaceSettings,
7
8
  _Workspace,
8
9
  _WorkspaceBillingManager,
9
10
  _WorkspaceMembersManager,
10
11
  _WorkspaceProxyTokenManager,
12
+ _WorkspaceSettingsManager,
11
13
  )
12
14
 
13
15
  Workspace = synchronize_api(_Workspace, target_module=__name__)
14
16
  WorkspaceMembersManager = synchronize_api(_WorkspaceMembersManager, target_module=__name__)
15
17
  WorkspaceBillingManager = synchronize_api(_WorkspaceBillingManager, target_module=__name__)
16
18
  WorkspaceProxyTokenManager = synchronize_api(_WorkspaceProxyTokenManager, target_module=__name__)
19
+ WorkspaceSettingsManager = synchronize_api(_WorkspaceSettingsManager, target_module=__name__)
17
20
 
18
21
  __all__ = [
19
22
  "ProxyTokenInfo",
@@ -22,4 +25,6 @@ __all__ = [
22
25
  "WorkspaceMemberInfo",
23
26
  "WorkspaceMembersManager",
24
27
  "WorkspaceProxyTokenManager",
28
+ "WorkspaceSettingsManager",
29
+ "WorkspaceSettings",
25
30
  ]
@@ -109,6 +109,36 @@ class WorkspaceMemberInfo:
109
109
  """Return hash(self)."""
110
110
  ...
111
111
 
112
+ class WorkspaceSettings:
113
+ """Current settings for the workspace."""
114
+
115
+ default_environment: str
116
+ image_builder_version: str
117
+
118
+ def __init__(self, default_environment: str, image_builder_version: str) -> None:
119
+ """Initialize self. See help(type(self)) for accurate signature."""
120
+ ...
121
+
122
+ def __repr__(self):
123
+ """Return repr(self)."""
124
+ ...
125
+
126
+ def __eq__(self, other):
127
+ """Return self==value."""
128
+ ...
129
+
130
+ def __setattr__(self, name, value):
131
+ """Implement setattr(self, name, value)."""
132
+ ...
133
+
134
+ def __delattr__(self, name):
135
+ """Implement delattr(self, name)."""
136
+ ...
137
+
138
+ def __hash__(self):
139
+ """Return hash(self)."""
140
+ ...
141
+
112
142
  class Workspace(modal.object.Object):
113
143
  _name: typing.Optional[str]
114
144
 
@@ -135,6 +165,8 @@ class Workspace(modal.object.Object):
135
165
  def billing(self) -> WorkspaceBillingManager: ...
136
166
  @property
137
167
  def proxy_tokens(self) -> WorkspaceProxyTokenManager: ...
168
+ @property
169
+ def settings(self) -> WorkspaceSettingsManager: ...
138
170
 
139
171
  class WorkspaceMembersManager:
140
172
  """mdmd:namespace
@@ -448,3 +480,100 @@ class WorkspaceProxyTokenManager:
448
480
  async def aio(self, /, environment_name: str) -> str: ...
449
481
 
450
482
  _environment_id: ___environment_id_spec
483
+
484
+ class WorkspaceSettingsManager:
485
+ """mdmd:namespace
486
+ Namespace for Workspace settings APIs.
487
+ """
488
+ def __init__(self, workspace: Workspace):
489
+ """mdmd:hidden"""
490
+ ...
491
+
492
+ @classmethod
493
+ def valid_settings(cls): ...
494
+
495
+ class __list_spec(typing_extensions.Protocol):
496
+ def __call__(self, /):
497
+ """Return a the current workspace settings.
498
+
499
+ Returns:
500
+ A `WorkspaceSettings` dataclass.
501
+ """
502
+ ...
503
+
504
+ async def aio(self, /):
505
+ """Return a the current workspace settings.
506
+
507
+ Returns:
508
+ A `WorkspaceSettings` dataclass.
509
+ """
510
+ ...
511
+
512
+ list: __list_spec
513
+
514
+ class ___set_image_builder_version_spec(typing_extensions.Protocol):
515
+ def __call__(self, /, version: str) -> None:
516
+ """mdmd:hidden
517
+ Set the image builder version for the Workspace.
518
+ """
519
+ ...
520
+
521
+ async def aio(self, /, version: str) -> None:
522
+ """mdmd:hidden
523
+ Set the image builder version for the Workspace.
524
+ """
525
+ ...
526
+
527
+ _set_image_builder_version: ___set_image_builder_version_spec
528
+
529
+ class ___set_default_environment_spec(typing_extensions.Protocol):
530
+ def __call__(self, /, name: str) -> None:
531
+ """Set the default environment for the Workspace."""
532
+ ...
533
+
534
+ async def aio(self, /, name: str) -> None:
535
+ """Set the default environment for the Workspace."""
536
+ ...
537
+
538
+ _set_default_environment: ___set_default_environment_spec
539
+
540
+ class __set_spec(typing_extensions.Protocol):
541
+ def __call__(self, /, name: str, value: str) -> None:
542
+ """Set a workspace setting to a new value. Must be workspace manager or owner.
543
+
544
+ The following settings can be updated:
545
+
546
+ - image-builder-version: The image builder version determines the software included in our base images.
547
+ - default-environment: The default environment when the environment is omitted from SDK or CLI methods.
548
+
549
+ Args:
550
+ name: The name of the setting.
551
+ value: The new value of the setting.
552
+
553
+ Examples:
554
+ ```python notest
555
+ modal.Workspace.from_context().settings.set("default-environment", "dev")
556
+ ```
557
+ """
558
+ ...
559
+
560
+ async def aio(self, /, name: str, value: str) -> None:
561
+ """Set a workspace setting to a new value. Must be workspace manager or owner.
562
+
563
+ The following settings can be updated:
564
+
565
+ - image-builder-version: The image builder version determines the software included in our base images.
566
+ - default-environment: The default environment when the environment is omitted from SDK or CLI methods.
567
+
568
+ Args:
569
+ name: The name of the setting.
570
+ value: The new value of the setting.
571
+
572
+ Examples:
573
+ ```python notest
574
+ modal.Workspace.from_context().settings.set("default-environment", "dev")
575
+ ```
576
+ """
577
+ ...
578
+
579
+ set: __set_spec
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev13
3
+ Version: 1.5.2.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2026
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.5.2.dev13"
4
+ __version__ = "1.5.2.dev14"
File without changes
File without changes