modal 1.4.3.dev25__tar.gz → 1.4.3.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 (214) hide show
  1. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/PKG-INFO +1 -1
  2. modal-1.4.3.dev27/modal/_function_variants.py +257 -0
  3. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_functions.py +150 -151
  4. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_output/status.py +1 -1
  5. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_partial_function.py +14 -16
  6. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_resources.py +1 -1
  7. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/container_io_manager.py +1 -3
  8. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/container_io_manager.pyi +3 -9
  9. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/user_code_imports.py +2 -2
  10. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_serialization.py +1 -1
  11. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/function_utils.py +23 -2
  12. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/app.py +6 -6
  13. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/app.pyi +10 -10
  14. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/cluster.py +13 -1
  15. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/container.py +13 -1
  16. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/import_refs.py +1 -1
  17. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/run.py +1 -1
  18. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/shell.py +4 -1
  19. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/client.pyi +2 -2
  20. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cls.py +117 -171
  21. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cls.pyi +44 -77
  22. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/config.py +1 -1
  23. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/experimental/flash.py +6 -6
  24. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/experimental/flash.pyi +2 -2
  25. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/functions.pyi +88 -15
  26. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/partial_function.pyi +14 -16
  27. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/sandbox.py +22 -12
  28. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/sandbox.pyi +11 -8
  29. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal.egg-info/PKG-INFO +1 -1
  30. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal.egg-info/SOURCES.txt +1 -0
  31. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/api_pb2.py +380 -380
  32. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/api_pb2.pyi +16 -4
  33. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_version/__init__.py +1 -1
  34. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/LICENSE +0 -0
  35. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/README.md +0 -0
  36. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/__init__.py +0 -0
  37. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/__main__.py +0 -0
  38. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_billing.py +0 -0
  39. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_clustered_functions.py +0 -0
  40. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_clustered_functions.pyi +0 -0
  41. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_container_entrypoint.py +0 -0
  42. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_environments.py +0 -0
  43. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_grpc_client.py +0 -0
  44. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_ipython.py +0 -0
  45. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_load_context.py +0 -0
  46. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_location.py +0 -0
  47. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_logs.py +0 -0
  48. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_object.py +0 -0
  49. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_output/__init__.py +0 -0
  50. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_output/manager.py +0 -0
  51. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_output/pty.py +0 -0
  52. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_output/rich.py +0 -0
  53. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_resolver.py +0 -0
  54. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/__init__.py +0 -0
  55. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/asgi.py +0 -0
  56. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/execution_context.py +0 -0
  57. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/execution_context.pyi +0 -0
  58. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  59. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/telemetry.py +0 -0
  60. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_runtime/user_code_event_loop.py +0 -0
  61. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_server.py +0 -0
  62. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_traceback.py +0 -0
  63. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_tunnel.py +0 -0
  64. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_tunnel.pyi +0 -0
  65. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_type_manager.py +0 -0
  66. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/__init__.py +0 -0
  67. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/app_utils.py +0 -0
  68. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/async_utils.py +0 -0
  69. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/auth_token_manager.py +0 -0
  70. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/blob_utils.py +0 -0
  71. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/browser_utils.py +0 -0
  72. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/bytes_io_segment_payload.py +0 -0
  73. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/deprecation.py +0 -0
  74. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/docker_utils.py +0 -0
  75. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/git_utils.py +0 -0
  76. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/grpc_testing.py +0 -0
  77. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/grpc_utils.py +0 -0
  78. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/hash_utils.py +0 -0
  79. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/http_utils.py +0 -0
  80. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/jwt_utils.py +0 -0
  81. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/logger.py +0 -0
  82. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/mount_utils.py +0 -0
  83. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/name_utils.py +0 -0
  84. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/package_utils.py +0 -0
  85. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/pattern_utils.py +0 -0
  86. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/rand_pb_testing.py +0 -0
  87. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/sandbox_fs_utils.py +0 -0
  88. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/shell_utils.py +0 -0
  89. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/task_command_router_client.py +0 -0
  90. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_utils/time_utils.py +0 -0
  91. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_vendor/__init__.py +0 -0
  92. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  93. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_vendor/cloudpickle.py +0 -0
  94. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_vendor/tblib.py +0 -0
  95. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_vendor/version.py +0 -0
  96. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/_watcher.py +0 -0
  97. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/billing.py +0 -0
  98. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/2023.12.312.txt +0 -0
  99. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/2023.12.txt +0 -0
  100. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/2024.04.txt +0 -0
  101. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/2024.10.txt +0 -0
  102. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/2025.06.txt +0 -0
  103. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/PREVIEW.txt +0 -0
  104. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/README.md +0 -0
  105. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/builder/base-images.json +0 -0
  106. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/call_graph.py +0 -0
  107. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/__init__.py +0 -0
  108. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/_download.py +0 -0
  109. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/_help.py +0 -0
  110. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/_traceback.py +0 -0
  111. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/app.py +0 -0
  112. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/billing.py +0 -0
  113. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/bootstrap.py +0 -0
  114. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/changelog.py +0 -0
  115. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/config.py +0 -0
  116. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/dashboard.py +0 -0
  117. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/dict.py +0 -0
  118. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/entry_point.py +0 -0
  119. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/environment.py +0 -0
  120. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/launch.py +0 -0
  121. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/logo.py +0 -0
  122. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/network_file_system.py +0 -0
  123. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/profile.py +0 -0
  124. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/programs/__init__.py +0 -0
  125. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/programs/run_jupyter.py +0 -0
  126. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/programs/vscode.py +0 -0
  127. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/queues.py +0 -0
  128. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/secret.py +0 -0
  129. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/selector.py +0 -0
  130. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/token.py +0 -0
  131. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/utils.py +0 -0
  132. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cli/volume.py +0 -0
  133. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/client.py +0 -0
  134. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cloud_bucket_mount.py +0 -0
  135. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/cloud_bucket_mount.pyi +0 -0
  136. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/container_process.py +0 -0
  137. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/container_process.pyi +0 -0
  138. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/dict.py +0 -0
  139. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/dict.pyi +0 -0
  140. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/environments.py +0 -0
  141. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/environments.pyi +0 -0
  142. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/exception.py +0 -0
  143. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/experimental/__init__.py +0 -0
  144. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/experimental/ipython.py +0 -0
  145. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/file_io.py +0 -0
  146. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/file_io.pyi +0 -0
  147. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/file_pattern_matcher.py +0 -0
  148. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/functions.py +0 -0
  149. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/image.py +0 -0
  150. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/image.pyi +0 -0
  151. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/io_streams.py +0 -0
  152. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/io_streams.pyi +0 -0
  153. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/mount.py +0 -0
  154. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/mount.pyi +0 -0
  155. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/network_file_system.py +0 -0
  156. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/network_file_system.pyi +0 -0
  157. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/object.py +0 -0
  158. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/object.pyi +0 -0
  159. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/output.py +0 -0
  160. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/parallel_map.py +0 -0
  161. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/parallel_map.pyi +0 -0
  162. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/partial_function.py +0 -0
  163. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/proxy.py +0 -0
  164. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/proxy.pyi +0 -0
  165. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/py.typed +0 -0
  166. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/queue.py +0 -0
  167. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/queue.pyi +0 -0
  168. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/retries.py +0 -0
  169. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/runner.py +0 -0
  170. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/runner.pyi +0 -0
  171. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/running_app.py +0 -0
  172. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/sandbox_fs.py +0 -0
  173. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/sandbox_fs.pyi +0 -0
  174. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/schedule.py +0 -0
  175. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/scheduler_placement.py +0 -0
  176. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/secret.py +0 -0
  177. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/secret.pyi +0 -0
  178. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/server.py +0 -0
  179. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/server.pyi +0 -0
  180. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/serving.py +0 -0
  181. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/serving.pyi +0 -0
  182. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/snapshot.py +0 -0
  183. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/snapshot.pyi +0 -0
  184. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/stream_type.py +0 -0
  185. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/token_flow.py +0 -0
  186. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/token_flow.pyi +0 -0
  187. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/volume.py +0 -0
  188. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal/volume.pyi +0 -0
  189. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal.egg-info/dependency_links.txt +0 -0
  190. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal.egg-info/entry_points.txt +0 -0
  191. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal.egg-info/requires.txt +0 -0
  192. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal.egg-info/top_level.txt +0 -0
  193. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/__init__.py +0 -0
  194. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/gen_cli_docs.py +0 -0
  195. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/gen_cli_docs_main.py +0 -0
  196. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/gen_reference_docs.py +0 -0
  197. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/gen_reference_docs_main.py +0 -0
  198. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/mdmd/__init__.py +0 -0
  199. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/mdmd/mdmd.py +0 -0
  200. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_docs/mdmd/signatures.py +0 -0
  201. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/__init__.py +0 -0
  202. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/api_grpc.py +0 -0
  203. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/api_pb2_grpc.py +0 -0
  204. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/api_pb2_grpc.pyi +0 -0
  205. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/modal_api_grpc.py +0 -0
  206. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/py.typed +0 -0
  207. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/task_command_router_grpc.py +0 -0
  208. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/task_command_router_pb2.py +0 -0
  209. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/task_command_router_pb2.pyi +0 -0
  210. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  211. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  212. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/modal_version/__main__.py +0 -0
  213. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/pyproject.toml +0 -0
  214. {modal-1.4.3.dev25 → modal-1.4.3.dev27}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.4.3.dev25
3
+ Version: 1.4.3.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
@@ -0,0 +1,257 @@
1
+ # Copyright Modal Labs 2026
2
+ import dataclasses
3
+ from collections.abc import Collection, Sequence, Sized
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, Any, Optional, Union
6
+
7
+ from modal_proto import api_pb2
8
+
9
+ from ._resources import convert_fn_config_to_resources_config
10
+ from ._serialization import (
11
+ apply_defaults,
12
+ serialize,
13
+ serialize_proto_params,
14
+ validate_parameter_values,
15
+ )
16
+ from ._utils.function_utils import _parse_retries
17
+ from ._utils.mount_utils import validate_volumes, validate_volumes_by_object_id
18
+ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
19
+ from .retries import Retries
20
+ from .secret import _Secret
21
+ from .volume import _Volume
22
+
23
+ if TYPE_CHECKING:
24
+ from ._functions import _Function
25
+ from ._load_context import LoadContext
26
+ from ._object import _Object
27
+ from ._resolver import Resolver
28
+
29
+
30
+ @dataclasses.dataclass()
31
+ class _FunctionOptions:
32
+ """Data class that holds local state for a dynamically configured Function / Cls.
33
+
34
+ Not a public interface. Dataclass fields represent post-validation parameter values.
35
+ Use the `.new()` constructor to transform from the public interface types.
36
+ """
37
+
38
+ # Note that default values must be "untruthy" so we can that detect when they are not set.
39
+ secrets: Collection[_Secret] = ()
40
+ validated_volumes: Sequence[tuple[str, _Volume]] = ()
41
+ cloud_bucket_mounts: Sequence[tuple[str, _CloudBucketMount]] = ()
42
+ resources: Optional[api_pb2.Resources] = None
43
+ retry_policy: Optional[api_pb2.FunctionRetryPolicy] = None
44
+ max_containers: Optional[int] = None
45
+ buffer_containers: Optional[int] = None
46
+ scaledown_window: Optional[int] = None
47
+ timeout_secs: Optional[int] = None
48
+ scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
49
+ cloud: Optional[str] = None
50
+ max_concurrent_inputs: Optional[int] = None
51
+ target_concurrent_inputs: Optional[int] = None
52
+ batch_max_size: Optional[int] = None
53
+ batch_wait_ms: Optional[int] = None
54
+
55
+ @classmethod
56
+ def new(
57
+ cls,
58
+ *,
59
+ cpu: Optional[Union[float, tuple[float, float]]] = None,
60
+ memory: Optional[Union[int, tuple[int, int]]] = None,
61
+ gpu: Optional[str] = None,
62
+ env: Optional[dict[str, Optional[str]]] = None,
63
+ secrets: Optional[Collection[_Secret]] = None,
64
+ volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
65
+ retries: Optional[Union[int, Retries]] = None,
66
+ max_containers: Optional[int] = None,
67
+ buffer_containers: Optional[int] = None,
68
+ scaledown_window: Optional[int] = None,
69
+ timeout: Optional[int] = None,
70
+ region: Optional[Union[str, Sequence[str]]] = None,
71
+ cloud: Optional[str] = None,
72
+ max_concurrent_inputs: Optional[int] = None,
73
+ target_concurrent_inputs: Optional[int] = None,
74
+ batch_max_size: Optional[int] = None,
75
+ batch_wait_ms: Optional[int] = None,
76
+ ) -> "_FunctionOptions":
77
+ """Internal constructor that validates and normalizes public parameters."""
78
+ retry_policy = _parse_retries(retries)
79
+ if gpu or cpu or memory:
80
+ resources = convert_fn_config_to_resources_config(cpu=cpu, memory=memory, gpu=gpu)
81
+ else:
82
+ resources = None
83
+
84
+ validated_volumes = validate_volumes(volumes)
85
+ cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
86
+ validated_volumes_no_cloud_buckets = [(k, v) for k, v in validated_volumes if isinstance(v, _Volume)]
87
+
88
+ secrets = secrets or []
89
+ if env:
90
+ secrets = [*secrets, _Secret.from_dict(env)]
91
+
92
+ scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
93
+ if region:
94
+ regions = [region] if isinstance(region, str) else list(region)
95
+ scheduler_placement = api_pb2.SchedulerPlacement(regions=regions)
96
+
97
+ # Use batched and concurrent decorators to apply consistent validation logic
98
+ from .partial_function import batched, concurrent
99
+
100
+ if batch_max_size is not None and batch_wait_ms is not None:
101
+ batched(max_batch_size=batch_max_size, wait_ms=batch_wait_ms)
102
+
103
+ if max_concurrent_inputs:
104
+ concurrent(max_inputs=max_concurrent_inputs, target_inputs=target_concurrent_inputs)
105
+
106
+ return cls(
107
+ secrets=secrets,
108
+ validated_volumes=validated_volumes_no_cloud_buckets,
109
+ cloud_bucket_mounts=cloud_bucket_mounts,
110
+ resources=resources,
111
+ retry_policy=retry_policy,
112
+ max_containers=max_containers,
113
+ buffer_containers=buffer_containers,
114
+ scaledown_window=scaledown_window,
115
+ timeout_secs=timeout,
116
+ scheduler_placement=scheduler_placement,
117
+ cloud=cloud,
118
+ max_concurrent_inputs=max_concurrent_inputs,
119
+ target_concurrent_inputs=target_concurrent_inputs,
120
+ batch_max_size=batch_max_size,
121
+ batch_wait_ms=batch_wait_ms,
122
+ )
123
+
124
+ def merge_options(self, new_options: "_FunctionOptions") -> "_FunctionOptions":
125
+ """Implement protobuf-like MergeFrom semantics for this dataclass.
126
+
127
+ This mostly exists to support "stacking" of `.with_options()` calls.
128
+ Returns a new _FunctionOptions instance without modifying self.
129
+ """
130
+ # Create a shallow copy of self to start with.
131
+ merged = dataclasses.replace(self)
132
+
133
+ # Don't use dataclasses.asdict() because it does a deepcopy(), which chokes on a hydrated object.
134
+ new_options_dict = {k.name: getattr(new_options, k.name) for k in dataclasses.fields(new_options)}
135
+
136
+ # Resources needs special merge handling because individual fields are parameters in the public API.
137
+ merged_resources = api_pb2.Resources()
138
+ if merged.resources:
139
+ merged_resources.MergeFrom(merged.resources)
140
+ if new_resources := new_options_dict.pop("resources"):
141
+ merged_resources.MergeFrom(new_resources)
142
+ merged.resources = merged_resources
143
+
144
+ for key, value in new_options_dict.items():
145
+ if value: # Only overwrite data when the value was set in the new options.
146
+ setattr(merged, key, value)
147
+
148
+ return merged
149
+
150
+ def _unhydrated_object_deps(self) -> list["_Object"]:
151
+ """Return unhydrated `modal.Object` instances that are part of the configuration payload."""
152
+ all_deps = (
153
+ [volume for _, volume in self.validated_volumes]
154
+ + list(self.secrets)
155
+ + [mount.secret for _, mount in self.cloud_bucket_mounts if mount.secret]
156
+ )
157
+ return [dep for dep in all_deps if not dep.is_hydrated]
158
+
159
+ def to_proto(self) -> api_pb2.FunctionOptions:
160
+ """Convert the dataclass to a FunctionOptions protobuf message."""
161
+ # Validate that the same volume (by object_id) isn't mounted at multiple paths.
162
+ # Needs to be called late so that volumes are hydrated
163
+ validate_volumes_by_object_id(self.validated_volumes)
164
+
165
+ volume_mounts = [
166
+ api_pb2.VolumeMount(
167
+ mount_path=path,
168
+ volume_id=volume.object_id,
169
+ allow_background_commits=True,
170
+ read_only=volume._read_only,
171
+ )
172
+ for path, volume in self.validated_volumes
173
+ ]
174
+ return api_pb2.FunctionOptions(
175
+ secret_ids=[secret.object_id for secret in self.secrets],
176
+ replace_secret_ids=bool(self.secrets),
177
+ replace_volume_mounts=len(volume_mounts) > 0,
178
+ volume_mounts=volume_mounts,
179
+ cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts),
180
+ replace_cloud_bucket_mounts=bool(self.cloud_bucket_mounts),
181
+ resources=self.resources,
182
+ retry_policy=self.retry_policy,
183
+ concurrency_limit=self.max_containers,
184
+ buffer_containers=self.buffer_containers,
185
+ task_idle_timeout_secs=self.scaledown_window,
186
+ timeout_secs=self.timeout_secs,
187
+ max_concurrent_inputs=self.max_concurrent_inputs,
188
+ target_concurrent_inputs=self.target_concurrent_inputs,
189
+ batch_max_size=self.batch_max_size,
190
+ batch_linger_ms=self.batch_wait_ms,
191
+ scheduler_placement=self.scheduler_placement,
192
+ cloud_provider_str=self.cloud,
193
+ )
194
+
195
+
196
+ def _make_function_variant(
197
+ base_function: "_Function",
198
+ options: Optional[_FunctionOptions],
199
+ parameter_schema: Optional[Sequence[api_pb2.ClassParameterSpec]],
200
+ args: Sized,
201
+ kwargs: dict[str, Any],
202
+ ) -> "_Function":
203
+ """Extend a base Function with parameter values or dynamic configuration options."""
204
+
205
+ async def _load(
206
+ function_variant: "_Function",
207
+ resolver: "Resolver",
208
+ load_context: "LoadContext",
209
+ existing_object_id: Optional[str],
210
+ ):
211
+ if not base_function.is_hydrated:
212
+ await base_function.hydrate(load_context.client)
213
+ assert base_function._client and base_function._client.stub
214
+
215
+ if parameter_schema is None:
216
+ # This branch is about backwards compatibility.
217
+ # For Cls, we have `parameter_schema = None` for both old-style classes that
218
+ # use a custom constructor (and hence use pickle serialization) and for
219
+ # un-parameterized classes of any vintage, because such classes historically
220
+ # sent serialized empty args/kwargs rather than a null `serialized_params` bytestring.
221
+ serialized_params = serialize((args, kwargs))
222
+ else:
223
+ # New-style modal.parameter() based parameterization with protobuf serialization,
224
+ # including true Function variants with no parameters defined
225
+ # (in which case, serialized_params is a null bytestring).
226
+ kwargs_with_defaults = apply_defaults(kwargs, parameter_schema)
227
+ validate_parameter_values(kwargs_with_defaults, parameter_schema)
228
+ serialized_params = serialize_proto_params(kwargs_with_defaults)
229
+
230
+ options_pb = options.to_proto() if options else None
231
+
232
+ req = api_pb2.FunctionBindParamsRequest(
233
+ function_id=base_function.object_id,
234
+ serialized_params=serialized_params,
235
+ function_options=options_pb,
236
+ environment_name=load_context.environment_name
237
+ or "", # TODO: investigate shouldn't environment name always be specified here?
238
+ )
239
+
240
+ response = await base_function._client.stub.FunctionBindParams(req)
241
+ function_variant._hydrate(response.bound_function_id, base_function._client, response.handle_metadata)
242
+
243
+ def _deps():
244
+ if options:
245
+ return options._unhydrated_object_deps()
246
+ return []
247
+
248
+ fun = base_function._from_loader(
249
+ _load,
250
+ base_function._rep,
251
+ hydrate_lazily=True,
252
+ deps=_deps,
253
+ load_context_overrides=base_function._load_context_overrides,
254
+ )
255
+ fun._info = base_function._info
256
+ fun._spec = base_function._spec # TODO (elias): fix - this is incorrect when using with_options
257
+ return fun
@@ -5,7 +5,7 @@ import inspect
5
5
  import time
6
6
  import typing
7
7
  import warnings
8
- from collections.abc import AsyncGenerator, Collection, Sequence, Sized
8
+ from collections.abc import AsyncGenerator, Collection, Sequence
9
9
  from dataclasses import dataclass
10
10
  from pathlib import PurePosixPath
11
11
  from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Optional, Union
@@ -18,6 +18,7 @@ from synchronicity.combined_types import MethodWithAio
18
18
  from modal_proto import api_pb2
19
19
  from modal_proto.modal_api_grpc import ModalClientModal
20
20
 
21
+ from ._function_variants import _FunctionOptions, _make_function_variant
21
22
  from ._load_context import LoadContext
22
23
  from ._object import _Object, live_method, live_method_gen
23
24
  from ._output.pty import get_pty_info
@@ -26,11 +27,8 @@ from ._resolver import Resolver
26
27
  from ._resources import convert_fn_config_to_resources_config
27
28
  from ._runtime.execution_context import current_input_id, is_local
28
29
  from ._serialization import (
29
- apply_defaults,
30
30
  get_callable_schema,
31
31
  serialize,
32
- serialize_proto_params,
33
- validate_parameter_values,
34
32
  )
35
33
  from ._traceback import print_server_warnings
36
34
  from ._utils.async_utils import (
@@ -48,6 +46,7 @@ from ._utils.function_utils import (
48
46
  OUTPUTS_TIMEOUT,
49
47
  FunctionInfo,
50
48
  _create_input,
49
+ _parse_retries,
51
50
  _process_result,
52
51
  _stream_function_call_data,
53
52
  get_function_type,
@@ -552,26 +551,6 @@ class FunctionStats:
552
551
  input_headroom: int
553
552
 
554
553
 
555
- def _parse_retries(
556
- retries: Optional[Union[int, Retries]],
557
- source: str = "",
558
- ) -> Optional[api_pb2.FunctionRetryPolicy]:
559
- if isinstance(retries, int):
560
- return Retries(
561
- max_retries=retries,
562
- initial_delay=1.0,
563
- backoff_coefficient=1.0,
564
- )._to_proto()
565
- elif isinstance(retries, Retries):
566
- return retries._to_proto()
567
- elif retries is None:
568
- return None
569
- else:
570
- extra = f" on {source}" if source else ""
571
- msg = f"Retries parameter must be an integer or instance of modal.Retries. Found: {type(retries)}{extra}."
572
- raise InvalidError(msg)
573
-
574
-
575
554
  @dataclass
576
555
  class _FunctionSpec:
577
556
  """
@@ -655,6 +634,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
655
634
  None # set for 0.67+ class service functions
656
635
  )
657
636
  _metadata: Optional[api_pb2.FunctionHandleMetadata] = None
637
+ _options: _FunctionOptions
638
+ _base_function: Optional["_Function"] = None
658
639
 
659
640
  @staticmethod
660
641
  def from_local(
@@ -738,9 +719,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
738
719
 
739
720
  if retry_policy is not None:
740
721
  if webhook_config is not None:
741
- raise InvalidError("Web endpoints do not support retries.")
722
+ raise InvalidError("Web Functions do not support retries.")
742
723
  if is_generator:
743
- raise InvalidError("Generator functions do not support retries.")
724
+ raise InvalidError("Generator Functions do not support retries.")
744
725
 
745
726
  if timeout is None: # type: ignore[unreachable] # Help users who aren't using type checkers
746
727
  raise InvalidError("The `timeout` parameter cannot be set to None: https://modal.com/docs/guide/timeouts")
@@ -1172,130 +1153,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1172
1153
 
1173
1154
  return obj
1174
1155
 
1175
- def _bind_parameters(
1176
- self,
1177
- obj: "modal.cls._Obj",
1178
- options: Optional["modal.cls._ServiceOptions"],
1179
- args: Sized,
1180
- kwargs: dict[str, Any],
1181
- ) -> "_Function":
1182
- """mdmd:hidden
1183
-
1184
- Binds a class-function to a specific instance of (init params, options) or a new workspace
1185
- """
1186
-
1187
- parent = self
1188
-
1189
- async def _load(
1190
- param_bound_func: _Function,
1191
- resolver: Resolver,
1192
- load_context: LoadContext,
1193
- existing_object_id: Optional[str],
1194
- ):
1195
- if not parent.is_hydrated:
1196
- # While the base Object.hydrate() method appears to be idempotent, it's not always safe
1197
- await parent.hydrate()
1198
-
1199
- assert parent._client and parent._client.stub
1200
-
1201
- if (
1202
- parent._class_parameter_info
1203
- and parent._class_parameter_info.format == api_pb2.ClassParameterInfo.PARAM_SERIALIZATION_FORMAT_PROTO
1204
- ):
1205
- if args:
1206
- # TODO(elias) - We could potentially support positional args as well, if we want to?
1207
- raise InvalidError(
1208
- "Can't use positional arguments with modal.parameter-based synthetic constructors.\n"
1209
- "Use (<parameter_name>=value) keyword arguments when constructing classes instead."
1210
- )
1211
- schema = parent._class_parameter_info.schema
1212
- kwargs_with_defaults = apply_defaults(kwargs, schema)
1213
- validate_parameter_values(kwargs_with_defaults, schema)
1214
- serialized_params = serialize_proto_params(kwargs_with_defaults)
1215
- can_use_parent = len(parent._class_parameter_info.schema) == 0 # no parameters
1216
- else:
1217
- from modal.cls import _ServiceOptions # Should probably define this dataclass here?
1218
-
1219
- can_use_parent = len(args) + len(kwargs) == 0 and (options == _ServiceOptions())
1220
- serialized_params = serialize((args, kwargs))
1221
-
1222
- if can_use_parent:
1223
- # We can end up here if parent wasn't hydrated when class was instantiated, but has been since.
1224
- param_bound_func._hydrate_from_other(parent)
1225
- return
1226
-
1227
- assert parent is not None and parent.is_hydrated
1228
-
1229
- if options:
1230
- # Validate that the same volume (by object_id) isn't mounted at multiple paths
1231
- validate_volumes_by_object_id(options.validated_volumes)
1232
-
1233
- volume_mounts = [
1234
- api_pb2.VolumeMount(
1235
- mount_path=path,
1236
- volume_id=volume.object_id,
1237
- allow_background_commits=True,
1238
- read_only=volume._read_only,
1239
- )
1240
- for path, volume in options.validated_volumes
1241
- ]
1242
- options_pb = api_pb2.FunctionOptions(
1243
- secret_ids=[s.object_id for s in options.secrets],
1244
- replace_secret_ids=bool(options.secrets),
1245
- replace_volume_mounts=len(volume_mounts) > 0,
1246
- volume_mounts=volume_mounts,
1247
- cloud_bucket_mounts=cloud_bucket_mounts_to_proto(options.cloud_bucket_mounts),
1248
- replace_cloud_bucket_mounts=bool(options.cloud_bucket_mounts),
1249
- resources=options.resources,
1250
- retry_policy=options.retry_policy,
1251
- concurrency_limit=options.max_containers,
1252
- buffer_containers=options.buffer_containers,
1253
- task_idle_timeout_secs=options.scaledown_window,
1254
- timeout_secs=options.timeout_secs,
1255
- max_concurrent_inputs=options.max_concurrent_inputs,
1256
- target_concurrent_inputs=options.target_concurrent_inputs,
1257
- batch_max_size=options.batch_max_size,
1258
- batch_linger_ms=options.batch_wait_ms,
1259
- scheduler_placement=options.scheduler_placement,
1260
- cloud_provider_str=options.cloud,
1261
- )
1262
- else:
1263
- options_pb = None
1264
-
1265
- req = api_pb2.FunctionBindParamsRequest(
1266
- function_id=parent.object_id,
1267
- serialized_params=serialized_params,
1268
- function_options=options_pb,
1269
- environment_name=load_context.environment_name
1270
- or "", # TODO: investigate shouldn't environment name always be specified here?
1271
- )
1272
-
1273
- response = await parent._client.stub.FunctionBindParams(req)
1274
- param_bound_func._hydrate(response.bound_function_id, parent._client, response.handle_metadata)
1275
-
1276
- def _deps():
1277
- if options:
1278
- all_deps = (
1279
- [v for _, v in options.validated_volumes]
1280
- + list(options.secrets)
1281
- + [mount.secret for _, mount in options.cloud_bucket_mounts if mount.secret]
1282
- )
1283
- return [dep for dep in all_deps if not dep.is_hydrated]
1284
- return []
1285
-
1286
- fun: _Function = _Function._from_loader(
1287
- _load,
1288
- "Function(parametrized)",
1289
- hydrate_lazily=True,
1290
- deps=_deps,
1291
- load_context_overrides=self._load_context_overrides,
1292
- )
1293
-
1294
- fun._info = self._info
1295
- fun._obj = obj
1296
- fun._spec = self._spec # TODO (elias): fix - this is incorrect when using with_options
1297
- return fun
1298
-
1299
1156
  @live_method
1300
1157
  async def update_autoscaler(
1301
1158
  self,
@@ -1472,6 +1329,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1472
1329
  self._serve_mounts = frozenset()
1473
1330
  self._metadata = None
1474
1331
  self._experimental_flash_urls = None
1332
+ self._options = _FunctionOptions()
1333
+ self._base_function = None
1475
1334
 
1476
1335
  def _hydrate_metadata(self, metadata: Optional[Message]):
1477
1336
  # Overridden concrete implementation of base class method
@@ -1532,7 +1391,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1532
1391
 
1533
1392
  @live_method
1534
1393
  async def get_web_url(self) -> Optional[str]:
1535
- """URL of a Function running as a web endpoint."""
1394
+ """URL for addressing a Web Function via HTTP.
1395
+
1396
+ Returns None when this is not a Web Function.
1397
+ """
1536
1398
  return self._web_url
1537
1399
 
1538
1400
  @live_method
@@ -1540,6 +1402,143 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1540
1402
  """URL of the flash service for the function."""
1541
1403
  return list(self._experimental_flash_urls) if self._experimental_flash_urls else None
1542
1404
 
1405
+ def _apply_dynamic_config(
1406
+ self,
1407
+ new_options: _FunctionOptions,
1408
+ config_method_name: str,
1409
+ ) -> "_Function[P, ReturnType, OriginalReturnType]":
1410
+ base_function = self._base_function or self
1411
+ combined_options = self._options.merge_options(new_options)
1412
+
1413
+ # For "True Functions" (i.e. not the Function inside a Cls), we will use
1414
+ # `parameter_schema=[]` when the Function is un-parameterized. Note that
1415
+ # `parameter_schema=Null` has different semantics that preserve Cls backwards compatibility.
1416
+ parameter_schema: Sequence[api_pb2.ClassParameterSpec] = []
1417
+ args = ()
1418
+ kwargs: dict[str, Any] = {}
1419
+
1420
+ # The main reason to have two layers here (_apply_dynamic_config -> _make_function_variant)
1421
+ # is because the _make_function_variant logic is shared between a true Function and a
1422
+ # "class service function", but we need to propagate different instance metadata to the
1423
+ # new object in each case. There's likely a cleaner way to do this, and then we could just
1424
+ # collapse the dynamic configuration into a single layer.
1425
+ new_function = _make_function_variant(base_function, combined_options, parameter_schema, args, kwargs)
1426
+
1427
+ new_function._options = combined_options
1428
+ new_function._base_function = base_function
1429
+ new_function._rep = f"{self._rep}.{config_method_name}(...)"
1430
+
1431
+ new_function._app = self._app
1432
+ new_function._obj = self._obj
1433
+
1434
+ new_function._is_generator = self._is_generator
1435
+ new_function._webhook_config = self._webhook_config
1436
+
1437
+ # Other fields not necessarily initialized
1438
+ if self._info is not None:
1439
+ new_function._build_args = self._build_args
1440
+ new_function._is_method = self._is_method
1441
+ new_function._raw_f = self._raw_f
1442
+ new_function._tag = self._tag
1443
+
1444
+ return new_function
1445
+
1446
+ def with_options(
1447
+ self,
1448
+ *,
1449
+ cpu: Optional[Union[float, tuple[float, float]]] = None,
1450
+ memory: Optional[Union[int, tuple[int, int]]] = None,
1451
+ gpu: Optional[str] = None,
1452
+ env: Optional[dict[str, Optional[str]]] = None,
1453
+ secrets: Optional[Collection[_Secret]] = None,
1454
+ volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
1455
+ retries: Optional[Union[int, Retries]] = None,
1456
+ max_containers: Optional[int] = None,
1457
+ buffer_containers: Optional[int] = None,
1458
+ scaledown_window: Optional[int] = None,
1459
+ timeout: Optional[int] = None,
1460
+ region: Optional[Union[str, Sequence[str]]] = None,
1461
+ cloud: Optional[str] = None,
1462
+ ) -> "_Function[P, ReturnType, OriginalReturnType]":
1463
+ """Dynamically override the static Function configuration with invocation-specific values.
1464
+
1465
+ This method returns a new Function instance with the dynamic configuration. Invocations of
1466
+ the new Function will run in a distinct container pool and autoscale independently from the
1467
+ base Function (and from other dynamic configurations).
1468
+
1469
+ Note that options cannot be "unset" with this method (i.e., if a GPU is configured in the
1470
+ `@app.cls()` decorator, passing `gpu=None` here will not create a CPU-only instance).
1471
+ Additionally, container arguments like `volumes` and `secrets` will _replace_ the base
1472
+ configuration or any previous use of this method rather than extending it.
1473
+
1474
+ **Usage:**
1475
+
1476
+ You can use this method after looking up a deployed Function:
1477
+
1478
+ ```python notest
1479
+ fn = modal.Function.from_name("my_app", "fn").with_options(gpu="H100")
1480
+ fn.remote() # will run on a H100 GPU
1481
+ ```
1482
+
1483
+ Or by referencing another Function defined in the same App:
1484
+
1485
+ ```python notest
1486
+ @app.function()
1487
+ def fn():
1488
+ ...
1489
+
1490
+ # From a local entrypoint or another Function
1491
+ fn.with_options(gpu="H100").remote() # Uses an H100 GPU
1492
+ fn.remote() # Uses the static configuration with no GPU
1493
+ ```
1494
+
1495
+ """
1496
+ options = _FunctionOptions.new(
1497
+ cpu=cpu,
1498
+ memory=memory,
1499
+ gpu=gpu,
1500
+ env=env,
1501
+ secrets=secrets,
1502
+ volumes=volumes,
1503
+ retries=retries,
1504
+ max_containers=max_containers,
1505
+ buffer_containers=buffer_containers,
1506
+ scaledown_window=scaledown_window,
1507
+ timeout=timeout,
1508
+ region=region,
1509
+ cloud=cloud,
1510
+ )
1511
+
1512
+ return self._apply_dynamic_config(options, "with_options")
1513
+
1514
+ def with_concurrency(
1515
+ self,
1516
+ *,
1517
+ max_inputs: int,
1518
+ target_inputs: Optional[int] = None,
1519
+ ) -> "_Function[P, ReturnType, OriginalReturnType]":
1520
+ """Override the static Function configuration with invocation-specific input concurrency.
1521
+
1522
+ Returns a new Function instance that is dynamically configured to behave like a Function using
1523
+ the `@modal.concurrent` decorator. This instance will autoscale independently from the base Function.
1524
+ """
1525
+ options = _FunctionOptions.new(max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs)
1526
+ return self._apply_dynamic_config(options, "with_concurrency")
1527
+
1528
+ def with_batching(
1529
+ self,
1530
+ *,
1531
+ max_batch_size: int,
1532
+ wait_ms: int,
1533
+ ) -> "_Function[P, ReturnType, OriginalReturnType]":
1534
+ """Override the static Function configuration with invocation-specific dynamic batching.
1535
+
1536
+ Returns a new Function instance that is dynamically configured to behave like a Function using
1537
+ the `@modal.batched` decorator. This instance will autoscale independently from the base Function.
1538
+ """
1539
+ options = _FunctionOptions.new(batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
1540
+ return self._apply_dynamic_config(options, "with_batching")
1541
+
1543
1542
  @property
1544
1543
  async def is_generator(self) -> bool:
1545
1544
  """mdmd:hidden"""
@@ -90,7 +90,7 @@ class FunctionCreationStatus:
90
90
  suffix = _get_suffix_from_web_url_info(url_info)
91
91
  class_web_endpoint_method_status_row = self._output_mgr.add_status_row()
92
92
  class_web_endpoint_method_status_row.finish(
93
- f"Created web endpoint for {method_definition.function_name} => [magenta underline]"
93
+ f"Created Web Function URL for {method_definition.function_name} => [magenta underline]"
94
94
  f"{method_definition.web_url}[/magenta underline]{suffix}"
95
95
  )
96
96
  for custom_domain in method_definition.custom_domain_info: