modal 1.5.6.dev26__tar.gz → 1.5.6.dev27__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 (244) hide show
  1. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/__init__.py +4 -0
  3. modal-1.5.6.dev27/modal/_cluster.py +192 -0
  4. modal-1.5.6.dev27/modal/_cluster.pyi +108 -0
  5. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_container_entrypoint.py +3 -11
  6. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_function_variants.py +71 -19
  7. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_functions.py +75 -75
  8. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_image.py +3 -0
  9. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_object.py +0 -31
  10. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_partial_function.py +77 -2
  11. modal-1.5.6.dev27/modal/_runtime/gpu_memory_snapshot.py +56 -0
  12. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/task_lifecycle_manager.py +16 -28
  13. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/task_lifecycle_manager.pyi +0 -5
  14. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_server.py +189 -2
  15. modal-1.5.6.dev27/modal/_server.pyi +411 -0
  16. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/function_utils.py +1 -1
  17. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/task_command_router_client.py +12 -3
  18. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_workspace.py +20 -0
  19. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/app.py +17 -47
  20. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/app.pyi +0 -56
  21. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/app.py +8 -4
  22. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/function.py +7 -5
  23. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/server.py +3 -3
  24. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/utils.py +2 -0
  25. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/workspace.py +24 -0
  26. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/client.pyi +2 -2
  27. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cloud_bucket_mount.py +16 -9
  28. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cloud_bucket_mount.pyi +32 -0
  29. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cls.py +12 -32
  30. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cls.pyi +0 -20
  31. modal-1.5.6.dev27/modal/cluster.py +7 -0
  32. modal-1.5.6.dev27/modal/cluster.pyi +108 -0
  33. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/config.py +1 -2
  34. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/__init__.py +9 -6
  35. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/_flash_container_entry.py +2 -2
  36. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/functions.pyi +18 -92
  37. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/io_streams.py +164 -73
  38. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/io_streams.pyi +65 -40
  39. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/object.pyi +0 -15
  40. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/partial_function.py +2 -0
  41. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/partial_function.pyi +62 -2
  42. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox.py +2 -0
  43. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/server.pyi +77 -0
  44. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/types.py +239 -105
  45. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/workspace.pyi +37 -0
  46. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/PKG-INFO +1 -1
  47. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/SOURCES.txt +5 -2
  48. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/mdmd.py +24 -3
  49. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2.pyi +1 -1
  50. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_version/__init__.py +1 -1
  51. modal-1.5.6.dev26/modal/_clustered_functions.py +0 -97
  52. modal-1.5.6.dev26/modal/_clustered_functions.pyi +0 -38
  53. modal-1.5.6.dev26/modal/_runtime/gpu_memory_snapshot.py +0 -281
  54. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/LICENSE +0 -0
  55. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/README.md +0 -0
  56. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/__main__.py +0 -0
  57. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_billing.py +0 -0
  58. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_environments.py +0 -0
  59. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_grpc_client.py +0 -0
  60. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_ipython.py +0 -0
  61. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_load_context.py +0 -0
  62. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_location.py +0 -0
  63. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_logs.py +0 -0
  64. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_logs_manager.py +0 -0
  65. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_logs_manager.pyi +0 -0
  66. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_outbound_policy.py +0 -0
  67. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_outbound_policy.pyi +0 -0
  68. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/__init__.py +0 -0
  69. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/manager.py +0 -0
  70. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/pty.py +0 -0
  71. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/rich.py +0 -0
  72. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/status.py +0 -0
  73. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_resolver.py +0 -0
  74. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_resources.py +0 -0
  75. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/__init__.py +0 -0
  76. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/asgi.py +0 -0
  77. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/container_io_manager.py +0 -0
  78. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/container_io_manager.pyi +0 -0
  79. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/execution_context.py +0 -0
  80. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/execution_context.pyi +0 -0
  81. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/telemetry.py +0 -0
  82. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/user_code_event_loop.py +0 -0
  83. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/user_code_imports.py +0 -0
  84. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_serialization.py +0 -0
  85. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_supports_logs.py +0 -0
  86. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_traceback.py +0 -0
  87. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_tunnel.py +0 -0
  88. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_tunnel.pyi +0 -0
  89. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_type_manager.py +0 -0
  90. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/__init__.py +0 -0
  91. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/app_utils.py +0 -0
  92. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/async_utils.py +0 -0
  93. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/auth_token_manager.py +0 -0
  94. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/blob_utils.py +0 -0
  95. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/browser_utils.py +0 -0
  96. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/bytes_io_segment_payload.py +0 -0
  97. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/curl_utils.py +0 -0
  98. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/deprecation.py +0 -0
  99. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/display_utils.py +0 -0
  100. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/docker_utils.py +0 -0
  101. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/git_utils.py +0 -0
  102. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/grpc_testing.py +0 -0
  103. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/grpc_utils.py +0 -0
  104. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/hash_utils.py +0 -0
  105. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/http_utils.py +0 -0
  106. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/idle_countdown.py +0 -0
  107. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/jwt_utils.py +0 -0
  108. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/logger.py +0 -0
  109. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/mount_utils.py +0 -0
  110. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/name_utils.py +0 -0
  111. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/package_utils.py +0 -0
  112. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/pattern_utils.py +0 -0
  113. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/proxy_support.py +0 -0
  114. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/rand_pb_testing.py +0 -0
  115. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/sandbox_fs_utils.py +0 -0
  116. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/shell_utils.py +0 -0
  117. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/time_utils.py +0 -0
  118. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/__init__.py +0 -0
  119. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  120. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/cloudpickle.py +0 -0
  121. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/tblib.py +0 -0
  122. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/version.py +0 -0
  123. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_watcher.py +0 -0
  124. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/billing.py +0 -0
  125. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/billing.pyi +0 -0
  126. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2023.12.312.txt +0 -0
  127. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2023.12.txt +0 -0
  128. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2024.04.txt +0 -0
  129. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2024.10.txt +0 -0
  130. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2025.06.txt +0 -0
  131. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/PREVIEW.txt +0 -0
  132. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/README.md +0 -0
  133. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/base-images.json +0 -0
  134. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/call_graph.py +0 -0
  135. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/__init__.py +0 -0
  136. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_download.py +0 -0
  137. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_help.py +0 -0
  138. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_logs.py +0 -0
  139. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_stats.py +0 -0
  140. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_traceback.py +0 -0
  141. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/billing.py +0 -0
  142. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/changelog.py +0 -0
  143. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/config.py +0 -0
  144. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/container.py +0 -0
  145. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/curl.py +0 -0
  146. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/dashboard.py +0 -0
  147. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/dict.py +0 -0
  148. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/endpoint.py +0 -0
  149. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/entry_point.py +0 -0
  150. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/environment.py +0 -0
  151. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/image.py +0 -0
  152. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/import_refs.py +0 -0
  153. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/logo.py +0 -0
  154. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/network_file_system.py +0 -0
  155. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/profile.py +0 -0
  156. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/queues.py +0 -0
  157. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/run.py +0 -0
  158. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/secret.py +0 -0
  159. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/selector.py +0 -0
  160. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/shell.py +0 -0
  161. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/skills.py +0 -0
  162. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/token.py +0 -0
  163. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/volume.py +0 -0
  164. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/client.py +0 -0
  165. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/container_process.py +0 -0
  166. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/container_process.pyi +0 -0
  167. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/dict.py +0 -0
  168. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/dict.pyi +0 -0
  169. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/environments.py +0 -0
  170. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/environments.pyi +0 -0
  171. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/exception.py +0 -0
  172. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash.py +0 -0
  173. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash.pyi +0 -0
  174. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash_server.py +0 -0
  175. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash_server.pyi +0 -0
  176. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/ipython.py +0 -0
  177. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/file_pattern_matcher.py +0 -0
  178. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/functions.py +0 -0
  179. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/image.py +0 -0
  180. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/image.pyi +0 -0
  181. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/mount.py +0 -0
  182. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/mount.pyi +0 -0
  183. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/network_file_system.py +0 -0
  184. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/network_file_system.pyi +0 -0
  185. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/object.py +0 -0
  186. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/output.py +0 -0
  187. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/parallel_map.py +0 -0
  188. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/parallel_map.pyi +0 -0
  189. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/proxy.py +0 -0
  190. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/proxy.pyi +0 -0
  191. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/py.typed +0 -0
  192. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/queue.py +0 -0
  193. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/queue.pyi +0 -0
  194. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/retries.py +0 -0
  195. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/runner.py +0 -0
  196. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/runner.pyi +0 -0
  197. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/running_app.py +0 -0
  198. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox.pyi +0 -0
  199. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox_fs.py +0 -0
  200. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox_fs.pyi +0 -0
  201. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/schedule.py +0 -0
  202. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/scheduler_placement.py +0 -0
  203. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/secret.py +0 -0
  204. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/secret.pyi +0 -0
  205. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/server.py +0 -0
  206. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/serving.py +0 -0
  207. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/serving.pyi +0 -0
  208. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/skills/modal/SKILL.md +0 -0
  209. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/snapshot.py +0 -0
  210. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/snapshot.pyi +0 -0
  211. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/stream_type.py +0 -0
  212. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/token_flow.py +0 -0
  213. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/token_flow.pyi +0 -0
  214. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/volume.py +0 -0
  215. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/volume.pyi +0 -0
  216. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/workspace.py +0 -0
  217. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/dependency_links.txt +0 -0
  218. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/entry_points.txt +0 -0
  219. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/requires.txt +0 -0
  220. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/top_level.txt +0 -0
  221. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/__init__.py +0 -0
  222. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_cli_docs.py +0 -0
  223. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_cli_docs_main.py +0 -0
  224. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_reference_docs.py +0 -0
  225. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_reference_docs_main.py +0 -0
  226. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/__init__.py +0 -0
  227. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/signatures.py +0 -0
  228. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/types.py +0 -0
  229. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/reference_intro.md +0 -0
  230. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/__init__.py +0 -0
  231. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_grpc.py +0 -0
  232. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2.py +0 -0
  233. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2_grpc.py +0 -0
  234. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2_grpc.pyi +0 -0
  235. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/modal_api_grpc.py +0 -0
  236. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/py.typed +0 -0
  237. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_grpc.py +0 -0
  238. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2.py +0 -0
  239. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2.pyi +0 -0
  240. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  241. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  242. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_version/__main__.py +0 -0
  243. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/pyproject.toml +0 -0
  244. {modal-1.5.6.dev26 → modal-1.5.6.dev27}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev26
3
+ Version: 1.5.6.dev27
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -16,6 +16,7 @@ try:
16
16
  from .client import Client
17
17
  from .cloud_bucket_mount import CloudBucketMount
18
18
  from .cls import Cls, parameter
19
+ from .cluster import Cluster
19
20
  from .dict import Dict
20
21
  from .environments import Environment
21
22
  from .exception import Error
@@ -33,6 +34,7 @@ try:
33
34
  exit,
34
35
  fastapi_endpoint,
35
36
  method,
37
+ sessioned,
36
38
  web_endpoint,
37
39
  web_server,
38
40
  wsgi_app,
@@ -62,6 +64,7 @@ __all__ = [
62
64
  "__version__",
63
65
  "App",
64
66
  "Client",
67
+ "Cluster",
65
68
  "Cls",
66
69
  "Cron",
67
70
  "Dict",
@@ -103,6 +106,7 @@ __all__ = [
103
106
  "interact",
104
107
  "method",
105
108
  "parameter",
109
+ "sessioned",
106
110
  "types",
107
111
  "web_endpoint",
108
112
  "web_server",
@@ -0,0 +1,192 @@
1
+ # Copyright Modal Labs 2026
2
+ import os
3
+ import socket
4
+ from dataclasses import dataclass
5
+ from typing import Literal, cast
6
+
7
+ from google.protobuf.message import Message
8
+
9
+ from modal_proto import api_pb2
10
+
11
+ from ._load_context import LoadContext
12
+ from ._object import _Object, live_method
13
+ from ._resolver import Resolver
14
+ from ._utils.async_utils import synchronize_api
15
+ from .client import _Client
16
+ from .exception import InvalidError
17
+
18
+
19
+ @dataclass
20
+ class ClusterContext:
21
+ """Cluster metadata and the executing container's rank, initialized at startup."""
22
+
23
+ rank: int
24
+ cluster_id: str
25
+ container_ips: list[str]
26
+ container_ipv4_ips: list[str]
27
+ fabric_ids: list[str]
28
+
29
+
30
+ current_cluster_context: ClusterContext | None = None
31
+
32
+
33
+ def get_current_cluster_context() -> ClusterContext:
34
+ """Return the executing container's initialized cluster context without an RPC."""
35
+ if current_cluster_context is None:
36
+ raise InvalidError(
37
+ "Cluster context is not initialized. This operation requires execution "
38
+ "inside a clustered Function or Server."
39
+ )
40
+ return current_cluster_context
41
+
42
+
43
+ class _Cluster(_Object, type_prefix="cu"):
44
+ """A group of containers scheduled together for a clustered Function or Server.
45
+
46
+ Use `Cluster.from_context()` inside a cluster, or `Cluster.from_id()` to
47
+ inspect a cluster remotely. Containers are ordered by cluster rank.
48
+ """
49
+
50
+ _metadata: api_pb2.ClusterStats | None = None
51
+
52
+ def _hydrate_metadata(self, metadata: Message | None):
53
+ if metadata is not None:
54
+ assert isinstance(metadata, api_pb2.ClusterStats)
55
+ self._metadata = metadata
56
+
57
+ def _get_metadata(self) -> api_pb2.ClusterStats:
58
+ assert self._metadata is not None
59
+ return self._metadata
60
+
61
+ @staticmethod
62
+ def from_context() -> "_Cluster":
63
+ """Reference a Cluster from within one of its containers.
64
+
65
+ Raises `InvalidError` outside an initialized clustered execution.
66
+ """
67
+ context = get_current_cluster_context()
68
+ if not context.cluster_id:
69
+ raise InvalidError("The executing container does not have an initialized cluster ID.")
70
+ return _Cluster.from_id(context.cluster_id)
71
+
72
+ @staticmethod
73
+ def from_id(cluster_id: str, *, client: _Client | None = None) -> "_Cluster":
74
+ """Reference a Cluster by its ID.
75
+
76
+ Args:
77
+ cluster_id: ID of the cluster.
78
+ client: Modal client to use; defaults to `Client.from_env()` when omitted.
79
+
80
+ Examples:
81
+ ```python notest
82
+ cluster = modal.Cluster.from_id("cu-123")
83
+ ```
84
+ """
85
+
86
+ async def _load(self: _Cluster, resolver: Resolver, context: LoadContext, existing_object_id: str | None):
87
+ response = await context.client._stub.ClusterGet(api_pb2.ClusterGetRequest(cluster_id=cluster_id))
88
+ self._hydrate(cluster_id, context.client, response.cluster)
89
+
90
+ obj = _Cluster._from_loader(
91
+ _load,
92
+ f"modal.Cluster.from_id({cluster_id!r})",
93
+ hydrate_lazily=True,
94
+ load_context_overrides=LoadContext(client=client),
95
+ )
96
+ obj._object_id = cluster_id
97
+ return obj
98
+
99
+ @property
100
+ def object_id(self) -> str:
101
+ """The cluster's unique `cu-` object ID."""
102
+ return super().object_id
103
+
104
+ @live_method
105
+ async def container_ids(self) -> list[str]:
106
+ """Return container IDs ordered by cluster rank."""
107
+ return list(self._get_metadata().task_ids)
108
+
109
+ async def container_ips(self, family: Literal["ipv4", "ipv6"] = "ipv6") -> list[str]:
110
+ """Return container IP addresses ordered by cluster rank.
111
+
112
+ Returns IPv6 addresses by default; pass `family="ipv4"` for IPv4.
113
+ These addresses are for intra-cluster communication.
114
+
115
+ Must be called from a container in this cluster; otherwise raises `InvalidError`.
116
+ """
117
+ if family not in ("ipv4", "ipv6"):
118
+ raise InvalidError("family must be 'ipv4' or 'ipv6'.")
119
+ context = get_current_cluster_context()
120
+ if context.cluster_id != self.object_id:
121
+ raise InvalidError("container_ips() must be called from a container in the referenced cluster.")
122
+ return list(context.container_ipv4_ips if family == "ipv4" else context.container_ips)
123
+
124
+ async def container_rank(self, container_id: str | None = None) -> int:
125
+ """Return a container's rank within this cluster.
126
+
127
+ With no argument, return the executing container's rank without a network
128
+ request. Raises `InvalidError` if it is not a member of this cluster.
129
+
130
+ With an explicit container ID, look up its rank in the cluster's membership.
131
+ Raises `InvalidError` for nonmembers.
132
+ """
133
+ if container_id is None:
134
+ context = get_current_cluster_context()
135
+ if context.cluster_id != self.object_id:
136
+ raise InvalidError("The executing container is not a member of the referenced cluster.")
137
+ return context.rank
138
+
139
+ container_ids = await self.container_ids()
140
+ try:
141
+ return container_ids.index(container_id)
142
+ except ValueError:
143
+ raise InvalidError(f"Container {container_id!r} is not a member of cluster {self.object_id!r}.") from None
144
+
145
+
146
+ async def _initialize_clustered_function(client: _Client, task_id: str):
147
+ global current_cluster_context
148
+
149
+ def get_i6pn() -> str:
150
+ """Returns the ipv6 address assigned to this container."""
151
+ # An AF_INET6 result always carries a (host, port, flowinfo, scope_id) sockaddr,
152
+ # but getaddrinfo is typed for every address family, some of which lead with an int
153
+ return cast(str, socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0])
154
+
155
+ hostname = socket.gethostname()
156
+ container_ip = get_i6pn()
157
+
158
+ # nccl's default host ID is $(hostname)$(cat /proc/sys/kernel/random/boot_id).
159
+ # on runc, if two i6pn-linked containers get scheduled on the same worker,
160
+ # their boot ID and hostname will both be identical, causing nccl to break.
161
+ # As a workaround, we can explicitly specify a unique host ID here.
162
+ # See MOD-4067.
163
+ os.environ["NCCL_HOSTID"] = f"{hostname}{container_ip}"
164
+
165
+ # We found these settings to work well in most cases. You may be able to achieve
166
+ # better performance by tuning these settings.
167
+ if os.environ["MODAL_CLOUD_PROVIDER"] in ("CLOUD_PROVIDER_GCP", "CLOUD_PROVIDER_OCI"):
168
+ os.environ["NCCL_SOCKET_NTHREADS"] = "4"
169
+ os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
170
+ elif os.environ["MODAL_CLOUD_PROVIDER"] == "CLOUD_PROVIDER_AWS":
171
+ os.environ["NCCL_SOCKET_NTHREADS"] = "2"
172
+ os.environ["NCCL_NSOCKS_PERTHREAD"] = "8"
173
+ else:
174
+ os.environ["NCCL_SOCKET_NTHREADS"] = "1"
175
+ os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
176
+
177
+ resp = await client._stub.TaskClusterHello(
178
+ api_pb2.TaskClusterHelloRequest(
179
+ task_id=task_id,
180
+ container_ip=container_ip,
181
+ ),
182
+ )
183
+ current_cluster_context = ClusterContext(
184
+ rank=resp.cluster_rank,
185
+ cluster_id=resp.cluster_id,
186
+ container_ips=list(resp.container_ips),
187
+ container_ipv4_ips=list(resp.container_ipv4_ips),
188
+ fabric_ids=list(resp.fabric_ids),
189
+ )
190
+
191
+
192
+ initialize_clustered_function = synchronize_api(_initialize_clustered_function)
@@ -0,0 +1,108 @@
1
+ import google.protobuf.message
2
+ import modal._object
3
+ import modal.client
4
+ import modal_proto.api_pb2
5
+ import types
6
+ import typing
7
+ import typing_extensions
8
+
9
+ class ClusterContext:
10
+ """Cluster metadata and the executing container's rank, initialized at startup."""
11
+
12
+ rank: int
13
+ cluster_id: str
14
+ container_ips: list[str]
15
+ container_ipv4_ips: list[str]
16
+ fabric_ids: list[str]
17
+
18
+ def __init__(
19
+ self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str]
20
+ ) -> None:
21
+ """Initialize self. See help(type(self)) for accurate signature."""
22
+ ...
23
+
24
+ def __repr__(self):
25
+ """Return repr(self)."""
26
+ ...
27
+
28
+ def __eq__(self, other):
29
+ """Return self==value."""
30
+ ...
31
+
32
+ def get_current_cluster_context() -> ClusterContext:
33
+ """Return the executing container's initialized cluster context without an RPC."""
34
+ ...
35
+
36
+ class _Cluster(modal._object._Object):
37
+ """A group of containers scheduled together for a clustered Function or Server.
38
+
39
+ Use `Cluster.from_context()` inside a cluster, or `Cluster.from_id()` to
40
+ inspect a cluster remotely. Containers are ordered by cluster rank.
41
+ """
42
+
43
+ _metadata: typing.Optional[modal_proto.api_pb2.ClusterStats]
44
+
45
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
46
+ def _get_metadata(self) -> modal_proto.api_pb2.ClusterStats: ...
47
+ @staticmethod
48
+ def from_context() -> _Cluster:
49
+ """Reference a Cluster from within one of its containers.
50
+
51
+ Raises `InvalidError` outside an initialized clustered execution.
52
+ """
53
+ ...
54
+
55
+ @staticmethod
56
+ def from_id(cluster_id: str, *, client: typing.Optional[modal.client._Client] = None) -> _Cluster:
57
+ """Reference a Cluster by its ID.
58
+
59
+ Args:
60
+ cluster_id: ID of the cluster.
61
+ client: Modal client to use; defaults to `Client.from_env()` when omitted.
62
+
63
+ Examples:
64
+ ```python notest
65
+ cluster = modal.Cluster.from_id("cu-123")
66
+ ```
67
+ """
68
+ ...
69
+
70
+ @property
71
+ def object_id(self) -> str:
72
+ """The cluster's unique `cu-` object ID."""
73
+ ...
74
+
75
+ async def container_ids(self) -> list[str]:
76
+ """Return container IDs ordered by cluster rank."""
77
+ ...
78
+
79
+ async def container_ips(self, family: typing.Literal["ipv4", "ipv6"] = "ipv6") -> list[str]:
80
+ """Return container IP addresses ordered by cluster rank.
81
+
82
+ Returns IPv6 addresses by default; pass `family="ipv4"` for IPv4.
83
+ These addresses are for intra-cluster communication.
84
+
85
+ Must be called from a container in this cluster; otherwise raises `InvalidError`.
86
+ """
87
+ ...
88
+
89
+ async def container_rank(self, container_id: typing.Optional[str] = None) -> int:
90
+ """Return a container's rank within this cluster.
91
+
92
+ With no argument, return the executing container's rank without a network
93
+ request. Raises `InvalidError` if it is not a member of this cluster.
94
+
95
+ With an explicit container ID, look up its rank in the cluster's membership.
96
+ Raises `InvalidError` for nonmembers.
97
+ """
98
+ ...
99
+
100
+ async def _initialize_clustered_function(client: modal.client._Client, task_id: str): ...
101
+
102
+ class __initialize_clustered_function_spec(typing_extensions.Protocol):
103
+ def __call__(self, /, client: modal.client.Client, task_id: str): ...
104
+ async def aio(self, /, client: modal.client.Client, task_id: str): ...
105
+
106
+ initialize_clustered_function: __initialize_clustered_function_spec
107
+
108
+ current_cluster_context: ClusterContext | None
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING, Any, cast
24
24
 
25
25
  from google.protobuf.message import Message
26
26
 
27
- from modal._clustered_functions import initialize_clustered_function
27
+ from modal._cluster import initialize_clustered_function
28
28
  from modal._runtime.user_code_event_loop import UserCodeEventLoop
29
29
  from modal._serialization import deserialize, deserialize_params
30
30
  from modal._utils.async_utils import TaskContext, aclosing, synchronizer
@@ -407,11 +407,7 @@ def run_server(container_args: api_pb2.ContainerArguments, client: Client):
407
407
  service: Service = hydrate_function(container_args, task_lifecycle_manager, function_def, _client)
408
408
  # Initialize clustered functions.
409
409
  if function_def._experimental_group_size > 0:
410
- initialize_clustered_function(
411
- client,
412
- container_args.task_id,
413
- function_def._experimental_group_size,
414
- )
410
+ initialize_clustered_function(client, container_args.task_id)
415
411
 
416
412
  with UserCodeEventLoop() as event_loop:
417
413
  with service.lifecycle_context(
@@ -452,11 +448,7 @@ def run_function(container_args: api_pb2.ContainerArguments, client: Client):
452
448
 
453
449
  # Initialize clustered functions.
454
450
  if function_def._experimental_group_size > 0:
455
- initialize_clustered_function(
456
- client,
457
- container_args.task_id,
458
- function_def._experimental_group_size,
459
- )
451
+ initialize_clustered_function(client, container_args.task_id)
460
452
 
461
453
  with service.function_execution_context(
462
454
  event_loop=event_loop,
@@ -1,12 +1,12 @@
1
1
  # Copyright Modal Labs 2026
2
+ import copy
2
3
  import dataclasses
3
4
  from collections.abc import Collection, Sequence, Sized
4
5
  from pathlib import PurePosixPath
5
- from typing import TYPE_CHECKING, Any
6
+ from typing import TYPE_CHECKING, Any, TypedDict
6
7
 
7
8
  import google.protobuf.message
8
9
 
9
- from modal.types import CloudBucketMountInfo, FunctionInfo, VolumeMountInfo
10
10
  from modal_proto import api_pb2
11
11
 
12
12
  from ._resources import convert_fn_config_to_resources_config
@@ -23,6 +23,7 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
23
23
  from .exception import InvalidError
24
24
  from .retries import Retries
25
25
  from .secret import _Secret
26
+ from .types import CloudBucketMountInfo, FunctionInfo, VolumeMountInfo
26
27
  from .volume import _Volume, _volume_to_mount_proto
27
28
 
28
29
  if TYPE_CHECKING:
@@ -441,7 +442,7 @@ def _make_function_variant(
441
442
  if options is not None:
442
443
  function_variant._function_info = _get_function_info_with_options(base_function._function_info, options)
443
444
  else:
444
- function_variant._function_info = base_function._function_info._get_copy()
445
+ function_variant._function_info = copy.deepcopy(base_function._function_info)
445
446
 
446
447
  def _deps():
447
448
  if options:
@@ -464,44 +465,67 @@ def _make_function_variant(
464
465
  if options is not None:
465
466
  fun._function_info = _get_function_info_with_options(base_function._function_info, options)
466
467
  else:
467
- fun._function_info = base_function._function_info._get_copy()
468
+ fun._function_info = copy.deepcopy(base_function._function_info)
468
469
 
469
470
  return fun
470
471
 
471
472
 
473
+ class _FunctionInfoArgsT(TypedDict, total=False):
474
+ cpu: float | tuple[float, float]
475
+ memory_mib: int | tuple[int, int]
476
+ gpus: list[tuple[str, int]]
477
+ ephemeral_disk_mib: int
478
+ timeout: int
479
+ max_retries: int
480
+ regions: list[str]
481
+ cloud: str
482
+ volumes: dict[str, VolumeMountInfo]
483
+ cloud_bucket_mounts: dict[str, CloudBucketMountInfo]
484
+ secrets: list[str]
485
+ routing_region: str
486
+ batching_info: FunctionInfo.BatchingInfo
487
+ concurrency_info: FunctionInfo.ConcurrencyInfo
488
+
489
+
472
490
  def _get_function_info_with_options(info: FunctionInfo, options: _FunctionOptions):
473
- new_info = info._get_copy()
491
+ args: _FunctionInfoArgsT = {}
474
492
 
475
493
  if options.resources is not None:
476
494
  res = options.resources
477
495
  if res.milli_cpu_max > 0:
478
- new_info.cpu = (res.milli_cpu / 1000, res.milli_cpu_max / 1000)
496
+ args["cpu"] = (res.milli_cpu / 1000, res.milli_cpu_max / 1000)
479
497
  elif res.milli_cpu > 0:
480
- new_info.cpu = res.milli_cpu / 1000
498
+ args["cpu"] = res.milli_cpu / 1000
481
499
 
482
500
  if res.memory_mb_max > 0:
483
- new_info.memory_mib = (res.memory_mb, res.memory_mb_max)
501
+ args["memory_mib"] = (res.memory_mb, res.memory_mb_max)
484
502
  elif res.memory_mb > 0:
485
- new_info.memory_mib = res.memory_mb
503
+ args["memory_mib"] = res.memory_mb
486
504
 
487
505
  if res.HasField("gpu_config") and res.gpu_config.count > 0:
488
- new_info.gpus = [(res.gpu_config.count, res.gpu_config.gpu_type)]
506
+ args["gpus"] = [(res.gpu_config.gpu_type, res.gpu_config.count)]
489
507
 
490
508
  if res.ephemeral_disk_mb > 0:
491
- new_info.ephemeral_disk_mib = res.ephemeral_disk_mb
509
+ args["ephemeral_disk_mib"] = res.ephemeral_disk_mb
510
+
511
+ if options.timeout_secs is not None:
512
+ args["timeout"] = options.timeout_secs
513
+
514
+ if options.retry_policy is not None:
515
+ args["max_retries"] = options.retry_policy.retries
492
516
 
493
517
  if options.scheduler_placement is not None:
494
518
  sp = options.scheduler_placement
495
519
  # Note: not putting `nonpreemptible` here as that is not currently possible to set via
496
520
  # `.with_options(...)`
497
521
  if len(sp.regions) > 0:
498
- new_info.regions = list(sp.regions)
522
+ args["regions"] = list(sp.regions)
499
523
 
500
524
  if options.cloud is not None:
501
- new_info.cloud = options.cloud
525
+ args["cloud"] = options.cloud
502
526
 
503
527
  if len(options.validated_volumes) > 0:
504
- new_info.volumes = {
528
+ args["volumes"] = {
505
529
  mount_path: VolumeMountInfo(vol._name, vol._object_id, False, None)
506
530
  if vol._mount_options is None
507
531
  else VolumeMountInfo(
@@ -514,16 +538,44 @@ def _get_function_info_with_options(info: FunctionInfo, options: _FunctionOption
514
538
  }
515
539
 
516
540
  if len(options.cloud_bucket_mounts) > 0:
517
- new_info.cloud_bucket_mounts = {}
541
+ args["cloud_bucket_mounts"] = {}
518
542
  protos, _ = cloud_bucket_mounts_to_proto(options.cloud_bucket_mounts, include_secrets=False)
519
543
 
520
544
  for proto in protos:
521
- new_info.cloud_bucket_mounts[proto.mount_path] = CloudBucketMountInfo._from_proto(proto)
545
+ args["cloud_bucket_mounts"][proto.mount_path] = CloudBucketMountInfo._from_proto(proto)
522
546
 
523
547
  if len(options.secrets) > 0:
524
- new_info.secrets = [repr(s) for s in options.secrets]
548
+ args["secrets"] = [repr(s) for s in options.secrets]
525
549
 
526
550
  if options.routing_region:
527
- new_info.routing_region = options.routing_region
551
+ args["routing_region"] = options.routing_region
552
+
553
+ if options.batch_max_size is not None and options.batch_wait_ms is not None:
554
+ args["batching_info"] = FunctionInfo.BatchingInfo(
555
+ max_batch_size=options.batch_max_size,
556
+ wait_ms=options.batch_wait_ms,
557
+ )
558
+
559
+ if options.max_concurrent_inputs is not None or options.target_concurrent_inputs is not None:
560
+ max_inputs = (
561
+ options.max_concurrent_inputs
562
+ if options.max_concurrent_inputs is not None
563
+ else info.concurrency_info.max_inputs
564
+ if info.concurrency_info is not None
565
+ else None
566
+ )
567
+
568
+ target_inputs = (
569
+ options.target_concurrent_inputs
570
+ if options.target_concurrent_inputs is not None
571
+ else info.concurrency_info.target_inputs
572
+ if info.concurrency_info is not None
573
+ else None
574
+ )
575
+
576
+ args["concurrency_info"] = FunctionInfo.ConcurrencyInfo(
577
+ max_inputs=max_inputs,
578
+ target_inputs=target_inputs,
579
+ )
528
580
 
529
- return new_info
581
+ return dataclasses.replace(copy.deepcopy(info), **args)