modal 1.5.3.dev3__tar.gz → 1.5.3.dev4__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 (231) hide show
  1. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/PKG-INFO +1 -1
  2. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_container_entrypoint.py +2 -4
  3. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/container_io_manager.py +6 -30
  4. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/container_io_manager.pyi +5 -16
  5. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/client.py +13 -7
  6. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/client.pyi +15 -13
  7. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/config.py +28 -0
  8. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/functions.pyi +6 -6
  9. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/sandbox.py +33 -4
  10. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/sandbox.pyi +75 -12
  11. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal.egg-info/PKG-INFO +1 -1
  12. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/api_pb2.py +1143 -1143
  13. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/api_pb2.pyi +27 -19
  14. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/task_command_router_grpc.py +16 -0
  15. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2.py +33 -13
  16. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2.pyi +31 -0
  17. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2_grpc.py +37 -0
  18. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2_grpc.pyi +20 -0
  19. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_version/__init__.py +1 -1
  20. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/LICENSE +0 -0
  21. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/README.md +0 -0
  22. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/__init__.py +0 -0
  23. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/__main__.py +0 -0
  24. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_billing.py +0 -0
  25. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_clustered_functions.py +0 -0
  26. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_clustered_functions.pyi +0 -0
  27. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_environments.py +0 -0
  28. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_function_variants.py +0 -0
  29. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_functions.py +0 -0
  30. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_grpc_client.py +0 -0
  31. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_image.py +0 -0
  32. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_ipython.py +0 -0
  33. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_load_context.py +0 -0
  34. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_location.py +0 -0
  35. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_logs.py +0 -0
  36. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_object.py +0 -0
  37. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_output/__init__.py +0 -0
  38. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_output/manager.py +0 -0
  39. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_output/pty.py +0 -0
  40. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_output/rich.py +0 -0
  41. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_output/status.py +0 -0
  42. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_partial_function.py +0 -0
  43. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_resolver.py +0 -0
  44. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_resources.py +0 -0
  45. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/__init__.py +0 -0
  46. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/asgi.py +0 -0
  47. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/execution_context.py +0 -0
  48. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/execution_context.pyi +0 -0
  49. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  50. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/task_lifecycle_manager.py +0 -0
  51. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  52. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/telemetry.py +0 -0
  53. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/user_code_event_loop.py +0 -0
  54. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_runtime/user_code_imports.py +0 -0
  55. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_serialization.py +0 -0
  56. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_server.py +0 -0
  57. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_traceback.py +0 -0
  58. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_tunnel.py +0 -0
  59. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_tunnel.pyi +0 -0
  60. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_type_manager.py +0 -0
  61. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/__init__.py +0 -0
  62. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/app_utils.py +0 -0
  63. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/async_utils.py +0 -0
  64. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/auth_token_manager.py +0 -0
  65. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/blob_utils.py +0 -0
  66. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/browser_utils.py +0 -0
  67. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
  68. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/curl_utils.py +0 -0
  69. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/deprecation.py +0 -0
  70. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/docker_utils.py +0 -0
  71. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/function_utils.py +0 -0
  72. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/git_utils.py +0 -0
  73. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/grpc_testing.py +0 -0
  74. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/grpc_utils.py +0 -0
  75. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/hash_utils.py +0 -0
  76. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/http_utils.py +0 -0
  77. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/jwt_utils.py +0 -0
  78. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/logger.py +0 -0
  79. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/mount_utils.py +0 -0
  80. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/name_utils.py +0 -0
  81. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/package_utils.py +0 -0
  82. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/pattern_utils.py +0 -0
  83. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/proxy_support.py +0 -0
  84. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/rand_pb_testing.py +0 -0
  85. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/sandbox_fs_utils.py +0 -0
  86. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/shell_utils.py +0 -0
  87. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/task_command_router_client.py +0 -0
  88. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_utils/time_utils.py +0 -0
  89. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_vendor/__init__.py +0 -0
  90. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  91. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_vendor/cloudpickle.py +0 -0
  92. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_vendor/tblib.py +0 -0
  93. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_vendor/version.py +0 -0
  94. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_watcher.py +0 -0
  95. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/_workspace.py +0 -0
  96. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/app.py +0 -0
  97. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/app.pyi +0 -0
  98. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/billing.py +0 -0
  99. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/billing.pyi +0 -0
  100. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/2023.12.312.txt +0 -0
  101. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/2023.12.txt +0 -0
  102. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/2024.04.txt +0 -0
  103. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/2024.10.txt +0 -0
  104. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/2025.06.txt +0 -0
  105. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/PREVIEW.txt +0 -0
  106. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/README.md +0 -0
  107. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/builder/base-images.json +0 -0
  108. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/call_graph.py +0 -0
  109. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/__init__.py +0 -0
  110. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/_download.py +0 -0
  111. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/_help.py +0 -0
  112. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/_traceback.py +0 -0
  113. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/app.py +0 -0
  114. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/billing.py +0 -0
  115. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/bootstrap.py +0 -0
  116. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/changelog.py +0 -0
  117. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/cluster.py +0 -0
  118. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/config.py +0 -0
  119. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/container.py +0 -0
  120. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/curl.py +0 -0
  121. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/dashboard.py +0 -0
  122. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/dict.py +0 -0
  123. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/endpoint.py +0 -0
  124. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/entry_point.py +0 -0
  125. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/environment.py +0 -0
  126. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/image.py +0 -0
  127. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/import_refs.py +0 -0
  128. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/launch.py +0 -0
  129. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/logo.py +0 -0
  130. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/network_file_system.py +0 -0
  131. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/profile.py +0 -0
  132. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/programs/__init__.py +0 -0
  133. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/programs/run_jupyter.py +0 -0
  134. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/programs/vscode.py +0 -0
  135. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/queues.py +0 -0
  136. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/run.py +0 -0
  137. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/secret.py +0 -0
  138. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/selector.py +0 -0
  139. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/shell.py +0 -0
  140. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/skills.py +0 -0
  141. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/token.py +0 -0
  142. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/utils.py +0 -0
  143. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/volume.py +0 -0
  144. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cli/workspace.py +0 -0
  145. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cloud_bucket_mount.py +0 -0
  146. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cloud_bucket_mount.pyi +0 -0
  147. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cls.py +0 -0
  148. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/cls.pyi +0 -0
  149. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/container_process.py +0 -0
  150. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/container_process.pyi +0 -0
  151. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/dict.py +0 -0
  152. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/dict.pyi +0 -0
  153. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/environments.py +0 -0
  154. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/environments.pyi +0 -0
  155. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/exception.py +0 -0
  156. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/experimental/__init__.py +0 -0
  157. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/experimental/flash.py +0 -0
  158. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/experimental/flash.pyi +0 -0
  159. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/experimental/ipython.py +0 -0
  160. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/file_io.py +0 -0
  161. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/file_io.pyi +0 -0
  162. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/file_pattern_matcher.py +0 -0
  163. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/functions.py +0 -0
  164. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/image.py +0 -0
  165. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/image.pyi +0 -0
  166. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/io_streams.py +0 -0
  167. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/io_streams.pyi +0 -0
  168. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/mount.py +0 -0
  169. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/mount.pyi +0 -0
  170. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/network_file_system.py +0 -0
  171. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/network_file_system.pyi +0 -0
  172. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/object.py +0 -0
  173. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/object.pyi +0 -0
  174. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/output.py +0 -0
  175. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/parallel_map.py +0 -0
  176. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/parallel_map.pyi +0 -0
  177. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/partial_function.py +0 -0
  178. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/partial_function.pyi +0 -0
  179. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/proxy.py +0 -0
  180. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/proxy.pyi +0 -0
  181. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/py.typed +0 -0
  182. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/queue.py +0 -0
  183. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/queue.pyi +0 -0
  184. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/retries.py +0 -0
  185. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/runner.py +0 -0
  186. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/runner.pyi +0 -0
  187. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/running_app.py +0 -0
  188. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/sandbox_fs.py +0 -0
  189. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/sandbox_fs.pyi +0 -0
  190. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/schedule.py +0 -0
  191. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/scheduler_placement.py +0 -0
  192. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/secret.py +0 -0
  193. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/secret.pyi +0 -0
  194. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/server.py +0 -0
  195. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/server.pyi +0 -0
  196. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/serving.py +0 -0
  197. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/serving.pyi +0 -0
  198. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/skills/modal/SKILL.md +0 -0
  199. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/snapshot.py +0 -0
  200. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/snapshot.pyi +0 -0
  201. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/stream_type.py +0 -0
  202. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/token_flow.py +0 -0
  203. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/token_flow.pyi +0 -0
  204. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/types.py +0 -0
  205. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/volume.py +0 -0
  206. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/volume.pyi +0 -0
  207. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/workspace.py +0 -0
  208. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal/workspace.pyi +0 -0
  209. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal.egg-info/SOURCES.txt +0 -0
  210. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal.egg-info/dependency_links.txt +0 -0
  211. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal.egg-info/entry_points.txt +0 -0
  212. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal.egg-info/requires.txt +0 -0
  213. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal.egg-info/top_level.txt +0 -0
  214. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/__init__.py +0 -0
  215. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/gen_cli_docs.py +0 -0
  216. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/gen_cli_docs_main.py +0 -0
  217. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/gen_reference_docs.py +0 -0
  218. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/gen_reference_docs_main.py +0 -0
  219. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/mdmd/__init__.py +0 -0
  220. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/mdmd/mdmd.py +0 -0
  221. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/mdmd/signatures.py +0 -0
  222. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_docs/mdmd/types.py +0 -0
  223. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/__init__.py +0 -0
  224. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/api_grpc.py +0 -0
  225. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/api_pb2_grpc.py +0 -0
  226. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/api_pb2_grpc.pyi +0 -0
  227. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/modal_api_grpc.py +0 -0
  228. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_proto/py.typed +0 -0
  229. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/modal_version/__main__.py +0 -0
  230. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/pyproject.toml +0 -0
  231. {modal-1.5.3.dev3 → modal-1.5.3.dev4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.3.dev3
3
+ Version: 1.5.3.dev4
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -145,7 +145,7 @@ def call_function(
145
145
  item_count += 1
146
146
 
147
147
  await container_io_manager._send_outputs.aio(
148
- started_at, io_context.output_items_generator_done(started_at, item_count)
148
+ io_context.output_items_generator_done(started_at, item_count)
149
149
  )
150
150
  else:
151
151
  value = await io_context.call_function_async()
@@ -182,9 +182,7 @@ def call_function(
182
182
  container_io_manager._queue_put(generator_queue, value)
183
183
  item_count += 1
184
184
 
185
- container_io_manager._send_outputs(
186
- started_at, io_context.output_items_generator_done(started_at, item_count)
187
- )
185
+ container_io_manager._send_outputs(io_context.output_items_generator_done(started_at, item_count))
188
186
  else:
189
187
  values = io_context.call_function_sync()
190
188
  container_io_manager.push_outputs(io_context, started_at, values)
@@ -1,7 +1,6 @@
1
1
  # Copyright Modal Labs 2024
2
2
  import asyncio
3
3
  import inspect
4
- import math
5
4
  import sys
6
5
  import time
7
6
  import traceback
@@ -48,8 +47,6 @@ DYNAMIC_CONCURRENCY_INTERVAL_SECS = 3
48
47
  DYNAMIC_CONCURRENCY_TIMEOUT_SECS = 10
49
48
  MAX_OUTPUT_BATCH_SIZE: int = 49
50
49
 
51
- RTT_S: float = 0.5 # conservative estimate of RTT in seconds.
52
-
53
50
 
54
51
  class Sentinel:
55
52
  """Used to get type-stubs to work with this object."""
@@ -474,8 +471,6 @@ class _ContainerIOManager:
474
471
  _task_lifecycle_manager: _TaskLifecycleManager
475
472
  input_plane_server_url: str | None
476
473
 
477
- calls_completed: int
478
- total_user_time: float
479
474
  current_input_id: str | None
480
475
  current_inputs: dict[str, IOContext] # input_id -> IOContext
481
476
  current_input_started_at: float | None
@@ -510,8 +505,6 @@ class _ContainerIOManager:
510
505
 
511
506
  self.input_plane_server_url = container_args.input_plane_server_url
512
507
 
513
- self.calls_completed = 0
514
- self.total_user_time = 0.0
515
508
  self.current_input_id = None
516
509
  self.current_inputs = {}
517
510
  self.current_input_started_at = None
@@ -791,18 +784,6 @@ class _ContainerIOManager:
791
784
  """Put a value onto a queue, using the synchronicity event loop."""
792
785
  await queue.put(value)
793
786
 
794
- def get_average_call_time(self) -> float:
795
- if self.calls_completed == 0:
796
- return 0
797
-
798
- return self.total_user_time / self.calls_completed
799
-
800
- def get_max_inputs_to_fetch(self):
801
- if self.calls_completed == 0:
802
- return 1
803
-
804
- return math.ceil(RTT_S / max(self.get_average_call_time(), 1e-6))
805
-
806
787
  @synchronizer.no_io_translation
807
788
  async def _generate_inputs(
808
789
  self,
@@ -814,8 +795,6 @@ class _ContainerIOManager:
814
795
  while self._fetching_inputs:
815
796
  await self._input_slots.acquire()
816
797
 
817
- request.average_call_time = self.get_average_call_time()
818
- request.max_values = self.get_max_inputs_to_fetch() # Deprecated; remove.
819
798
  request.input_concurrency = self.get_input_concurrency()
820
799
  request.batch_max_size, request.batch_linger_ms = batch_max_size, batch_wait_ms
821
800
 
@@ -888,7 +867,7 @@ class _ContainerIOManager:
888
867
  # collect all active input slots, meaning all inputs have wrapped up.
889
868
  await self._input_slots.close()
890
869
 
891
- async def _send_outputs(self, started_at: float, outputs: list[api_pb2.FunctionPutOutputsItem]) -> None:
870
+ async def _send_outputs(self, outputs: list[api_pb2.FunctionPutOutputsItem]) -> None:
892
871
  """Send pre-built output items with retry and chunking."""
893
872
  # There are multiple outputs for a single IOContext in the case of @modal.batched.
894
873
  # Limit the batch size to 20 to stay within message size limits and buffer size limits.
@@ -902,7 +881,7 @@ class _ContainerIOManager:
902
881
  ),
903
882
  )
904
883
  input_ids = [output.input_id for output in outputs]
905
- self.exit_context(started_at, input_ids)
884
+ self.exit_context(input_ids)
906
885
 
907
886
  @asynccontextmanager
908
887
  async def handle_input_exception(
@@ -922,7 +901,7 @@ class _ContainerIOManager:
922
901
  raise
923
902
  except (InputCancellation, asyncio.CancelledError):
924
903
  outputs = await io_context.output_items_cancellation(started_at)
925
- await self._send_outputs(started_at, outputs)
904
+ await self._send_outputs(outputs)
926
905
  logger.warning(f"Successfully canceled input {io_context.input_ids}")
927
906
  return
928
907
  except BaseException as exc:
@@ -933,12 +912,9 @@ class _ContainerIOManager:
933
912
  # print exception so it's logged
934
913
  print_exception(*sys.exc_info())
935
914
  outputs = await io_context.output_items_exception(started_at, self.task_id, exc)
936
- await self._send_outputs(started_at, outputs)
937
-
938
- def exit_context(self, started_at, input_ids: list[str]):
939
- self.total_user_time += time.time() - started_at
940
- self.calls_completed += 1
915
+ await self._send_outputs(outputs)
941
916
 
917
+ def exit_context(self, input_ids: list[str]):
942
918
  for input_id in input_ids:
943
919
  self.current_inputs.pop(input_id)
944
920
 
@@ -954,7 +930,7 @@ class _ContainerIOManager:
954
930
  ) -> None:
955
931
  # The standard output encoding+sending method for successful function outputs
956
932
  outputs = await io_context.output_items(started_at, output_data)
957
- await self._send_outputs(started_at, outputs)
933
+ await self._send_outputs(outputs)
958
934
 
959
935
  @asynccontextmanager
960
936
  async def snapshot_context_manager(self) -> AsyncGenerator[None, None]:
@@ -100,8 +100,6 @@ class _ContainerIOManager:
100
100
  app_id: str
101
101
  _task_lifecycle_manager: modal._runtime.task_lifecycle_manager._TaskLifecycleManager
102
102
  input_plane_server_url: typing.Optional[str]
103
- calls_completed: int
104
- total_user_time: float
105
103
  current_input_id: typing.Optional[str]
106
104
  current_inputs: dict[str, IOContext]
107
105
  current_input_started_at: typing.Optional[float]
@@ -185,8 +183,6 @@ class _ContainerIOManager:
185
183
  """Put a value onto a queue, using the synchronicity event loop."""
186
184
  ...
187
185
 
188
- def get_average_call_time(self) -> float: ...
189
- def get_max_inputs_to_fetch(self): ...
190
186
  def _generate_inputs(
191
187
  self, batch_max_size: int, batch_wait_ms: int
192
188
  ) -> collections.abc.AsyncIterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
@@ -196,7 +192,7 @@ class _ContainerIOManager:
196
192
  batch_max_size: int = 0,
197
193
  batch_wait_ms: int = 0,
198
194
  ) -> collections.abc.AsyncIterator[IOContext]: ...
199
- async def _send_outputs(self, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
195
+ async def _send_outputs(self, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
200
196
  """Send pre-built output items with retry and chunking."""
201
197
  ...
202
198
 
@@ -204,7 +200,7 @@ class _ContainerIOManager:
204
200
  """Handle an exception while processing a function input."""
205
201
  ...
206
202
 
207
- def exit_context(self, started_at, input_ids: list[str]): ...
203
+ def exit_context(self, input_ids: list[str]): ...
208
204
  async def push_outputs(self, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
209
205
  def snapshot_context_manager(self) -> typing.AsyncContextManager[None]: ...
210
206
  async def interact(self, from_breakpoint: bool = False): ...
@@ -246,8 +242,6 @@ class ContainerIOManager:
246
242
  app_id: str
247
243
  _task_lifecycle_manager: modal._runtime.task_lifecycle_manager.TaskLifecycleManager
248
244
  input_plane_server_url: typing.Optional[str]
249
- calls_completed: int
250
- total_user_time: float
251
245
  current_input_id: typing.Optional[str]
252
246
  current_inputs: dict[str, IOContext]
253
247
  current_input_started_at: typing.Optional[float]
@@ -413,9 +407,6 @@ class ContainerIOManager:
413
407
 
414
408
  _queue_put: ___queue_put_spec
415
409
 
416
- def get_average_call_time(self) -> float: ...
417
- def get_max_inputs_to_fetch(self): ...
418
-
419
410
  class ___generate_inputs_spec(typing_extensions.Protocol):
420
411
  def __call__(
421
412
  self, /, batch_max_size: int, batch_wait_ms: int
@@ -445,11 +436,11 @@ class ContainerIOManager:
445
436
  run_inputs_outputs: __run_inputs_outputs_spec
446
437
 
447
438
  class ___send_outputs_spec(typing_extensions.Protocol):
448
- def __call__(self, /, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
439
+ def __call__(self, /, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
449
440
  """Send pre-built output items with retry and chunking."""
450
441
  ...
451
442
 
452
- async def aio(self, /, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
443
+ async def aio(self, /, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
453
444
  """Send pre-built output items with retry and chunking."""
454
445
  ...
455
446
 
@@ -468,7 +459,7 @@ class ContainerIOManager:
468
459
 
469
460
  handle_input_exception: __handle_input_exception_spec
470
461
 
471
- def exit_context(self, started_at, input_ids: list[str]): ...
462
+ def exit_context(self, input_ids: list[str]): ...
472
463
 
473
464
  class __push_outputs_spec(typing_extensions.Protocol):
474
465
  def __call__(self, /, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
@@ -517,5 +508,3 @@ class ContainerIOManager:
517
508
  def stop_fetching_inputs(cls): ...
518
509
 
519
510
  MAX_OUTPUT_BATCH_SIZE: int
520
-
521
- RTT_S: float
@@ -6,7 +6,7 @@ import sys
6
6
  import urllib.parse
7
7
  import warnings
8
8
  from collections.abc import AsyncGenerator, Collection, Mapping
9
- from typing import Any, ClassVar, TypeVar
9
+ from typing import Any, ClassVar, Coroutine, TypeVar
10
10
 
11
11
  import grpclib.client
12
12
  from google.protobuf import empty_pb2
@@ -41,7 +41,7 @@ def _get_metadata(client_type: int, credentials: tuple[str, str] | None, version
41
41
  # sys.version_info is structured unlike sys.version or platform.python_version()
42
42
  python_version = "%d.%d.%d" % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro)
43
43
 
44
- metadata = {
44
+ metadata: dict[str, str] = {
45
45
  "x-modal-client-version": version,
46
46
  "x-modal-client-type": str(client_type),
47
47
  "x-modal-python-version": python_version,
@@ -58,9 +58,15 @@ def _get_metadata(client_type: int, credentials: tuple[str, str] | None, version
58
58
  )
59
59
  if agent_env := _agent_environment():
60
60
  metadata["x-modal-agent-harness"] = urllib.parse.quote(agent_env)
61
+
62
+ override_headers = config.get("override_headers")
63
+ if override_headers:
64
+ metadata.update(override_headers)
65
+
61
66
  return metadata
62
67
 
63
68
 
69
+ T = TypeVar("T")
64
70
  ReturnType = TypeVar("ReturnType")
65
71
  _Value = str | bytes
66
72
  _MetadataLike = Mapping[str, _Value] | Collection[tuple[str, _Value]]
@@ -308,7 +314,7 @@ class _Client:
308
314
  ("x-modal-auth-token", token),
309
315
  ]
310
316
 
311
- async def _call_safely(self, coro, readable_method: str):
317
+ async def _call_safely(self, coro: Coroutine[Any, Any, T], readable_method: str) -> T:
312
318
  """Runs coroutine wrapped in a task that's part of the client's task context
313
319
 
314
320
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -364,11 +370,11 @@ class _Client:
364
370
  async def _call_unary(
365
371
  self,
366
372
  grpclib_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
367
- request: Any,
373
+ request: RequestType,
368
374
  *,
369
375
  timeout: float | None = None,
370
376
  metadata: _MetadataLike | None = None,
371
- ) -> Any:
377
+ ) -> ResponseType:
372
378
  coro = grpclib_method(request, timeout=timeout, metadata=metadata)
373
379
  return await self._call_safely(coro, grpclib_method.name)
374
380
 
@@ -376,10 +382,10 @@ class _Client:
376
382
  async def _call_stream(
377
383
  self,
378
384
  grpclib_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
379
- request: Any,
385
+ request: RequestType,
380
386
  *,
381
387
  metadata: _MetadataLike | None,
382
- ) -> AsyncGenerator[Any, None]:
388
+ ) -> AsyncGenerator[ResponseType, None]:
383
389
  stream_context = grpclib_method.open(metadata=metadata)
384
390
  stream = await self._call_safely(stream_context.__aenter__(), f"{grpclib_method.name}.open")
385
391
  try:
@@ -13,6 +13,8 @@ import typing_extensions
13
13
 
14
14
  def _get_metadata(client_type: int, credentials: typing.Optional[tuple[str, str]], version: str) -> dict[str, str]: ...
15
15
 
16
+ T = typing.TypeVar("T")
17
+
16
18
  ReturnType = typing.TypeVar("ReturnType")
17
19
 
18
20
  RequestType = typing.TypeVar("RequestType", bound="google.protobuf.message.Message")
@@ -35,7 +37,7 @@ class _Client:
35
37
  server_url: str,
36
38
  client_type: int,
37
39
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.5.3.dev3",
40
+ version: str = "1.5.3.dev4",
39
41
  ):
40
42
  """mdmd:hidden
41
43
  The Modal client object is not intended to be instantiated directly by users.
@@ -154,7 +156,7 @@ class _Client:
154
156
  """
155
157
  ...
156
158
 
157
- async def _call_safely(self, coro, readable_method: str):
159
+ async def _call_safely(self, coro: typing.Coroutine[typing.Any, typing.Any, T], readable_method: str) -> T:
158
160
  """Runs coroutine wrapped in a task that's part of the client's task context
159
161
 
160
162
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -168,7 +170,7 @@ class _Client:
168
170
  async def _call_unary(
169
171
  self,
170
172
  grpclib_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
171
- request: typing.Any,
173
+ request: RequestType,
172
174
  *,
173
175
  timeout: typing.Optional[float] = None,
174
176
  metadata: typing.Union[
@@ -176,18 +178,18 @@ class _Client:
176
178
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
177
179
  None,
178
180
  ] = None,
179
- ) -> typing.Any: ...
181
+ ) -> ResponseType: ...
180
182
  def _call_stream(
181
183
  self,
182
184
  grpclib_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
183
- request: typing.Any,
185
+ request: RequestType,
184
186
  *,
185
187
  metadata: typing.Union[
186
188
  collections.abc.Mapping[str, typing.Union[str, bytes]],
187
189
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
188
190
  None,
189
191
  ],
190
- ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
192
+ ) -> collections.abc.AsyncGenerator[ResponseType, None]: ...
191
193
 
192
194
  class Client:
193
195
  _client_from_env: typing.ClassVar[typing.Optional[Client]]
@@ -205,7 +207,7 @@ class Client:
205
207
  server_url: str,
206
208
  client_type: int,
207
209
  credentials: typing.Optional[tuple[str, str]],
208
- version: str = "1.5.3.dev3",
210
+ version: str = "1.5.3.dev4",
209
211
  ):
210
212
  """mdmd:hidden
211
213
  The Modal client object is not intended to be instantiated directly by users.
@@ -429,7 +431,7 @@ class Client:
429
431
  get_input_plane_metadata: __get_input_plane_metadata_spec
430
432
 
431
433
  class ___call_safely_spec(typing_extensions.Protocol):
432
- def __call__(self, /, coro, readable_method: str):
434
+ def __call__(self, /, coro: T, readable_method: str) -> T:
433
435
  """Runs coroutine wrapped in a task that's part of the client's task context
434
436
 
435
437
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -438,7 +440,7 @@ class Client:
438
440
  """
439
441
  ...
440
442
 
441
- async def aio(self, /, coro, readable_method: str):
443
+ async def aio(self, /, coro: typing.Coroutine[typing.Any, typing.Any, T], readable_method: str) -> T:
442
444
  """Runs coroutine wrapped in a task that's part of the client's task context
443
445
 
444
446
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -464,7 +466,7 @@ class Client:
464
466
  async def _call_unary(
465
467
  self,
466
468
  grpclib_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
467
- request: typing.Any,
469
+ request: RequestType,
468
470
  *,
469
471
  timeout: typing.Optional[float] = None,
470
472
  metadata: typing.Union[
@@ -472,18 +474,18 @@ class Client:
472
474
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
473
475
  None,
474
476
  ] = None,
475
- ) -> typing.Any: ...
477
+ ) -> ResponseType: ...
476
478
  def _call_stream(
477
479
  self,
478
480
  grpclib_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
479
- request: typing.Any,
481
+ request: RequestType,
480
482
  *,
481
483
  metadata: typing.Union[
482
484
  collections.abc.Mapping[str, typing.Union[str, bytes]],
483
485
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
484
486
  None,
485
487
  ],
486
- ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
488
+ ) -> collections.abc.AsyncGenerator[ResponseType, None]: ...
487
489
 
488
490
  HEARTBEAT_INTERVAL: float
489
491
 
@@ -264,6 +264,33 @@ def _enforce_suffix_rules(x: str) -> str:
264
264
  return x
265
265
 
266
266
 
267
+ def _transform_headers(s: str) -> dict[str, str]:
268
+ error_msg = (
269
+ "Must be a comma-separated list of `<header>:<value>` pairs (whitespace is ignored). "
270
+ "<header> and <value> must contain at least one non-whitespace character, and may "
271
+ "not contain commas."
272
+ )
273
+
274
+ if s == "":
275
+ return {}
276
+
277
+ pairs = s.split(",")
278
+
279
+ headers: dict[str, str] = {}
280
+ for pair in pairs:
281
+ res = pair.split(":", maxsplit=1)
282
+ if len(res) != 2:
283
+ raise ValueError(error_msg)
284
+
285
+ header, value = res[0].strip(), res[1].strip()
286
+ if len(header) == 0 or len(value) == 0:
287
+ raise ValueError(error_msg)
288
+
289
+ headers[header] = value
290
+
291
+ return headers
292
+
293
+
267
294
  class _Setting(typing.NamedTuple):
268
295
  default: typing.Any = None
269
296
  transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
@@ -306,6 +333,7 @@ _SETTINGS = {
306
333
  "max_throttle_wait": _Setting(None, transform=lambda x: int(x) if x else None),
307
334
  "async_warnings": _Setting(True, transform=_to_boolean), # Activate synchronicity usage warnings
308
335
  "disable_api_proxy": _Setting(False, transform=_to_boolean),
336
+ "override_headers": _Setting(None, transform=_transform_headers),
309
337
  }
310
338
 
311
339
 
@@ -493,7 +493,7 @@ class Function(
493
493
 
494
494
  _call_generator: ___call_generator_spec
495
495
 
496
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
496
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
497
497
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
498
498
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
499
499
 
@@ -518,7 +518,7 @@ class Function(
518
518
  """
519
519
  ...
520
520
 
521
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
521
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
522
522
 
523
523
  class __remote_gen_spec(typing_extensions.Protocol):
524
524
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -568,7 +568,7 @@ class Function(
568
568
  """
569
569
  ...
570
570
 
571
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
571
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
572
572
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
573
573
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
574
574
 
@@ -601,7 +601,7 @@ class Function(
601
601
  """
602
602
  ...
603
603
 
604
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
604
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
605
605
 
606
606
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
607
607
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -609,7 +609,7 @@ class Function(
609
609
 
610
610
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
611
611
 
612
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
612
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
613
613
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
614
614
  """Calls the function with the given arguments, without waiting for the results.
615
615
 
@@ -642,7 +642,7 @@ class Function(
642
642
  """
643
643
  ...
644
644
 
645
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
645
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
646
646
 
647
647
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
648
648
  """Return the inner Python object wrapped by this Modal Function.
@@ -842,6 +842,7 @@ class _Sandbox(_Object, type_prefix="sb"):
842
842
  experimental_options: dict[str, Any] | None = None,
843
843
  include_oidc_identity_token: bool = False,
844
844
  verbose: bool = False,
845
+ custom_domain: str | None = None,
845
846
  client: _Client | None = None,
846
847
  ) -> "_Sandbox":
847
848
  """Create a sandbox using the V2 backend.
@@ -849,15 +850,17 @@ class _Sandbox(_Object, type_prefix="sb"):
849
850
  Supported features include exec, encrypted tunnels, wait/poll/terminate,
850
851
  CPU and memory configuration, region placement, private IPv6 networking
851
852
  (i6pn), volumes, cloud bucket mounts (with static credentials via
852
- `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
853
- filesystem snapshots.
853
+ `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies,
854
+ filesystem snapshots, and custom domains (`custom_domain=...` allows
855
+ connections to the sandbox via a subdomain of that parent domain rather
856
+ than a default Modal domain; requires prior setup by Modal).
854
857
 
855
858
  `cpu` and `memory` accept either a scalar request or a `(request, limit)`
856
859
  tuple that additionally sets a hard limit (fractional CPU cores; memory
857
860
  in MiB), matching `Sandbox.create()`.
858
861
 
859
- Features like memory snapshots, network file systems, GPUs, and custom
860
- domains are not supported.
862
+ Features like memory snapshots, network file systems, and GPUs are not
863
+ supported.
861
864
 
862
865
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
863
866
  workspace can address each other directly at their `i6pn.modal.local`
@@ -996,6 +999,7 @@ class _Sandbox(_Object, type_prefix="sb"):
996
999
  experimental_options_v2=(
997
1000
  {k: str(v) for k, v in experimental_options.items()} if experimental_options else None
998
1001
  ),
1002
+ custom_domain=custom_domain,
999
1003
  )
1000
1004
 
1001
1005
  tag_protos = [api_pb2.SandboxTag(tag_name=k, tag_value=v) for k, v in tags.items()] if tags else []
@@ -1317,6 +1321,31 @@ class _Sandbox(_Object, type_prefix="sb"):
1317
1321
  )
1318
1322
  await stub.SandboxTagsSet(req)
1319
1323
 
1324
+ async def _experimental_set_name(self, name: str) -> None:
1325
+ """Assign a name to a running V2 Sandbox that was created without one.
1326
+
1327
+ This is only supported for V2 sandboxes, ie sandboxes created via
1328
+ `modal.Sandbox._experimental_create`. A name may only be set once and
1329
+ only on a Sandbox that has never had one; afterwards the Sandbox can
1330
+ be looked up with `Sandbox._experimental_from_name(app_name, name)`.
1331
+
1332
+ Args:
1333
+ name: Name to assign to the Sandbox. Must be unique within the App.
1334
+
1335
+ Raises:
1336
+ AlreadyExistsError: If another running Sandbox in the App already holds the name.
1337
+ ConflictError: If the Sandbox already has a name or is no longer running.
1338
+ """
1339
+ self._ensure_attached()
1340
+ if not self._is_v2:
1341
+ raise InvalidError("Sandbox._experimental_set_name() is only supported for V2 sandboxes")
1342
+ check_object_name(name, "Sandbox")
1343
+
1344
+ req = api_pb2.SandboxSetNameRequest(sandbox_id=self.object_id, name=name)
1345
+ assert self._client._auth_token_manager
1346
+ auth_token = await self._client._auth_token_manager.get_token()
1347
+ await self._client.stub.SandboxSetName(req, metadata=[("x-modal-auth-token", auth_token)])
1348
+
1320
1349
  async def _experimental_set_outbound_network_policy(
1321
1350
  self,
1322
1351
  *,