modal 1.2.3.dev8__tar.gz → 1.2.3.dev10__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 (200) hide show
  1. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/PKG-INFO +1 -1
  2. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_functions.py +9 -4
  3. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/grpc_utils.py +57 -4
  4. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/app.py +47 -21
  5. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/app.pyi +8 -4
  6. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/run.py +1 -1
  7. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/client.pyi +2 -2
  8. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cls.py +5 -3
  9. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/functions.pyi +8 -8
  10. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/image.py +1 -4
  11. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/sandbox.py +6 -15
  12. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/sandbox.pyi +0 -9
  13. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/PKG-INFO +1 -1
  14. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api.proto +13 -0
  15. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_grpc.py +1 -0
  16. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2.py +1032 -1021
  17. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2.pyi +29 -0
  18. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_version/__init__.py +1 -1
  19. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/LICENSE +0 -0
  20. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/README.md +0 -0
  21. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/__init__.py +0 -0
  22. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/__main__.py +0 -0
  23. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_billing.py +0 -0
  24. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_clustered_functions.py +0 -0
  25. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_clustered_functions.pyi +0 -0
  26. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_container_entrypoint.py +0 -0
  27. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_grpc_client.py +0 -0
  28. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_ipython.py +0 -0
  29. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_load_context.py +0 -0
  30. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_location.py +0 -0
  31. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_object.py +0 -0
  32. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_output.py +0 -0
  33. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_partial_function.py +0 -0
  34. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_pty.py +0 -0
  35. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_resolver.py +0 -0
  36. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_resources.py +0 -0
  37. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/__init__.py +0 -0
  38. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/asgi.py +0 -0
  39. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/container_io_manager.py +0 -0
  40. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
  41. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/execution_context.py +0 -0
  42. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/execution_context.pyi +0 -0
  43. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  44. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/telemetry.py +0 -0
  45. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/user_code_imports.py +0 -0
  46. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_serialization.py +0 -0
  47. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_traceback.py +0 -0
  48. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_tunnel.py +0 -0
  49. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_tunnel.pyi +0 -0
  50. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_type_manager.py +0 -0
  51. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/__init__.py +0 -0
  52. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/app_utils.py +0 -0
  53. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/async_utils.py +0 -0
  54. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/auth_token_manager.py +0 -0
  55. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/blob_utils.py +0 -0
  56. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
  57. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/deprecation.py +0 -0
  58. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/docker_utils.py +0 -0
  59. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/function_utils.py +0 -0
  60. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/git_utils.py +0 -0
  61. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/grpc_testing.py +0 -0
  62. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/hash_utils.py +0 -0
  63. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/http_utils.py +0 -0
  64. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/jwt_utils.py +0 -0
  65. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/logger.py +0 -0
  66. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/mount_utils.py +0 -0
  67. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/name_utils.py +0 -0
  68. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/package_utils.py +0 -0
  69. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/pattern_utils.py +0 -0
  70. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/rand_pb_testing.py +0 -0
  71. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/shell_utils.py +0 -0
  72. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/task_command_router_client.py +0 -0
  73. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/time_utils.py +0 -0
  74. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/__init__.py +0 -0
  75. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  76. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/cloudpickle.py +0 -0
  77. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/tblib.py +0 -0
  78. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_watcher.py +0 -0
  79. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/billing.py +0 -0
  80. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2023.12.312.txt +0 -0
  81. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2023.12.txt +0 -0
  82. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2024.04.txt +0 -0
  83. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2024.10.txt +0 -0
  84. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2025.06.txt +0 -0
  85. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/PREVIEW.txt +0 -0
  86. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/README.md +0 -0
  87. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/base-images.json +0 -0
  88. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/call_graph.py +0 -0
  89. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/__init__.py +0 -0
  90. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/_download.py +0 -0
  91. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/_traceback.py +0 -0
  92. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/app.py +0 -0
  93. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/cluster.py +0 -0
  94. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/config.py +0 -0
  95. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/container.py +0 -0
  96. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/dict.py +0 -0
  97. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/entry_point.py +0 -0
  98. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/environment.py +0 -0
  99. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/import_refs.py +0 -0
  100. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/launch.py +0 -0
  101. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/network_file_system.py +0 -0
  102. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/profile.py +0 -0
  103. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/__init__.py +0 -0
  104. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/launch_instance_ssh.py +0 -0
  105. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/run_jupyter.py +0 -0
  106. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/run_marimo.py +0 -0
  107. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/vscode.py +0 -0
  108. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/queues.py +0 -0
  109. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/secret.py +0 -0
  110. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/token.py +0 -0
  111. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/utils.py +0 -0
  112. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/volume.py +0 -0
  113. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/client.py +0 -0
  114. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cloud_bucket_mount.py +0 -0
  115. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cloud_bucket_mount.pyi +0 -0
  116. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cls.pyi +0 -0
  117. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/config.py +0 -0
  118. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/container_process.py +0 -0
  119. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/container_process.pyi +0 -0
  120. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/dict.py +0 -0
  121. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/dict.pyi +0 -0
  122. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/environments.py +0 -0
  123. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/environments.pyi +0 -0
  124. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/exception.py +0 -0
  125. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/__init__.py +0 -0
  126. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/flash.py +0 -0
  127. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/flash.pyi +0 -0
  128. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/ipython.py +0 -0
  129. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/file_io.py +0 -0
  130. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/file_io.pyi +0 -0
  131. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/file_pattern_matcher.py +0 -0
  132. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/functions.py +0 -0
  133. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/gpu.py +0 -0
  134. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/image.pyi +0 -0
  135. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/io_streams.py +0 -0
  136. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/io_streams.pyi +0 -0
  137. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/mount.py +0 -0
  138. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/mount.pyi +0 -0
  139. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/network_file_system.py +0 -0
  140. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/network_file_system.pyi +0 -0
  141. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/object.py +0 -0
  142. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/object.pyi +0 -0
  143. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/output.py +0 -0
  144. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/parallel_map.py +0 -0
  145. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/parallel_map.pyi +0 -0
  146. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/partial_function.py +0 -0
  147. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/partial_function.pyi +0 -0
  148. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/proxy.py +0 -0
  149. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/proxy.pyi +0 -0
  150. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/py.typed +0 -0
  151. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/queue.py +0 -0
  152. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/queue.pyi +0 -0
  153. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/retries.py +0 -0
  154. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/runner.py +0 -0
  155. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/runner.pyi +0 -0
  156. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/running_app.py +0 -0
  157. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/schedule.py +0 -0
  158. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/scheduler_placement.py +0 -0
  159. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/secret.py +0 -0
  160. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/secret.pyi +0 -0
  161. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/serving.py +0 -0
  162. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/serving.pyi +0 -0
  163. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/snapshot.py +0 -0
  164. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/snapshot.pyi +0 -0
  165. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/stream_type.py +0 -0
  166. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/token_flow.py +0 -0
  167. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/token_flow.pyi +0 -0
  168. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/volume.py +0 -0
  169. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/volume.pyi +0 -0
  170. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/SOURCES.txt +0 -0
  171. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/dependency_links.txt +0 -0
  172. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/entry_points.txt +0 -0
  173. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/requires.txt +0 -0
  174. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/top_level.txt +0 -0
  175. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/__init__.py +0 -0
  176. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/gen_cli_docs.py +0 -0
  177. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/gen_reference_docs.py +0 -0
  178. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/mdmd/__init__.py +0 -0
  179. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/mdmd/mdmd.py +0 -0
  180. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/mdmd/signatures.py +0 -0
  181. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/__init__.py +0 -0
  182. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2_grpc.py +0 -0
  183. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
  184. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/modal_api_grpc.py +0 -0
  185. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/py.typed +0 -0
  186. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router.proto +0 -0
  187. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_grpc.py +0 -0
  188. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2.py +0 -0
  189. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2.pyi +0 -0
  190. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  191. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  192. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router.proto +0 -0
  193. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_grpc.py +0 -0
  194. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2.py +0 -0
  195. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
  196. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  197. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  198. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_version/__main__.py +0 -0
  199. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/pyproject.toml +0 -0
  200. {modal-1.2.3.dev8 → modal-1.2.3.dev10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.3.dev8
3
+ Version: 1.2.3.dev10
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -90,7 +90,6 @@ from .parallel_map import (
90
90
  from .proxy import _Proxy
91
91
  from .retries import Retries, RetryManager
92
92
  from .schedule import Schedule
93
- from .scheduler_placement import SchedulerPlacement
94
93
  from .secret import _Secret
95
94
  from .volume import _Volume
96
95
 
@@ -589,7 +588,7 @@ class _FunctionSpec:
589
588
  cpu: Optional[Union[float, tuple[float, float]]]
590
589
  memory: Optional[Union[int, tuple[int, int]]]
591
590
  ephemeral_disk: Optional[int]
592
- scheduler_placement: Optional[SchedulerPlacement]
591
+ scheduler_placement: Optional[api_pb2.SchedulerPlacement]
593
592
  proxy: Optional[_Proxy]
594
593
 
595
594
 
@@ -683,7 +682,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
683
682
  batch_max_size: Optional[int] = None,
684
683
  batch_wait_ms: Optional[int] = None,
685
684
  cloud: Optional[str] = None,
686
- scheduler_placement: Optional[SchedulerPlacement] = None,
685
+ region: Optional[Union[str, Sequence[str]]] = None,
686
+ nonpreemptible: bool = False,
687
687
  is_builder_function: bool = False,
688
688
  is_auto_snapshot: bool = False,
689
689
  enable_memory_snapshot: bool = False,
@@ -746,6 +746,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
746
746
  if env:
747
747
  secrets = [*secrets, _Secret.from_dict(env)]
748
748
 
749
+ scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
750
+ if region or nonpreemptible:
751
+ regions = [region] if isinstance(region, str) else (list(region) if region else None)
752
+ scheduler_placement = api_pb2.SchedulerPlacement(regions=regions, nonpreemptible=nonpreemptible)
753
+
749
754
  function_spec = _FunctionSpec(
750
755
  mounts=all_mounts,
751
756
  secrets=secrets,
@@ -1018,7 +1023,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1018
1023
  untrusted=restrict_modal_access,
1019
1024
  max_inputs=max_inputs or 0,
1020
1025
  cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
1021
- scheduler_placement=scheduler_placement.proto if scheduler_placement else None,
1026
+ scheduler_placement=scheduler_placement,
1022
1027
  is_class=info.is_service_class(),
1023
1028
  class_parameter_info=info.class_parameter_info(),
1024
1029
  i6pn_enabled=i6pn_enabled,
@@ -9,7 +9,8 @@ import urllib.parse
9
9
  import uuid
10
10
  from collections.abc import AsyncIterator
11
11
  from dataclasses import dataclass, field
12
- from typing import Any, Optional, TypeVar
12
+ from functools import cache
13
+ from typing import Any, Optional, Sequence, TypeVar
13
14
 
14
15
  import grpclib.client
15
16
  import grpclib.config
@@ -17,11 +18,14 @@ import grpclib.events
17
18
  import grpclib.protocol
18
19
  import grpclib.stream
19
20
  from google.protobuf.message import Message
21
+ from google.protobuf.symbol_database import SymbolDatabase
20
22
  from grpclib import GRPCError, Status
23
+ from grpclib.encoding.base import StatusDetailsCodecBase
21
24
  from grpclib.exceptions import StreamTerminatedError
22
25
  from grpclib.protocol import H2Protocol
23
26
 
24
27
  from modal.exception import AuthError, ConnectionError
28
+ from modal_proto import api_pb2
25
29
  from modal_version import __version__
26
30
 
27
31
  from .._traceback import suppress_tb_frames
@@ -107,6 +111,56 @@ class ConnectionManager:
107
111
  self._channels.clear()
108
112
 
109
113
 
114
+ @cache
115
+ def _sym_db() -> SymbolDatabase:
116
+ from google.protobuf.symbol_database import Default
117
+
118
+ return Default()
119
+
120
+
121
+ class CustomProtoStatusDetailsCodec(StatusDetailsCodecBase):
122
+ """grpclib compatible details codec.
123
+
124
+ The server can encode the details using `google.rpc.Status` using grpclib's default codec and this custom codec
125
+ can decode it into a `api_pb2.RPCStatus`.
126
+ """
127
+
128
+ def encode(
129
+ self,
130
+ status: Status,
131
+ message: Optional[str],
132
+ details: Optional[Sequence[Message]],
133
+ ) -> bytes:
134
+ details_proto = api_pb2.RPCStatus(code=status.value, message=message or "")
135
+ if details is not None:
136
+ for detail in details:
137
+ detail_container = details_proto.details.add()
138
+ detail_container.Pack(detail)
139
+ return details_proto.SerializeToString()
140
+
141
+ def decode(
142
+ self,
143
+ status: Status,
144
+ message: Optional[str],
145
+ data: bytes,
146
+ ) -> Any:
147
+ sym_db = _sym_db()
148
+ details_proto = api_pb2.RPCStatus.FromString(data)
149
+
150
+ details = []
151
+ for detail_container in details_proto.details:
152
+ # If we do not know how to decode an emssage, we'll ignore it.
153
+ with contextlib.suppress(Exception):
154
+ msg_type = sym_db.GetSymbol(detail_container.TypeName())
155
+ detail = msg_type()
156
+ detail_container.Unpack(detail)
157
+ details.append(detail)
158
+ return details
159
+
160
+
161
+ custom_detail_codec = CustomProtoStatusDetailsCodec()
162
+
163
+
110
164
  def create_channel(
111
165
  server_url: str,
112
166
  metadata: dict[str, str] = {},
@@ -125,7 +179,7 @@ def create_channel(
125
179
  )
126
180
 
127
181
  if o.scheme == "unix":
128
- channel = grpclib.client.Channel(path=o.path, config=config) # probably pointless to use a pool ever
182
+ channel = grpclib.client.Channel(path=o.path, config=config, status_details_codec=custom_detail_codec)
129
183
  elif o.scheme in ("http", "https"):
130
184
  target = o.netloc
131
185
  parts = target.split(":")
@@ -133,7 +187,7 @@ def create_channel(
133
187
  ssl = o.scheme.endswith("s")
134
188
  host = parts[0]
135
189
  port = int(parts[1]) if len(parts) == 2 else 443 if ssl else 80
136
- channel = grpclib.client.Channel(host, port, ssl=ssl, config=config)
190
+ channel = grpclib.client.Channel(host, port, ssl=ssl, config=config, status_details_codec=custom_detail_codec)
137
191
  else:
138
192
  raise Exception(f"Unknown scheme: {o.scheme}")
139
193
 
@@ -258,7 +312,6 @@ async def _retry_transient_errors(
258
312
  raise AuthError(exc.message)
259
313
  else:
260
314
  raise exc
261
-
262
315
  if retry.max_retries is not None and n_retries >= retry.max_retries:
263
316
  final_attempt = True
264
317
  elif total_deadline is not None and time.time() + delay + retry.attempt_timeout_floor >= total_deadline:
@@ -719,6 +719,7 @@ class _App:
719
719
  ] = None, # Set this to True if it's a non-generator function returning a [sync/async] generator object
720
720
  cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
721
721
  region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
722
+ nonpreemptible: bool = False, # Whether to run the function on a nonpreemptible instance.
722
723
  enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
723
724
  block_network: bool = False, # Whether to block network access
724
725
  restrict_modal_access: bool = False, # Whether to allow this function access to other Modal resources
@@ -731,9 +732,6 @@ class _App:
731
732
  include_source: Optional[bool] = None,
732
733
  experimental_options: Optional[dict[str, Any]] = None,
733
734
  # Parameters below here are experimental. Use with caution!
734
- _experimental_scheduler_placement: Optional[
735
- SchedulerPlacement
736
- ] = None, # Experimental controls over fine-grained scheduling (alpha).
737
735
  _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
738
736
  _experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
739
737
  _experimental_restrict_output: bool = False, # Don't use pickle for return values
@@ -743,6 +741,8 @@ class _App:
743
741
  container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
744
742
  allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
745
743
  _experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
744
+ _experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
745
+ # using `region` and `nonpreemptible`
746
746
  ) -> _FunctionDecoratorType:
747
747
  """Decorator to register a new Modal Function with this App."""
748
748
  if isinstance(_warn_parentheses_missing, _Image):
@@ -762,6 +762,24 @@ class _App:
762
762
  "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
763
763
  )
764
764
 
765
+ if _experimental_scheduler_placement is not None:
766
+ deprecation_warning(
767
+ (2025, 11, 17),
768
+ "The `_experimental_scheduler_placement` parameter is deprecated."
769
+ " Please use the `region` and `nonpreemptible` parameters instead.",
770
+ )
771
+ if region is not None or nonpreemptible:
772
+ raise InvalidError(
773
+ "Cannot use `_experimental_scheduler_placement` together with "
774
+ "`region` or `nonpreemptible` parameters."
775
+ )
776
+ # Extract regions and lifecycle from scheduler placement
777
+ if _experimental_scheduler_placement.proto.regions:
778
+ region = list(_experimental_scheduler_placement.proto.regions)
779
+ if _experimental_scheduler_placement.proto._lifecycle:
780
+ # Convert lifecycle to nonpreemptible: "on-demand" -> True, "spot" -> False
781
+ nonpreemptible = _experimental_scheduler_placement.proto._lifecycle == "on-demand"
782
+
765
783
  secrets = secrets or []
766
784
  if env:
767
785
  secrets = [*secrets, _Secret.from_dict(env)]
@@ -771,7 +789,7 @@ class _App:
771
789
  def wrapped(
772
790
  f: Union[_PartialFunction, Callable[..., Any], None],
773
791
  ) -> _Function:
774
- nonlocal is_generator, cloud, serialized
792
+ nonlocal is_generator, cloud, serialized, region, nonpreemptible
775
793
 
776
794
  # Check if the decorated object is a class
777
795
  if inspect.isclass(f):
@@ -860,12 +878,6 @@ class _App:
860
878
  if is_generator is None:
861
879
  is_generator = inspect.isgeneratorfunction(raw_f) or inspect.isasyncgenfunction(raw_f)
862
880
 
863
- scheduler_placement: Optional[SchedulerPlacement] = _experimental_scheduler_placement
864
- if region:
865
- if scheduler_placement:
866
- raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
867
- scheduler_placement = SchedulerPlacement(region=region)
868
-
869
881
  function = _Function.from_local(
870
882
  info,
871
883
  app=self,
@@ -892,12 +904,13 @@ class _App:
892
904
  timeout=timeout,
893
905
  startup_timeout=startup_timeout or timeout,
894
906
  cloud=cloud,
907
+ region=region,
908
+ nonpreemptible=nonpreemptible,
895
909
  webhook_config=webhook_config,
896
910
  enable_memory_snapshot=enable_memory_snapshot,
897
911
  block_network=block_network,
898
912
  restrict_modal_access=restrict_modal_access,
899
913
  max_inputs=max_inputs,
900
- scheduler_placement=scheduler_placement,
901
914
  i6pn_enabled=i6pn_enabled,
902
915
  cluster_size=cluster_size, # Experimental: Clustered functions
903
916
  rdma=rdma,
@@ -950,6 +963,7 @@ class _App:
950
963
  startup_timeout: Optional[int] = None, # Maximum startup time in seconds with higher precedence than `timeout`.
951
964
  cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
952
965
  region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
966
+ nonpreemptible: bool = False, # Whether to run the function on a non-preemptible instance.
953
967
  enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
954
968
  block_network: bool = False, # Whether to block network access
955
969
  restrict_modal_access: bool = False, # Whether to allow this class access to other Modal resources
@@ -960,9 +974,6 @@ class _App:
960
974
  include_source: Optional[bool] = None, # When `False`, don't automatically add the App source to the container.
961
975
  experimental_options: Optional[dict[str, Any]] = None,
962
976
  # Parameters below here are experimental. Use with caution!
963
- _experimental_scheduler_placement: Optional[
964
- SchedulerPlacement
965
- ] = None, # Experimental controls over fine-grained scheduling (alpha).
966
977
  _experimental_proxy_ip: Optional[str] = None, # IP address of proxy
967
978
  _experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
968
979
  _experimental_restrict_output: bool = False, # Don't use pickle for return values
@@ -972,6 +983,8 @@ class _App:
972
983
  container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
973
984
  allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
974
985
  _experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
986
+ _experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
987
+ # using `region` and `nonpreemptible`
975
988
  ) -> Callable[[Union[CLS_T, _PartialFunction]], CLS_T]:
976
989
  """
977
990
  Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App.
@@ -979,12 +992,6 @@ class _App:
979
992
  if _warn_parentheses_missing:
980
993
  raise InvalidError("Did you forget parentheses? Suggestion: `@app.cls()`.")
981
994
 
982
- scheduler_placement = _experimental_scheduler_placement
983
- if region:
984
- if scheduler_placement:
985
- raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
986
- scheduler_placement = SchedulerPlacement(region=region)
987
-
988
995
  if allow_concurrent_inputs is not None:
989
996
  deprecation_warning(
990
997
  (2025, 4, 9),
@@ -993,6 +1000,24 @@ class _App:
993
1000
  "\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
994
1001
  )
995
1002
 
1003
+ if _experimental_scheduler_placement is not None:
1004
+ deprecation_warning(
1005
+ (2025, 11, 17),
1006
+ "The `_experimental_scheduler_placement` parameter is deprecated."
1007
+ " Please use the `region` and `nonpreemptible` parameters instead.",
1008
+ )
1009
+ if region is not None or nonpreemptible:
1010
+ raise InvalidError(
1011
+ "Cannot use `_experimental_scheduler_placement` together with "
1012
+ "`region` or `nonpreemptible` parameters."
1013
+ )
1014
+ # Extract regions and lifecycle from scheduler placement
1015
+ if _experimental_scheduler_placement.proto.regions:
1016
+ region = list(_experimental_scheduler_placement.proto.regions)
1017
+ if _experimental_scheduler_placement.proto._lifecycle:
1018
+ # Convert lifecycle to nonpreemptible: "on-demand" -> True, "spot" -> False
1019
+ nonpreemptible = _experimental_scheduler_placement.proto._lifecycle == "on-demand"
1020
+
996
1021
  secrets = secrets or []
997
1022
  if env:
998
1023
  secrets = [*secrets, _Secret.from_dict(env)]
@@ -1086,11 +1111,12 @@ class _App:
1086
1111
  timeout=timeout,
1087
1112
  startup_timeout=startup_timeout or timeout,
1088
1113
  cloud=cloud,
1114
+ region=region,
1115
+ nonpreemptible=nonpreemptible,
1089
1116
  enable_memory_snapshot=enable_memory_snapshot,
1090
1117
  block_network=block_network,
1091
1118
  restrict_modal_access=restrict_modal_access,
1092
1119
  max_inputs=max_inputs,
1093
- scheduler_placement=scheduler_placement,
1094
1120
  i6pn_enabled=i6pn_enabled,
1095
1121
  cluster_size=cluster_size,
1096
1122
  rdma=rdma,
@@ -487,6 +487,7 @@ class _App:
487
487
  is_generator: typing.Optional[bool] = None,
488
488
  cloud: typing.Optional[str] = None,
489
489
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
490
+ nonpreemptible: bool = False,
490
491
  enable_memory_snapshot: bool = False,
491
492
  block_network: bool = False,
492
493
  restrict_modal_access: bool = False,
@@ -494,7 +495,6 @@ class _App:
494
495
  i6pn: typing.Optional[bool] = None,
495
496
  include_source: typing.Optional[bool] = None,
496
497
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
497
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
498
498
  _experimental_proxy_ip: typing.Optional[str] = None,
499
499
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
500
500
  _experimental_restrict_output: bool = False,
@@ -503,6 +503,7 @@ class _App:
503
503
  container_idle_timeout: typing.Optional[int] = None,
504
504
  allow_concurrent_inputs: typing.Optional[int] = None,
505
505
  _experimental_buffer_containers: typing.Optional[int] = None,
506
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
506
507
  ) -> _FunctionDecoratorType:
507
508
  """Decorator to register a new Modal Function with this App."""
508
509
  ...
@@ -540,6 +541,7 @@ class _App:
540
541
  startup_timeout: typing.Optional[int] = None,
541
542
  cloud: typing.Optional[str] = None,
542
543
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
544
+ nonpreemptible: bool = False,
543
545
  enable_memory_snapshot: bool = False,
544
546
  block_network: bool = False,
545
547
  restrict_modal_access: bool = False,
@@ -547,7 +549,6 @@ class _App:
547
549
  i6pn: typing.Optional[bool] = None,
548
550
  include_source: typing.Optional[bool] = None,
549
551
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
550
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
551
552
  _experimental_proxy_ip: typing.Optional[str] = None,
552
553
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
553
554
  _experimental_restrict_output: bool = False,
@@ -556,6 +557,7 @@ class _App:
556
557
  container_idle_timeout: typing.Optional[int] = None,
557
558
  allow_concurrent_inputs: typing.Optional[int] = None,
558
559
  _experimental_buffer_containers: typing.Optional[int] = None,
560
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
559
561
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
560
562
  """Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
561
563
  ...
@@ -1148,6 +1150,7 @@ class App:
1148
1150
  is_generator: typing.Optional[bool] = None,
1149
1151
  cloud: typing.Optional[str] = None,
1150
1152
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
1153
+ nonpreemptible: bool = False,
1151
1154
  enable_memory_snapshot: bool = False,
1152
1155
  block_network: bool = False,
1153
1156
  restrict_modal_access: bool = False,
@@ -1155,7 +1158,6 @@ class App:
1155
1158
  i6pn: typing.Optional[bool] = None,
1156
1159
  include_source: typing.Optional[bool] = None,
1157
1160
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1158
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1159
1161
  _experimental_proxy_ip: typing.Optional[str] = None,
1160
1162
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1161
1163
  _experimental_restrict_output: bool = False,
@@ -1164,6 +1166,7 @@ class App:
1164
1166
  container_idle_timeout: typing.Optional[int] = None,
1165
1167
  allow_concurrent_inputs: typing.Optional[int] = None,
1166
1168
  _experimental_buffer_containers: typing.Optional[int] = None,
1169
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1167
1170
  ) -> _FunctionDecoratorType:
1168
1171
  """Decorator to register a new Modal Function with this App."""
1169
1172
  ...
@@ -1201,6 +1204,7 @@ class App:
1201
1204
  startup_timeout: typing.Optional[int] = None,
1202
1205
  cloud: typing.Optional[str] = None,
1203
1206
  region: typing.Union[str, collections.abc.Sequence[str], None] = None,
1207
+ nonpreemptible: bool = False,
1204
1208
  enable_memory_snapshot: bool = False,
1205
1209
  block_network: bool = False,
1206
1210
  restrict_modal_access: bool = False,
@@ -1208,7 +1212,6 @@ class App:
1208
1212
  i6pn: typing.Optional[bool] = None,
1209
1213
  include_source: typing.Optional[bool] = None,
1210
1214
  experimental_options: typing.Optional[dict[str, typing.Any]] = None,
1211
- _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1212
1215
  _experimental_proxy_ip: typing.Optional[str] = None,
1213
1216
  _experimental_custom_scaling_factor: typing.Optional[float] = None,
1214
1217
  _experimental_restrict_output: bool = False,
@@ -1217,6 +1220,7 @@ class App:
1217
1220
  container_idle_timeout: typing.Optional[int] = None,
1218
1221
  allow_concurrent_inputs: typing.Optional[int] = None,
1219
1222
  _experimental_buffer_containers: typing.Optional[int] = None,
1223
+ _experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
1220
1224
  ) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
1221
1225
  """Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
1222
1226
  ...
@@ -693,7 +693,7 @@ def shell(
693
693
  cpu=function_spec.cpu,
694
694
  memory=function_spec.memory,
695
695
  volumes=function_spec.volumes,
696
- region=function_spec.scheduler_placement.proto.regions if function_spec.scheduler_placement else None,
696
+ region=function_spec.scheduler_placement.regions if function_spec.scheduler_placement else None,
697
697
  pty=pty,
698
698
  proxy=function_spec.proxy,
699
699
  )
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.2.3.dev8",
36
+ version: str = "1.2.3.dev10",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.3.dev8",
167
+ version: str = "1.2.3.dev10",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -37,7 +37,6 @@ from .cloud_bucket_mount import _CloudBucketMount
37
37
  from .exception import ExecutionError, InvalidError, NotFoundError
38
38
  from .gpu import GPU_T
39
39
  from .retries import Retries
40
- from .scheduler_placement import SchedulerPlacement
41
40
  from .secret import _Secret
42
41
  from .volume import _Volume
43
42
 
@@ -746,8 +745,6 @@ More information on class parameterization can be found here: https://modal.com/
746
745
  else:
747
746
  resources = None
748
747
 
749
- scheduler_placement = SchedulerPlacement(region=region).proto if region else None
750
-
751
748
  if allow_concurrent_inputs is not None:
752
749
  deprecation_warning(
753
750
  (2025, 5, 9),
@@ -790,6 +787,11 @@ More information on class parameterization can be found here: https://modal.com/
790
787
  if env:
791
788
  secrets = [*secrets, _Secret.from_dict(env)]
792
789
 
790
+ scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
791
+ if region:
792
+ regions = [region] if isinstance(region, str) else list(region)
793
+ scheduler_placement = api_pb2.SchedulerPlacement(regions=regions)
794
+
793
795
  new_options = _ServiceOptions(
794
796
  secrets=secrets,
795
797
  validated_volumes=validated_volumes_no_cloud_buckets,
@@ -18,7 +18,6 @@ import modal.parallel_map
18
18
  import modal.proxy
19
19
  import modal.retries
20
20
  import modal.schedule
21
- import modal.scheduler_placement
22
21
  import modal.secret
23
22
  import modal.volume
24
23
  import modal_proto.api_pb2
@@ -97,7 +96,8 @@ class Function(
97
96
  batch_max_size: typing.Optional[int] = None,
98
97
  batch_wait_ms: typing.Optional[int] = None,
99
98
  cloud: typing.Optional[str] = None,
100
- scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
99
+ region: typing.Union[str, collections.abc.Sequence[str], None] = None,
100
+ nonpreemptible: bool = False,
101
101
  is_builder_function: bool = False,
102
102
  is_auto_snapshot: bool = False,
103
103
  enable_memory_snapshot: bool = False,
@@ -408,7 +408,7 @@ class Function(
408
408
 
409
409
  _call_generator: ___call_generator_spec[typing_extensions.Self]
410
410
 
411
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
411
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
412
412
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
413
413
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
414
414
  ...
@@ -417,7 +417,7 @@ class Function(
417
417
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
418
418
  ...
419
419
 
420
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
420
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
421
421
 
422
422
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
423
423
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -444,7 +444,7 @@ class Function(
444
444
  """
445
445
  ...
446
446
 
447
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
447
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
448
448
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
449
449
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
450
450
 
@@ -468,7 +468,7 @@ class Function(
468
468
  ...
469
469
 
470
470
  _experimental_spawn: ___experimental_spawn_spec[
471
- modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
471
+ modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
472
472
  ]
473
473
 
474
474
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -477,7 +477,7 @@ class Function(
477
477
 
478
478
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
479
479
 
480
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
480
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
481
481
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
482
482
  """Calls the function with the given arguments, without waiting for the results.
483
483
 
@@ -498,7 +498,7 @@ class Function(
498
498
  """
499
499
  ...
500
500
 
501
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
501
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
502
502
 
503
503
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
504
504
  """Return the inner Python object wrapped by this Modal Function."""
@@ -51,7 +51,6 @@ from .gpu import GPU_T, parse_gpu_config
51
51
  from .mount import _Mount, python_standalone_mount_name
52
52
  from .network_file_system import _NetworkFileSystem
53
53
  from .output import _get_output_manager
54
- from .scheduler_placement import SchedulerPlacement
55
54
  from .secret import _Secret
56
55
  from .volume import _Volume
57
56
 
@@ -2300,8 +2299,6 @@ class _Image(_Object, type_prefix="im"):
2300
2299
  # It may be possible to support lambdas eventually, but for now we don't handle them well, so reject quickly
2301
2300
  raise InvalidError("Image.run_function does not support lambda functions.")
2302
2301
 
2303
- scheduler_placement = SchedulerPlacement(region=region) if region else None
2304
-
2305
2302
  info = FunctionInfo(raw_f)
2306
2303
 
2307
2304
  function = _Function.from_local(
@@ -2313,7 +2310,7 @@ class _Image(_Object, type_prefix="im"):
2313
2310
  volumes=volumes,
2314
2311
  network_file_systems=network_file_systems,
2315
2312
  cloud=cloud,
2316
- scheduler_placement=scheduler_placement,
2313
+ region=region,
2317
2314
  memory=memory,
2318
2315
  timeout=timeout,
2319
2316
  cpu=cpu,
@@ -41,7 +41,6 @@ from .image import _Image
41
41
  from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
42
42
  from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
43
43
  from .proxy import _Proxy
44
- from .scheduler_placement import SchedulerPlacement
45
44
  from .secret import _Secret
46
45
  from .snapshot import _SandboxSnapshot
47
46
  from .stream_type import StreamType
@@ -148,7 +147,6 @@ class _Sandbox(_Object, type_prefix="sb"):
148
147
  unencrypted_ports: Sequence[int] = [],
149
148
  proxy: Optional[_Proxy] = None,
150
149
  experimental_options: Optional[dict[str, bool]] = None,
151
- _experimental_scheduler_placement: Optional[SchedulerPlacement] = None,
152
150
  enable_snapshot: bool = False,
153
151
  verbose: bool = False,
154
152
  ) -> "_Sandbox":
@@ -156,12 +154,6 @@ class _Sandbox(_Object, type_prefix="sb"):
156
154
 
157
155
  validated_network_file_systems = validate_network_file_systems(network_file_systems)
158
156
 
159
- scheduler_placement: Optional[SchedulerPlacement] = _experimental_scheduler_placement
160
- if region:
161
- if scheduler_placement:
162
- raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
163
- scheduler_placement = SchedulerPlacement(region=region)
164
-
165
157
  if isinstance(gpu, list):
166
158
  raise InvalidError(
167
159
  "Sandboxes do not support configuring a list of GPUs. "
@@ -176,6 +168,11 @@ class _Sandbox(_Object, type_prefix="sb"):
176
168
  cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
177
169
  validated_volumes = [(k, v) for k, v in validated_volumes if isinstance(v, _Volume)]
178
170
 
171
+ scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
172
+ if region:
173
+ regions = [region] if isinstance(region, str) else (list(region) if region else None)
174
+ scheduler_placement = api_pb2.SchedulerPlacement(regions=regions)
175
+
179
176
  if pty:
180
177
  pty_info = _Sandbox._default_pty_info()
181
178
 
@@ -252,7 +249,7 @@ class _Sandbox(_Object, type_prefix="sb"):
252
249
  cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
253
250
  volume_mounts=volume_mounts,
254
251
  pty_info=pty_info,
255
- scheduler_placement=scheduler_placement.proto if scheduler_placement else None,
252
+ scheduler_placement=scheduler_placement,
256
253
  worker_id=config.get("worker_id"),
257
254
  open_ports=api_pb2.PortSpecs(ports=open_ports),
258
255
  network_access=network_access,
@@ -320,9 +317,6 @@ class _Sandbox(_Object, type_prefix="sb"):
320
317
  experimental_options: Optional[dict[str, bool]] = None,
321
318
  # Enable memory snapshots.
322
319
  _experimental_enable_snapshot: bool = False,
323
- _experimental_scheduler_placement: Optional[
324
- SchedulerPlacement
325
- ] = None, # Experimental controls over fine-grained scheduling (alpha).
326
320
  client: Optional[_Client] = None,
327
321
  environment_name: Optional[str] = None, # *DEPRECATED* Optionally override the default environment
328
322
  pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
@@ -384,7 +378,6 @@ class _Sandbox(_Object, type_prefix="sb"):
384
378
  proxy=proxy,
385
379
  experimental_options=experimental_options,
386
380
  _experimental_enable_snapshot=_experimental_enable_snapshot,
387
- _experimental_scheduler_placement=_experimental_scheduler_placement,
388
381
  client=client,
389
382
  verbose=verbose,
390
383
  pty_info=pty_info,
@@ -418,7 +411,6 @@ class _Sandbox(_Object, type_prefix="sb"):
418
411
  proxy: Optional[_Proxy] = None,
419
412
  experimental_options: Optional[dict[str, bool]] = None,
420
413
  _experimental_enable_snapshot: bool = False,
421
- _experimental_scheduler_placement: Optional[SchedulerPlacement] = None,
422
414
  client: Optional[_Client] = None,
423
415
  verbose: bool = False,
424
416
  pty_info: Optional[api_pb2.PTYInfo] = None,
@@ -468,7 +460,6 @@ class _Sandbox(_Object, type_prefix="sb"):
468
460
  unencrypted_ports=unencrypted_ports,
469
461
  proxy=proxy,
470
462
  experimental_options=experimental_options,
471
- _experimental_scheduler_placement=_experimental_scheduler_placement,
472
463
  enable_snapshot=_experimental_enable_snapshot,
473
464
  verbose=verbose,
474
465
  )