modAL 1.3.1.dev33__tar.gz → 1.3.1.dev35__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 (197) hide show
  1. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/PKG-INFO +1 -1
  2. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/__init__.py +2 -0
  3. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_container_entrypoint.py +13 -1
  4. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_functions.py +5 -2
  5. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_object.py +5 -3
  6. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/user_code_imports.py +131 -63
  7. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_server.py +12 -6
  8. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/function_utils.py +3 -4
  9. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/app.py +156 -1
  10. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/app.pyi +118 -0
  11. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/import_refs.py +2 -2
  12. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/client.pyi +2 -2
  13. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/experimental/__init__.py +1 -1
  14. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/experimental/flash.py +1 -0
  15. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/functions.pyi +1 -0
  16. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/runner.py +10 -0
  17. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/server.pyi +4 -4
  18. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/volume.pyi +2 -2
  19. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal.egg-info/PKG-INFO +1 -1
  20. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/api.proto +3 -2
  21. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/api_pb2.py +529 -529
  22. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/api_pb2.pyi +6 -3
  23. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_version/__init__.py +2 -2
  24. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/LICENSE +0 -0
  25. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/README.md +0 -0
  26. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/__main__.py +0 -0
  27. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_billing.py +0 -0
  28. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_clustered_functions.py +0 -0
  29. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_clustered_functions.pyi +0 -0
  30. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_grpc_client.py +0 -0
  31. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_ipython.py +0 -0
  32. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_load_context.py +0 -0
  33. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_location.py +0 -0
  34. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_output.py +0 -0
  35. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_partial_function.py +0 -0
  36. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_pty.py +0 -0
  37. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_resolver.py +0 -0
  38. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_resources.py +0 -0
  39. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/__init__.py +0 -0
  40. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/asgi.py +0 -0
  41. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/container_io_manager.py +0 -0
  42. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/container_io_manager.pyi +0 -0
  43. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/execution_context.py +0 -0
  44. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/execution_context.pyi +0 -0
  45. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  46. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/telemetry.py +0 -0
  47. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_runtime/user_code_event_loop.py +0 -0
  48. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_serialization.py +0 -0
  49. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_traceback.py +0 -0
  50. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_tunnel.py +0 -0
  51. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_tunnel.pyi +0 -0
  52. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_type_manager.py +0 -0
  53. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/__init__.py +0 -0
  54. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/app_utils.py +0 -0
  55. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/async_utils.py +0 -0
  56. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/auth_token_manager.py +0 -0
  57. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/blob_utils.py +0 -0
  58. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/bytes_io_segment_payload.py +0 -0
  59. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/deprecation.py +0 -0
  60. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/docker_utils.py +0 -0
  61. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/git_utils.py +0 -0
  62. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/grpc_testing.py +0 -0
  63. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/grpc_utils.py +0 -0
  64. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/hash_utils.py +0 -0
  65. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/http_utils.py +0 -0
  66. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/jwt_utils.py +0 -0
  67. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/logger.py +0 -0
  68. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/mount_utils.py +0 -0
  69. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/name_utils.py +0 -0
  70. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/package_utils.py +0 -0
  71. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/pattern_utils.py +0 -0
  72. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/rand_pb_testing.py +0 -0
  73. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/shell_utils.py +0 -0
  74. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/task_command_router_client.py +0 -0
  75. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_utils/time_utils.py +0 -0
  76. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_vendor/__init__.py +0 -0
  77. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  78. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_vendor/cloudpickle.py +0 -0
  79. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_vendor/tblib.py +0 -0
  80. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/_watcher.py +0 -0
  81. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/billing.py +0 -0
  82. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/2023.12.312.txt +0 -0
  83. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/2023.12.txt +0 -0
  84. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/2024.04.txt +0 -0
  85. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/2024.10.txt +0 -0
  86. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/2025.06.txt +0 -0
  87. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/PREVIEW.txt +0 -0
  88. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/README.md +0 -0
  89. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/builder/base-images.json +0 -0
  90. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/call_graph.py +0 -0
  91. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/__init__.py +0 -0
  92. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/_download.py +0 -0
  93. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/_traceback.py +0 -0
  94. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/app.py +0 -0
  95. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/cluster.py +0 -0
  96. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/config.py +0 -0
  97. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/container.py +0 -0
  98. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/dict.py +0 -0
  99. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/entry_point.py +0 -0
  100. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/environment.py +0 -0
  101. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/launch.py +0 -0
  102. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/network_file_system.py +0 -0
  103. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/profile.py +0 -0
  104. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/programs/__init__.py +0 -0
  105. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/programs/run_jupyter.py +0 -0
  106. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/programs/vscode.py +0 -0
  107. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/queues.py +0 -0
  108. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/run.py +0 -0
  109. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/secret.py +0 -0
  110. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/shell.py +0 -0
  111. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/token.py +0 -0
  112. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/utils.py +0 -0
  113. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cli/volume.py +0 -0
  114. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/client.py +0 -0
  115. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cloud_bucket_mount.py +0 -0
  116. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cloud_bucket_mount.pyi +0 -0
  117. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cls.py +0 -0
  118. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/cls.pyi +0 -0
  119. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/config.py +0 -0
  120. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/container_process.py +0 -0
  121. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/container_process.pyi +0 -0
  122. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/dict.py +0 -0
  123. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/dict.pyi +0 -0
  124. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/environments.py +0 -0
  125. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/environments.pyi +0 -0
  126. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/exception.py +0 -0
  127. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/experimental/flash.pyi +0 -0
  128. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/experimental/ipython.py +0 -0
  129. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/file_io.py +0 -0
  130. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/file_io.pyi +0 -0
  131. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/file_pattern_matcher.py +0 -0
  132. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/functions.py +0 -0
  133. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/gpu.py +0 -0
  134. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/image.py +0 -0
  135. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/image.pyi +0 -0
  136. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/io_streams.py +0 -0
  137. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/io_streams.pyi +0 -0
  138. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/mount.py +0 -0
  139. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/mount.pyi +0 -0
  140. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/network_file_system.py +0 -0
  141. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/network_file_system.pyi +0 -0
  142. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/object.py +0 -0
  143. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/object.pyi +0 -0
  144. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/output.py +0 -0
  145. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/parallel_map.py +0 -0
  146. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/parallel_map.pyi +0 -0
  147. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/partial_function.py +0 -0
  148. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/partial_function.pyi +0 -0
  149. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/proxy.py +0 -0
  150. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/proxy.pyi +0 -0
  151. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/py.typed +0 -0
  152. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/queue.py +0 -0
  153. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/queue.pyi +0 -0
  154. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/retries.py +0 -0
  155. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/runner.pyi +0 -0
  156. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/running_app.py +0 -0
  157. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/sandbox.py +0 -0
  158. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/sandbox.pyi +0 -0
  159. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/schedule.py +0 -0
  160. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/scheduler_placement.py +0 -0
  161. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/secret.py +0 -0
  162. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/secret.pyi +0 -0
  163. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/server.py +0 -0
  164. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/serving.py +0 -0
  165. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/serving.pyi +0 -0
  166. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/snapshot.py +0 -0
  167. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/snapshot.pyi +0 -0
  168. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/stream_type.py +0 -0
  169. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/token_flow.py +0 -0
  170. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/token_flow.pyi +0 -0
  171. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal/volume.py +0 -0
  172. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal.egg-info/SOURCES.txt +0 -0
  173. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal.egg-info/dependency_links.txt +0 -0
  174. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal.egg-info/entry_points.txt +0 -0
  175. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal.egg-info/requires.txt +0 -0
  176. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal.egg-info/top_level.txt +0 -0
  177. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_docs/__init__.py +0 -0
  178. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_docs/gen_cli_docs.py +0 -0
  179. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_docs/gen_reference_docs.py +0 -0
  180. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_docs/mdmd/__init__.py +0 -0
  181. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_docs/mdmd/mdmd.py +0 -0
  182. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_docs/mdmd/signatures.py +0 -0
  183. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/__init__.py +0 -0
  184. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/api_grpc.py +0 -0
  185. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/api_pb2_grpc.py +0 -0
  186. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/api_pb2_grpc.pyi +0 -0
  187. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/modal_api_grpc.py +0 -0
  188. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/py.typed +0 -0
  189. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/task_command_router.proto +0 -0
  190. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/task_command_router_grpc.py +0 -0
  191. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/task_command_router_pb2.py +0 -0
  192. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/task_command_router_pb2.pyi +0 -0
  193. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  194. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  195. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/modal_version/__main__.py +0 -0
  196. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/pyproject.toml +0 -0
  197. {modal-1.3.1.dev33 → modal-1.3.1.dev35}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.1.dev33
3
+ Version: 1.3.1.dev35
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -41,6 +41,7 @@ try:
41
41
  from .schedule import Cron, Period
42
42
  from .scheduler_placement import SchedulerPlacement
43
43
  from .secret import Secret
44
+ from .server import Server
44
45
  from .snapshot import SandboxSnapshot
45
46
  from .volume import Volume
46
47
  except Exception:
@@ -75,6 +76,7 @@ __all__ = [
75
76
  "SandboxSnapshot",
76
77
  "SchedulerPlacement",
77
78
  "Secret",
79
+ "Server",
78
80
  "Tunnel",
79
81
  "Volume",
80
82
  "asgi_app",
@@ -5,6 +5,7 @@ import os
5
5
  from modal._runtime.user_code_imports import (
6
6
  Service,
7
7
  import_class_service,
8
+ import_server_service,
8
9
  import_single_function_service,
9
10
  )
10
11
 
@@ -300,7 +301,18 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
300
301
  param_kwargs = {}
301
302
 
302
303
  with execution_context._import_context():
303
- if function_def.is_class:
304
+ if function_def.is_server:
305
+ service_base_function_id = container_args.app_layout.function_ids[function_def.function_name]
306
+ service_function_hydration_data = [
307
+ o for o in container_args.app_layout.objects if o.object_id == service_base_function_id
308
+ ][0]
309
+ service = import_server_service(
310
+ function_def,
311
+ service_function_hydration_data,
312
+ client,
313
+ ser_usr_cls,
314
+ )
315
+ elif function_def.is_class:
304
316
  # this is a bit ugly - match the function and class based on function name to get metadata
305
317
  # This metadata is required in order to hydrate the class in case it's not globally
306
318
  # decorated (or serialized)
@@ -689,6 +689,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
689
689
  nonpreemptible: bool = False,
690
690
  is_builder_function: bool = False,
691
691
  is_auto_snapshot: bool = False,
692
+ is_server: bool = False,
692
693
  enable_memory_snapshot: bool = False,
693
694
  block_network: bool = False,
694
695
  restrict_modal_access: bool = False,
@@ -722,7 +723,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
722
723
  f"Function {raw_f} has a schedule, so it needs to support being called with no arguments"
723
724
  )
724
725
  else:
725
- # must be a "class service function"
726
726
  assert info.user_cls
727
727
  assert not webhook_config
728
728
  assert not schedule
@@ -734,7 +734,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
734
734
  ]
735
735
 
736
736
  retry_policy = _parse_retries(
737
- retries, f"Function '{info.get_tag()}'" if info.raw_f else f"Class '{info.get_tag()}'"
737
+ retries,
738
+ f"Function '{info.get_tag()}'" if info.raw_f else f"Class '{info.get_tag()}'",
738
739
  )
739
740
 
740
741
  if retry_policy is not None:
@@ -1054,6 +1055,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1054
1055
  supported_input_formats=supported_input_formats,
1055
1056
  supported_output_formats=supported_output_formats,
1056
1057
  http_config=http_config,
1058
+ is_server=is_server,
1057
1059
  )
1058
1060
 
1059
1061
  if isinstance(gpu, list):
@@ -1093,6 +1095,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1093
1095
  supported_input_formats=supported_input_formats,
1094
1096
  supported_output_formats=supported_output_formats,
1095
1097
  http_config=http_config,
1098
+ is_server=function_definition.is_server,
1096
1099
  )
1097
1100
 
1098
1101
  ranked_functions = []
@@ -127,9 +127,11 @@ class _Object:
127
127
  assert isinstance(object_id, str) and self._type_prefix is not None
128
128
  if not object_id.startswith(self._type_prefix):
129
129
  raise ExecutionError(
130
- f"Can not hydrate {type(self)}:"
130
+ f"Can not hydrate {type(self)}: "
131
131
  f" it has type prefix {self._type_prefix}"
132
- f" but the object_id starts with {object_id[:3]}"
132
+ f" but the object_id starts with {object_id[:3]}. "
133
+ "This usually means the object name was previously used for a different type. "
134
+ "Rename the object/app or stop the previous deployment and redeploy."
133
135
  )
134
136
  self._object_id = object_id
135
137
  self._client = client
@@ -137,7 +139,7 @@ class _Object:
137
139
  self._is_hydrated = True
138
140
 
139
141
  def _hydrate_metadata(self, metadata: Optional[Message]):
140
- # override this is subclasses that need additional data (other than an object_id) for a functioning Handle
142
+ # override this if it's a subclass that needs additional data (other than an object_id) for a functioning Handle
141
143
  pass
142
144
 
143
145
  def _get_metadata(self) -> Optional[Message]:
@@ -13,6 +13,7 @@ from typing import Any, Callable, Generator, Optional, Sequence
13
13
  import modal._object
14
14
  import modal._runtime.container_io_manager
15
15
  import modal.cls
16
+ import modal.server
16
17
  from modal import Function
17
18
  from modal._functions import _Function
18
19
  from modal._partial_function import (
@@ -129,21 +130,23 @@ class Service(metaclass=ABCMeta):
129
130
  self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
130
131
  ) -> dict[str, "FinalizedFunction"]: ...
131
132
 
132
- @abstractmethod
133
133
  @contextmanager
134
134
  def lifecycle_presnapshot(
135
135
  self,
136
136
  event_loop: UserCodeEventLoop,
137
137
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
138
- ) -> Generator[None, None, None]: ...
138
+ ) -> Generator[None, None, None]:
139
+ # Default no-op implementation for services without pre-snapshot lifecycle handling
140
+ yield
139
141
 
140
- @abstractmethod
141
142
  @contextmanager
142
143
  def lifecycle_postsnapshot(
143
144
  self,
144
145
  event_loop: UserCodeEventLoop,
145
146
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
146
- ) -> Generator[None, None, None]: ...
147
+ ) -> Generator[None, None, None]:
148
+ # Default no-op implementation for services without post-snapshot lifecycle handling
149
+ yield
147
150
 
148
151
  @contextmanager
149
152
  def execution_context(
@@ -303,13 +306,28 @@ class ImportedFunction(Service):
303
306
  )
304
307
  }
305
308
 
309
+
310
+ class _LifecycleManager:
311
+ """Lifecycle manager for class-based services (Cls and Server).
312
+
313
+ Handles pre snapshot, post snapshot, and exit lifecycle handling
314
+ """
315
+
316
+ user_cls_instance: Any
317
+ function_def: api_pb2.Function
318
+
306
319
  @contextmanager
307
320
  def lifecycle_presnapshot(
308
321
  self,
309
322
  event_loop: UserCodeEventLoop,
310
323
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
311
324
  ):
312
- # This is a no-op for imported functions since @enter methods are not supported
325
+ # Identify all "enter" methods that need to run before we snapshot.
326
+ if not self.function_def.is_auto_snapshot:
327
+ pre_snapshot_methods = _find_callables_for_obj(
328
+ self.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
329
+ )
330
+ call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
313
331
  yield
314
332
 
315
333
  @contextmanager
@@ -318,12 +336,26 @@ class ImportedFunction(Service):
318
336
  event_loop: UserCodeEventLoop,
319
337
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
320
338
  ):
321
- # This is a no-op for imported functions since @enter methods are not supported
322
- yield
339
+ # Identify the "enter" methods to run after resuming from a snapshot.
340
+ flash_entry = _FlashContainerEntry(self.function_def.http_config)
341
+ if not self.function_def.is_auto_snapshot:
342
+ post_snapshot_methods = _find_callables_for_obj(
343
+ self.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
344
+ )
345
+ call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
346
+ flash_entry.enter()
347
+ try:
348
+ yield
349
+ finally:
350
+ if not self.function_def.is_auto_snapshot:
351
+ flash_entry.stop()
352
+ exit_methods = _find_callables_for_obj(self.user_cls_instance, _PartialFunctionFlags.EXIT)
353
+ call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
354
+ flash_entry.close()
323
355
 
324
356
 
325
357
  @dataclass
326
- class ImportedClass(Service):
358
+ class ImportedClass(_LifecycleManager, Service):
327
359
  user_cls_instance: Any
328
360
  app: "modal.app._App"
329
361
  service_deps: Optional[Sequence["modal._object._Object"]]
@@ -373,42 +405,18 @@ class ImportedClass(Service):
373
405
  finalized_functions[method_name] = finalized_function
374
406
  return finalized_functions
375
407
 
376
- @contextmanager
377
- def lifecycle_presnapshot(
378
- self,
379
- event_loop: UserCodeEventLoop,
380
- container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
381
- ):
382
- # Identify all "enter" methods that need to run before we snapshot.
383
- if not self.function_def.is_auto_snapshot:
384
- pre_snapshot_methods = _find_callables_for_obj(
385
- self.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
386
- )
387
- call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
388
- yield
389
408
 
390
- @contextmanager
391
- def lifecycle_postsnapshot(
392
- self,
393
- event_loop: UserCodeEventLoop,
394
- container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
395
- ):
396
- flash_entry = _FlashContainerEntry(self.function_def.http_config)
397
- # Identify the "enter" methods to run after resuming from a snapshot.
398
- if not self.function_def.is_auto_snapshot:
399
- post_snapshot_methods = _find_callables_for_obj(
400
- self.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
401
- )
402
- call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
403
- flash_entry.enter()
404
- try:
405
- yield
406
- finally:
407
- if not self.function_def.is_auto_snapshot:
408
- flash_entry.stop()
409
- exit_methods = _find_callables_for_obj(self.user_cls_instance, _PartialFunctionFlags.EXIT)
410
- call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
411
- flash_entry.close()
409
+ @dataclass
410
+ class ImportedServer(_LifecycleManager, Service):
411
+ user_cls_instance: Any
412
+ app: "modal.app._App"
413
+ service_deps: Optional[Sequence["modal._object._Object"]]
414
+ function_def: api_pb2.Function
415
+
416
+ def get_finalized_functions(
417
+ self, fun_def: api_pb2.Function, container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager"
418
+ ) -> dict[str, "FinalizedFunction"]:
419
+ return {}
412
420
 
413
421
 
414
422
  def get_user_class_instance(_cls: modal.cls._Cls, args: tuple[Any, ...], kwargs: dict[str, Any]) -> typing.Any:
@@ -497,29 +505,15 @@ def import_single_function_service(
497
505
  )
498
506
 
499
507
 
500
- def import_class_service(
508
+ def _get_cls_or_user_cls(
501
509
  function_def: api_pb2.Function,
502
- service_function_hydration_data: api_pb2.Object,
503
- class_id: str,
504
- client: "modal.client.Client",
505
510
  ser_user_cls: Optional[type],
506
- cls_args,
507
- cls_kwargs,
508
- ) -> Service:
509
- """
510
- This imports a full class to be able to execute any @method or webhook decorated methods.
511
-
512
- See import_function.
513
- """
514
- active_app: Optional["modal.app._App"]
515
- service_deps: Optional[Sequence["modal._object._Object"]]
516
- cls_or_user_cls: typing.Union[type, modal.cls.Cls]
517
-
511
+ ) -> typing.Union[type, modal.cls.Cls, modal.server.Server]:
518
512
  if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
519
513
  assert ser_user_cls is not None
520
514
  cls_or_user_cls = ser_user_cls
521
515
  else:
522
- # Load the module dynamically
516
+ # Load the module dynamically for non-serialized class.
523
517
  module = importlib.import_module(function_def.module_name)
524
518
  qual_name: str = function_def.function_name
525
519
 
@@ -527,9 +521,8 @@ def import_class_service(
527
521
  raise LocalFunctionError("Attempted to load a class defined in a function scope")
528
522
 
529
523
  parts = qual_name.split(".")
530
- if not (
531
- len(parts) == 2 and parts[1] == "*"
532
- ): # the "function name" of a class service "function placeholder" is expected to be "ClassName.*"
524
+ # Class service functions have pattern "ClassName.*", servers use "ClassName"
525
+ if not (len(parts) == 1 or (len(parts) == 2 and parts[1] == "*")):
533
526
  raise ExecutionError(
534
527
  f"Internal error: Invalid 'service function' identifier {qual_name}. Please contact Modal support"
535
528
  )
@@ -537,6 +530,31 @@ def import_class_service(
537
530
  assert not function_def.use_method_name # new "placeholder methods" should not be invoked directly!
538
531
  cls_name = parts[0]
539
532
  cls_or_user_cls = getattr(module, cls_name)
533
+ return cls_or_user_cls
534
+
535
+
536
+ def import_class_service(
537
+ function_def: api_pb2.Function,
538
+ service_function_hydration_data: api_pb2.Object,
539
+ class_id: str,
540
+ client: "modal.client.Client",
541
+ ser_user_cls: Optional[type],
542
+ cls_args,
543
+ cls_kwargs,
544
+ ) -> Service:
545
+ """
546
+ This imports a full class to be able to execute any @method or webhook decorated methods.
547
+
548
+ See import_function.
549
+ """
550
+ active_app: Optional["modal.app._App"]
551
+ service_deps: Optional[Sequence["modal._object._Object"]]
552
+ cls_or_user_cls: typing.Union[type, modal.cls.Cls]
553
+
554
+ cls_or_user_cls = typing.cast(
555
+ typing.Union[type, modal.cls.Cls],
556
+ _get_cls_or_user_cls(function_def, ser_user_cls),
557
+ )
540
558
 
541
559
  if isinstance(cls_or_user_cls, modal.cls.Cls):
542
560
  _cls = typing.cast(modal.cls._Cls, synchronizer._translate_in(cls_or_user_cls))
@@ -572,6 +590,56 @@ def import_class_service(
572
590
  )
573
591
 
574
592
 
593
+ def import_server_service(
594
+ function_def: api_pb2.Function,
595
+ service_function_hydration_data: api_pb2.Object,
596
+ client: "modal.client.Client",
597
+ ser_user_cls: Optional[type],
598
+ ) -> Service:
599
+ """
600
+ This imports a class as a server to server HTTP requests.
601
+
602
+ See import_function.
603
+ """
604
+ active_app: Optional["modal.app._App"]
605
+ service_deps: Optional[Sequence["modal._object._Object"]]
606
+ cls_or_user_cls: typing.Union[type, modal.server.Server]
607
+
608
+ cls_or_user_cls = typing.cast(
609
+ typing.Union[type, modal.server.Server],
610
+ _get_cls_or_user_cls(function_def, ser_user_cls),
611
+ )
612
+
613
+ if isinstance(cls_or_user_cls, modal.server.Server):
614
+ _server = typing.cast(modal._server._Server, synchronizer._translate_in(cls_or_user_cls))
615
+ server_service_function: _Function = _server._get_service_function()
616
+ service_deps = server_service_function.deps(only_explicit_mounts=True)
617
+ active_app = _server._get_app()
618
+
619
+ else:
620
+ # Undecorated user class (serialized or local scope-decoration).
621
+ service_deps = None # we can't infer service deps for now
622
+ active_app = get_active_app_fallback(function_def)
623
+ _client = typing.cast("modal.client._Client", synchronizer._translate_in(client))
624
+ _service_function: modal._functions._Function[Any, Any, Any] = modal._functions._Function._new_hydrated(
625
+ service_function_hydration_data.object_id,
626
+ _client,
627
+ service_function_hydration_data.function_handle_metadata,
628
+ is_another_app=True, # this skips re-loading the function, which is required since it doesn't have a loader
629
+ )
630
+
631
+ _server = modal._server._Server._from_local(cls_or_user_cls, active_app, _service_function)
632
+
633
+ user_cls = _server._get_user_cls()
634
+ # Create server object with lifecycle methods registered.
635
+ return ImportedServer(
636
+ user_cls_instance=user_cls(),
637
+ app=active_app,
638
+ service_deps=service_deps,
639
+ function_def=function_def,
640
+ )
641
+
642
+
575
643
  def get_active_app_fallback(function_def: api_pb2.Function) -> _App:
576
644
  # This branch is reached in the special case that the imported function/class is:
577
645
  # 1) not serialized, and
@@ -41,10 +41,10 @@ class _Server:
41
41
  See [lifecycle hooks](https://modal.com/docs/guide/lifecycle-functions) for more information.
42
42
 
43
43
  Generally, you will not construct a Server directly.
44
- Instead, use the [`@app.server()`](https://modal.com/docs/reference/modal.App#server) decorator.
44
+ Instead, use the [`@app._experimental_server()`](https://modal.com/docs/reference/modal.App#server) decorator.
45
45
 
46
46
  ```python notest
47
- @app.server(port=8000, proxy_regions=["us-east", "us-west"])
47
+ @app._experimental_server(port=8000, proxy_regions=["us-east", "us-west"])
48
48
  class MyServer:
49
49
  @modal.enter()
50
50
  def start_server(self):
@@ -78,8 +78,14 @@ class _Server:
78
78
  # ============ Live Methods ============
79
79
 
80
80
  @live_method
81
- async def get_urls(self) -> Optional[list[str]]:
82
- return await self._get_service_function()._experimental_get_flash_urls()
81
+ async def get_urls(self) -> Optional[dict[str, str]]:
82
+ def _extract_region_from_url(url: str) -> str:
83
+ return url.split(".")[-3].removeprefix("modal-")
84
+
85
+ return {
86
+ _extract_region_from_url(url): url
87
+ for url in await self._get_service_function()._experimental_get_flash_urls()
88
+ }
83
89
 
84
90
  @live_method
85
91
  async def update_autoscaler(
@@ -129,7 +135,7 @@ class _Server:
129
135
  ) -> "_Server":
130
136
  """Create a Server from a local class definition."""
131
137
 
132
- # Note: Validation should be done by the caller (app.server()) BEFORE creating the Server.
138
+ # Note: Validation should be done by the caller (app._experimental_server()) BEFORE creating the Server.
133
139
  # Extract the underlying class if wrapped in a _PartialFunction (e.g., from @modal.clustered())
134
140
  user_cls = _Server._extract_user_cls(wrapped_user_cls)
135
141
 
@@ -182,7 +188,7 @@ class _Server:
182
188
  user_cls = _Server._extract_user_cls(wrapped_user_cls)
183
189
 
184
190
  if not inspect.isclass(user_cls):
185
- raise TypeError("The @app.server() decorator must be used on a class.")
191
+ raise TypeError("The @app._experimental_server() decorator must be used on a class.")
186
192
 
187
193
  # Check for modal.parameter() - not allowed on server classes
188
194
  params = {k: v for k, v in user_cls.__dict__.items() if is_parameter(v)}
@@ -160,7 +160,6 @@ class FunctionInfo:
160
160
  ):
161
161
  self.raw_f = f
162
162
  self.user_cls = user_cls
163
-
164
163
  if f is None and user_cls:
165
164
  # "service function" for running all methods of a class
166
165
  self.implementation_name = f"{user_cls.__name__}.*"
@@ -264,8 +263,7 @@ class FunctionInfo:
264
263
  LOAD_ATTR = opcode.opmap["LOAD_ATTR"]
265
264
  STORE_ATTR = opcode.opmap["STORE_ATTR"]
266
265
 
267
- func = self.raw_f
268
- code = func.__code__
266
+ code = self.raw_f.__code__
269
267
  f_attr_ops = set()
270
268
  for instr in dis.get_instructions(code):
271
269
  if instr.opcode == LOAD_ATTR:
@@ -663,10 +661,11 @@ class FunctionCreationStatus:
663
661
  )
664
662
 
665
663
  elif self.response.function.flash_service_urls:
664
+ self.status_row.finish(f"Created function {self.tag}.")
666
665
  for flash_service_url in self.response.function.flash_service_urls:
667
666
  flash_service_url_status_row = self.resolver.add_status_row()
668
667
  flash_service_url_status_row.finish(
669
- f"Created flash service endpoint for {self.tag} => "
668
+ f"Created server endpoints for {self.tag} => "
670
669
  f"[magenta underline]{flash_service_url}[/magenta underline]"
671
670
  )
672
671
 
@@ -30,12 +30,18 @@ from ._partial_function import (
30
30
  _PartialFunctionFlags,
31
31
  verify_concurrent_params,
32
32
  )
33
+ from ._server import _Server, validate_http_server_config
33
34
  from ._utils.async_utils import synchronize_api
34
35
  from ._utils.deprecation import (
35
36
  deprecation_warning,
36
37
  warn_on_renamed_autoscaler_settings,
37
38
  )
38
- from ._utils.function_utils import FunctionInfo, is_flash_object, is_global_object, is_method_fn
39
+ from ._utils.function_utils import (
40
+ FunctionInfo,
41
+ is_flash_object,
42
+ is_global_object,
43
+ is_method_fn,
44
+ )
39
45
  from ._utils.mount_utils import validate_volumes
40
46
  from ._utils.name_utils import check_object_name, check_tag_dict
41
47
  from .client import _Client
@@ -1184,6 +1190,155 @@ class _App:
1184
1190
 
1185
1191
  return wrapper
1186
1192
 
1193
+ def _experimental_server(
1194
+ self,
1195
+ _warn_parentheses_missing=None, # mdmd:line-hidden
1196
+ *,
1197
+ image: Optional[_Image] = None, # The image to run as the container for the server
1198
+ env: Optional[dict[str, Optional[str]]] = None, # Environment variables to set in the container
1199
+ secrets: Optional[Collection[_Secret]] = None, # Secrets to inject into the container as environment variables
1200
+ gpu: Union[GPU_T, list[GPU_T]] = None, # GPU request; either a single GPU type or a list of types
1201
+ serialized: bool = False, # Whether to send the server class over using cloudpickle.
1202
+ volumes: dict[
1203
+ Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]
1204
+ ] = {}, # Mount points for Modal Volumes & CloudBucketMounts
1205
+ cpu: Optional[Union[float, tuple[float, float]]] = None, # CPU cores to request
1206
+ memory: Optional[Union[int, tuple[int, int]]] = None, # Memory in MiB to request
1207
+ ephemeral_disk: Optional[int] = None, # Ephemeral disk size in MiB
1208
+ min_containers: Optional[int] = None, # Minimum number of containers to keep warm
1209
+ max_containers: Optional[int] = None, # Maximum number of containers
1210
+ buffer_containers: Optional[int] = None, # Additional idle containers under active load
1211
+ scaledown_window: Optional[int] = None, # Max idle time before scaling down (seconds)
1212
+ proxy: Optional[_Proxy] = None, # Modal Proxy to use in front of this server
1213
+ port: int = 8000, # Port the HTTP server listens on
1214
+ startup_timeout: int = 30, # Maximum startup time in seconds
1215
+ exit_grace_period: int = 0, # Grace period for in-flight requests on shutdown
1216
+ proxy_regions: Optional[Sequence[str]] = ["us-east"], # Required: Regions to deploy proxy endpoints
1217
+ h2_enabled: bool = False, # Enable HTTP/2
1218
+ target_concurrency: Optional[int] = None, # Target concurrency for the server
1219
+ cloud: Optional[str] = None, # Cloud provider (aws, gcp, oci, auto)
1220
+ region: Optional[Union[str, Sequence[str]]] = None, # Region(s) to run on
1221
+ nonpreemptible: bool = False, # Whether to use non-preemptible instances
1222
+ enable_memory_snapshot: bool = False, # Enable memory checkpointing
1223
+ i6pn: Optional[bool] = None, # Enable IPv6 container networking
1224
+ include_source: Optional[bool] = None, # Whether to add source to container
1225
+ # Experimental options
1226
+ experimental_options: Optional[dict[str, Any]] = None,
1227
+ ) -> Callable[[Union[CLS_T, _PartialFunction]], _Server]:
1228
+ """
1229
+ Decorator to register a new Modal Server with this App.
1230
+
1231
+ Servers run HTTP servers that are started in an `@enter` method.
1232
+ Unlike `@app.cls()`, servers only expose HTTP endpoints and do not
1233
+ support `.remote()` method calls.
1234
+
1235
+ Example:
1236
+
1237
+ ```python
1238
+ @app._experimental_server(port=8000, proxy_regions=["us-east"])
1239
+ class MyServer:
1240
+ @modal.enter()
1241
+ def start(self):
1242
+ self.proc = subprocess.Popen(["python3", "-m", "http.server", "8000"])
1243
+
1244
+ @modal.exit()
1245
+ def stop(self):
1246
+ self.proc.terminate()
1247
+ ```
1248
+ """
1249
+ if _warn_parentheses_missing:
1250
+ raise InvalidError("Did you forget parentheses? Suggestion: `@app._experimental_server()`.")
1251
+
1252
+ # Validate HTTP server config
1253
+ validate_http_server_config(
1254
+ port=port,
1255
+ proxy_regions=proxy_regions,
1256
+ startup_timeout=startup_timeout,
1257
+ exit_grace_period=exit_grace_period,
1258
+ )
1259
+
1260
+ if target_concurrency is not None:
1261
+ if not isinstance(target_concurrency, int) or target_concurrency < 1:
1262
+ raise InvalidError("The `target_concurrency` argument must be a positive integer.")
1263
+
1264
+ http_config = api_pb2.HTTPConfig(
1265
+ port=port,
1266
+ proxy_regions=proxy_regions,
1267
+ startup_timeout=startup_timeout,
1268
+ exit_grace_period=exit_grace_period,
1269
+ h2_enabled=h2_enabled,
1270
+ )
1271
+
1272
+ # Build secrets list
1273
+ secrets_list: list[_Secret] = list(secrets) if secrets else []
1274
+ if env:
1275
+ secrets_list.append(_Secret.from_dict(env))
1276
+
1277
+ def wrapper(wrapped_user_cls: Union[CLS_T, _PartialFunction, Callable]) -> _Server:
1278
+ _Server._validate_wrapped_user_cls_decorators(wrapped_user_cls, enable_memory_snapshot)
1279
+
1280
+ # Validate the server class
1281
+ _Server._validate_construction_mechanism(wrapped_user_cls)
1282
+
1283
+ # Extract the underlying class if wrapped in a _PartialFunction (e.g., from @modal.clustered())
1284
+ cluster_size = None
1285
+ rdma = None
1286
+ user_cls = wrapped_user_cls
1287
+
1288
+ if isinstance(wrapped_user_cls, _PartialFunction):
1289
+ user_cls = wrapped_user_cls.user_cls
1290
+ if wrapped_user_cls.flags & _PartialFunctionFlags.CLUSTERED:
1291
+ cluster_size = wrapped_user_cls.params.cluster_size
1292
+ rdma = wrapped_user_cls.params.rdma
1293
+
1294
+ local_state = self._local_state
1295
+
1296
+ # Create the FunctionInfo for the server, note we treat FunctionInfo as a class for servers
1297
+ info = FunctionInfo(None, serialized=serialized, user_cls=user_cls, name_override=user_cls.__name__)
1298
+ # Create the service function
1299
+ service_function = _Function.from_local(
1300
+ info,
1301
+ app=self,
1302
+ image=image or self._get_default_image(),
1303
+ secrets=[*local_state.secrets_default, *secrets_list],
1304
+ gpu=gpu,
1305
+ network_file_systems={}, # Deprecated: No support for Server level network file systems
1306
+ volumes={**local_state.volumes_default, **volumes},
1307
+ cpu=cpu,
1308
+ memory=memory,
1309
+ ephemeral_disk=ephemeral_disk,
1310
+ min_containers=min_containers,
1311
+ max_containers=max_containers,
1312
+ buffer_containers=buffer_containers,
1313
+ scaledown_window=scaledown_window,
1314
+ proxy=proxy,
1315
+ retries=None, # No support for Server level retries
1316
+ max_concurrent_inputs=None, # No support for Server level concurrent inputs
1317
+ target_concurrent_inputs=target_concurrency, # No support for Server level concurrent inputs
1318
+ batch_max_size=None, # No support for Server level batching
1319
+ batch_wait_ms=None, # No support for Server level batching
1320
+ startup_timeout=startup_timeout,
1321
+ cloud=cloud,
1322
+ region=region,
1323
+ nonpreemptible=nonpreemptible,
1324
+ enable_memory_snapshot=enable_memory_snapshot,
1325
+ single_use_containers=False, # No support for single-use server containers
1326
+ http_config=http_config,
1327
+ is_server=True,
1328
+ i6pn_enabled=i6pn or (cluster_size is not None),
1329
+ cluster_size=cluster_size,
1330
+ rdma=rdma,
1331
+ include_source=include_source if include_source is not None else local_state.include_source_default,
1332
+ experimental_options={k: str(v) for k, v in (experimental_options or {}).items()},
1333
+ restrict_output=False,
1334
+ )
1335
+
1336
+ self._add_function(service_function, is_web_endpoint=False)
1337
+ server: _Server = _Server._from_local(wrapped_user_cls, self, service_function)
1338
+ return server
1339
+
1340
+ return wrapper
1341
+
1187
1342
  def include(self, /, other_app: "_App", inherit_tags: bool = True) -> typing_extensions.Self:
1188
1343
  """Include another App's objects in this one.
1189
1344