modal 1.5.6.dev20__tar.gz → 1.5.6.dev22__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 (243) hide show
  1. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/PKG-INFO +1 -1
  2. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/pty.py +2 -2
  3. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/rich.py +16 -4
  4. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/user_code_imports.py +1 -1
  5. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_tunnel.py +0 -1
  6. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/app.py +22 -2
  7. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/app.pyi +8 -0
  8. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/entry_point.py +0 -4
  9. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/client.pyi +2 -2
  10. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/__init__.py +4 -0
  11. modal-1.5.6.dev22/modal/experimental/_flash_container_entry.py +66 -0
  12. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/flash.py +5 -67
  13. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/flash.pyi +0 -30
  14. modal-1.5.6.dev22/modal/experimental/flash_server.py +39 -0
  15. modal-1.5.6.dev22/modal/experimental/flash_server.pyi +38 -0
  16. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/functions.pyi +6 -6
  17. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox.py +6 -1
  18. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/PKG-INFO +1 -1
  19. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/SOURCES.txt +3 -4
  20. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_grpc.py +48 -0
  21. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2.py +1038 -901
  22. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2.pyi +314 -2
  23. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2_grpc.py +99 -0
  24. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/api_pb2_grpc.pyi +30 -0
  25. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/modal_api_grpc.py +3 -0
  26. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_version/__init__.py +1 -1
  27. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/pyproject.toml +0 -2
  28. modal-1.5.6.dev20/modal/cli/launch.py +0 -141
  29. modal-1.5.6.dev20/modal/cli/programs/run_jupyter.py +0 -95
  30. modal-1.5.6.dev20/modal/cli/programs/vscode.py +0 -110
  31. modal-1.5.6.dev20/modal_docs/mdmd/__init__.py +0 -1
  32. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/LICENSE +0 -0
  33. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/README.md +0 -0
  34. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/__init__.py +0 -0
  35. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/__main__.py +0 -0
  36. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_billing.py +0 -0
  37. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_clustered_functions.py +0 -0
  38. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_clustered_functions.pyi +0 -0
  39. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_container_entrypoint.py +0 -0
  40. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_environments.py +0 -0
  41. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_function_variants.py +0 -0
  42. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_functions.py +0 -0
  43. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_grpc_client.py +0 -0
  44. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_image.py +0 -0
  45. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_ipython.py +0 -0
  46. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_load_context.py +0 -0
  47. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_location.py +0 -0
  48. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_logs.py +0 -0
  49. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_logs_manager.py +0 -0
  50. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_logs_manager.pyi +0 -0
  51. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_object.py +0 -0
  52. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/__init__.py +0 -0
  53. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/manager.py +0 -0
  54. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_output/status.py +0 -0
  55. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_partial_function.py +0 -0
  56. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_resolver.py +0 -0
  57. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_resources.py +0 -0
  58. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/__init__.py +0 -0
  59. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/asgi.py +0 -0
  60. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/container_io_manager.py +0 -0
  61. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/container_io_manager.pyi +0 -0
  62. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/execution_context.py +0 -0
  63. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/execution_context.pyi +0 -0
  64. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  65. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/task_lifecycle_manager.py +0 -0
  66. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  67. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/telemetry.py +0 -0
  68. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_runtime/user_code_event_loop.py +0 -0
  69. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_serialization.py +0 -0
  70. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_server.py +0 -0
  71. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_supports_logs.py +0 -0
  72. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_traceback.py +0 -0
  73. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_tunnel.pyi +0 -0
  74. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_type_manager.py +0 -0
  75. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/__init__.py +0 -0
  76. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/app_utils.py +0 -0
  77. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/async_utils.py +0 -0
  78. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/auth_token_manager.py +0 -0
  79. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/blob_utils.py +0 -0
  80. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/browser_utils.py +0 -0
  81. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/bytes_io_segment_payload.py +0 -0
  82. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/curl_utils.py +0 -0
  83. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/deprecation.py +0 -0
  84. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/display_utils.py +0 -0
  85. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/docker_utils.py +0 -0
  86. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/function_utils.py +0 -0
  87. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/git_utils.py +0 -0
  88. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/grpc_testing.py +0 -0
  89. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/grpc_utils.py +0 -0
  90. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/hash_utils.py +0 -0
  91. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/http_utils.py +0 -0
  92. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/idle_countdown.py +0 -0
  93. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/jwt_utils.py +0 -0
  94. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/logger.py +0 -0
  95. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/mount_utils.py +0 -0
  96. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/name_utils.py +0 -0
  97. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/package_utils.py +0 -0
  98. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/pattern_utils.py +0 -0
  99. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/proxy_support.py +0 -0
  100. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/rand_pb_testing.py +0 -0
  101. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/sandbox_fs_utils.py +0 -0
  102. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/shell_utils.py +0 -0
  103. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/task_command_router_client.py +0 -0
  104. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_utils/time_utils.py +0 -0
  105. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/__init__.py +0 -0
  106. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  107. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/cloudpickle.py +0 -0
  108. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/tblib.py +0 -0
  109. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_vendor/version.py +0 -0
  110. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_watcher.py +0 -0
  111. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/_workspace.py +0 -0
  112. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/billing.py +0 -0
  113. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/billing.pyi +0 -0
  114. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2023.12.312.txt +0 -0
  115. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2023.12.txt +0 -0
  116. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2024.04.txt +0 -0
  117. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2024.10.txt +0 -0
  118. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/2025.06.txt +0 -0
  119. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/PREVIEW.txt +0 -0
  120. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/README.md +0 -0
  121. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/builder/base-images.json +0 -0
  122. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/call_graph.py +0 -0
  123. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/__init__.py +0 -0
  124. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_download.py +0 -0
  125. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_help.py +0 -0
  126. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_logs.py +0 -0
  127. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_stats.py +0 -0
  128. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/_traceback.py +0 -0
  129. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/app.py +0 -0
  130. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/billing.py +0 -0
  131. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/changelog.py +0 -0
  132. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/cluster.py +0 -0
  133. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/config.py +0 -0
  134. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/container.py +0 -0
  135. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/curl.py +0 -0
  136. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/dashboard.py +0 -0
  137. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/dict.py +0 -0
  138. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/endpoint.py +0 -0
  139. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/environment.py +0 -0
  140. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/function.py +0 -0
  141. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/image.py +0 -0
  142. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/import_refs.py +0 -0
  143. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/logo.py +0 -0
  144. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/network_file_system.py +0 -0
  145. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/profile.py +0 -0
  146. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/queues.py +0 -0
  147. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/run.py +0 -0
  148. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/secret.py +0 -0
  149. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/selector.py +0 -0
  150. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/server.py +0 -0
  151. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/shell.py +0 -0
  152. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/skills.py +0 -0
  153. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/token.py +0 -0
  154. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/utils.py +0 -0
  155. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/volume.py +0 -0
  156. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cli/workspace.py +0 -0
  157. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/client.py +0 -0
  158. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cloud_bucket_mount.py +0 -0
  159. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cloud_bucket_mount.pyi +0 -0
  160. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cls.py +0 -0
  161. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/cls.pyi +0 -0
  162. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/config.py +0 -0
  163. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/container_process.py +0 -0
  164. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/container_process.pyi +0 -0
  165. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/dict.py +0 -0
  166. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/dict.pyi +0 -0
  167. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/environments.py +0 -0
  168. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/environments.pyi +0 -0
  169. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/exception.py +0 -0
  170. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/experimental/ipython.py +0 -0
  171. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/file_pattern_matcher.py +0 -0
  172. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/functions.py +0 -0
  173. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/image.py +0 -0
  174. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/image.pyi +0 -0
  175. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/io_streams.py +0 -0
  176. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/io_streams.pyi +0 -0
  177. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/mount.py +0 -0
  178. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/mount.pyi +0 -0
  179. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/network_file_system.py +0 -0
  180. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/network_file_system.pyi +0 -0
  181. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/object.py +0 -0
  182. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/object.pyi +0 -0
  183. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/outbound_policy.py +0 -0
  184. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/outbound_policy.pyi +0 -0
  185. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/output.py +0 -0
  186. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/parallel_map.py +0 -0
  187. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/parallel_map.pyi +0 -0
  188. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/partial_function.py +0 -0
  189. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/partial_function.pyi +0 -0
  190. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/proxy.py +0 -0
  191. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/proxy.pyi +0 -0
  192. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/py.typed +0 -0
  193. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/queue.py +0 -0
  194. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/queue.pyi +0 -0
  195. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/retries.py +0 -0
  196. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/runner.py +0 -0
  197. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/runner.pyi +0 -0
  198. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/running_app.py +0 -0
  199. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox.pyi +0 -0
  200. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox_fs.py +0 -0
  201. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/sandbox_fs.pyi +0 -0
  202. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/schedule.py +0 -0
  203. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/scheduler_placement.py +0 -0
  204. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/secret.py +0 -0
  205. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/secret.pyi +0 -0
  206. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/server.py +0 -0
  207. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/server.pyi +0 -0
  208. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/serving.py +0 -0
  209. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/serving.pyi +0 -0
  210. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/skills/modal/SKILL.md +0 -0
  211. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/snapshot.py +0 -0
  212. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/snapshot.pyi +0 -0
  213. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/stream_type.py +0 -0
  214. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/token_flow.py +0 -0
  215. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/token_flow.pyi +0 -0
  216. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/types.py +0 -0
  217. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/volume.py +0 -0
  218. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/volume.pyi +0 -0
  219. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/workspace.py +0 -0
  220. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal/workspace.pyi +0 -0
  221. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/dependency_links.txt +0 -0
  222. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/entry_points.txt +0 -0
  223. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/requires.txt +0 -0
  224. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal.egg-info/top_level.txt +0 -0
  225. {modal-1.5.6.dev20/modal/cli/programs → modal-1.5.6.dev22/modal_docs}/__init__.py +0 -0
  226. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_cli_docs.py +0 -0
  227. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_cli_docs_main.py +0 -0
  228. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_reference_docs.py +0 -0
  229. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/gen_reference_docs_main.py +0 -0
  230. {modal-1.5.6.dev20/modal_docs → modal-1.5.6.dev22/modal_docs/mdmd}/__init__.py +0 -0
  231. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/mdmd/mdmd.py +0 -0
  232. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/mdmd/signatures.py +0 -0
  233. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/mdmd/types.py +0 -0
  234. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_docs/reference_intro.md +0 -0
  235. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/__init__.py +0 -0
  236. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/py.typed +0 -0
  237. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_grpc.py +0 -0
  238. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2.py +0 -0
  239. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2.pyi +0 -0
  240. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  241. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  242. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/modal_version/__main__.py +0 -0
  243. {modal-1.5.6.dev20 → modal-1.5.6.dev22}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.6.dev20
3
+ Version: 1.5.6.dev22
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -128,8 +128,8 @@ async def get_app_logs_loop(
128
128
  async def _put_log(log_batch: api_pb2.TaskLogsBatch, log: api_pb2.TaskLogs):
129
129
  if log.task_state:
130
130
  output_mgr.update_task_state(log_batch.task_id, log.task_state)
131
- if log.task_state == api_pb2.TASK_STATE_WORKER_ASSIGNED:
132
- # Close function's queueing progress bar (if it exists)
131
+ if log_batch.function_id:
132
+ # Any task state the server reports means the task is on a worker, so close queueing progress
133
133
  output_mgr.update_queueing_progress(
134
134
  function_id=log_batch.function_id, completed=1, total=1, description=None
135
135
  )
@@ -14,6 +14,7 @@ import io
14
14
  import platform
15
15
  import re
16
16
  import sys
17
+ import time
17
18
  from collections.abc import Callable, Generator
18
19
  from datetime import timedelta
19
20
  from pathlib import Path
@@ -218,6 +219,9 @@ _FD_COLORS: dict[int, AnsiColor] = {
218
219
  }
219
220
  _ANSI_RESET = "\033[0m"
220
221
 
222
+ # Seconds to wait after a task is scheduled before showing queueing progress again for a Function
223
+ QUEUEING_STRAGGLER_GRACE_SECS = 30.0
224
+
221
225
 
222
226
  class RichOutputManager(OutputManager):
223
227
  """Rich-based implementation of OutputManager.
@@ -230,6 +234,7 @@ class RichOutputManager(OutputManager):
230
234
  _stderr_console: Console
231
235
  _task_states: dict[str, int]
232
236
  _task_progress_items: dict[tuple[str, int], TaskID]
237
+ _function_scheduled_at: dict[str, float]
233
238
  _current_render_group: Group | None
234
239
  _function_progress: Progress | None
235
240
  _function_queueing_progress: Progress | None
@@ -264,6 +269,7 @@ class RichOutputManager(OutputManager):
264
269
  self._stderr_console = _make_console(stderr=True, highlight=True)
265
270
  self._task_states = {}
266
271
  self._task_progress_items = {}
272
+ self._function_scheduled_at = {}
267
273
  self._current_render_group = None
268
274
  self._function_progress = None
269
275
  self._function_queueing_progress = None
@@ -587,6 +593,9 @@ class RichOutputManager(OutputManager):
587
593
  task_description = description or f"'{function_id}' function waiting on worker"
588
594
  task_description = escape(task_description)
589
595
  task_desc = f"[yellow]{task_description}. Time in queue:"
596
+ is_completion = completed == total
597
+ if is_completion:
598
+ self._function_scheduled_at[function_id] = time.monotonic()
590
599
  if task_key in self._task_progress_items:
591
600
  progress_task_id = self._task_progress_items[task_key]
592
601
  try:
@@ -596,14 +605,17 @@ class RichOutputManager(OutputManager):
596
605
  completed=completed,
597
606
  total=total,
598
607
  )
599
- if completed == total:
608
+ if is_completion:
600
609
  del self._task_progress_items[task_key]
601
610
  self._function_queueing_progress_bar.remove_task(progress_task_id)
602
611
  except KeyError:
603
612
  pass
604
- elif completed != total: # Create new bar for queued function
605
- progress_task_id = self._function_queueing_progress_bar.add_task(task_desc, start=True, total=None)
606
- self._task_progress_items[task_key] = progress_task_id
613
+ elif not is_completion:
614
+ scheduled_at = self._function_scheduled_at.get(function_id)
615
+ if scheduled_at is None or (time.monotonic() - scheduled_at) >= QUEUEING_STRAGGLER_GRACE_SECS:
616
+ # Create new bar for queued function
617
+ progress_task_id = self._function_queueing_progress_bar.add_task(task_desc, start=True, total=None)
618
+ self._task_progress_items[task_key] = progress_task_id
607
619
 
608
620
  def _get_line_buffer(self, fd: int, mode: str, callback: Callable[[str], None]) -> LineBufferedOutput:
609
621
  """Get or create a LineBufferedOutput for the given file descriptor and mode.
@@ -29,7 +29,7 @@ from modal._utils.function_utils import (
29
29
  from modal.app import _App
30
30
  from modal.config import logger
31
31
  from modal.exception import ExecutionError, InvalidError
32
- from modal.experimental.flash import _FlashContainerEntry
32
+ from modal.experimental._flash_container_entry import _FlashContainerEntry
33
33
  from modal_proto import api_pb2
34
34
 
35
35
  if typing.TYPE_CHECKING:
@@ -173,7 +173,6 @@ async def _forward(
173
173
  forwarding code in an `@enter` lifecycle method of an @app.cls, to only make a single
174
174
  ssh server and port for each container (and not one for each input to the function).
175
175
  """
176
-
177
176
  if not isinstance(port, int):
178
177
  raise InvalidError(f"The port argument should be an int, not {port!r}")
179
178
  if port < 1 or port > 65535:
@@ -1332,6 +1332,7 @@ class _App:
1332
1332
  memory: int | tuple[int, int] | None = None, # Memory in MiB to request
1333
1333
  ephemeral_disk: int | None = None, # Ephemeral disk size in MiB
1334
1334
  target_concurrency: float | None = None, # Target concurrency for the server; 0 disables autoscaling
1335
+ max_concurrency: int | None = None, # Maximum concurrent requests per container
1335
1336
  min_containers: int | None = None, # Minimum number of containers to keep warm
1336
1337
  max_containers: int | None = None, # Maximum number of containers
1337
1338
  buffer_containers: int | None = None, # Additional idle containers under active load
@@ -1381,6 +1382,9 @@ class _App:
1381
1382
  target_concurrency:
1382
1383
  Target number of concurrent requests per container; 0 disables autoscaling. May be
1383
1384
  fractional, e.g. 1.5 to target three concurrent requests per two containers.
1385
+ max_concurrency:
1386
+ Maximum number of concurrent requests per container. Requests above this limit
1387
+ receive a 503 response. If set to 0 or unset, request concurrency is unlimited.
1384
1388
  min_containers: Minimum number of containers to keep running regardless of demand.
1385
1389
  max_containers: Limit on the number of containers that can be concurrently running.
1386
1390
  buffer_containers: Extra containers to scale up beyond current demand.
@@ -1430,6 +1434,22 @@ class _App:
1430
1434
  if target_concurrency is not None:
1431
1435
  validate_target_concurrency(target_concurrency, "target_concurrency", allow_fractional=True)
1432
1436
 
1437
+ if max_concurrency is not None:
1438
+ if experimental_options is not None and "max_concurrency" in experimental_options:
1439
+ raise InvalidError(
1440
+ "`max_concurrency` cannot be set both as an app.server parameter and an experimental option."
1441
+ )
1442
+ if isinstance(max_concurrency, bool) or not isinstance(max_concurrency, (int, float)):
1443
+ raise InvalidError(
1444
+ f"The `max_concurrency` argument must be a number, not {type(max_concurrency).__name__}."
1445
+ )
1446
+ if not isinstance(max_concurrency, int):
1447
+ raise InvalidError("The `max_concurrency` argument must be an integer.")
1448
+ if max_concurrency < 0:
1449
+ raise InvalidError("The `max_concurrency` argument must be non-negative.")
1450
+ if max_concurrency and target_concurrency is not None and target_concurrency > max_concurrency:
1451
+ raise InvalidError("The `target_concurrency` argument cannot be greater than `max_concurrency`.")
1452
+
1433
1453
  if scaleup_window is not None and scaleup_window <= 0:
1434
1454
  raise InvalidError("`scaleup_window` must be > 0")
1435
1455
 
@@ -1491,8 +1511,8 @@ class _App:
1491
1511
  scaledown_window=scaledown_window,
1492
1512
  proxy=proxy,
1493
1513
  retries=None, # No support for Server level retries
1494
- max_concurrent_inputs=None, # No support for Server level concurrent inputs
1495
- target_concurrent_inputs=target_concurrency, # No support for Server level concurrent inputs
1514
+ max_concurrent_inputs=max_concurrency,
1515
+ target_concurrent_inputs=target_concurrency,
1496
1516
  batch_max_size=None, # No support for Server level batching
1497
1517
  batch_wait_ms=None, # No support for Server level batching
1498
1518
  startup_timeout=startup_timeout,
@@ -759,6 +759,7 @@ class _App:
759
759
  memory: typing.Union[int, tuple[int, int], None] = None,
760
760
  ephemeral_disk: typing.Optional[int] = None,
761
761
  target_concurrency: typing.Optional[float] = None,
762
+ max_concurrency: typing.Optional[int] = None,
762
763
  min_containers: typing.Optional[int] = None,
763
764
  max_containers: typing.Optional[int] = None,
764
765
  buffer_containers: typing.Optional[int] = None,
@@ -808,6 +809,9 @@ class _App:
808
809
  target_concurrency:
809
810
  Target number of concurrent requests per container; 0 disables autoscaling. May be
810
811
  fractional, e.g. 1.5 to target three concurrent requests per two containers.
812
+ max_concurrency:
813
+ Maximum number of concurrent requests per container. Requests above this limit
814
+ receive a 503 response. If set to 0 or unset, request concurrency is unlimited.
811
815
  min_containers: Minimum number of containers to keep running regardless of demand.
812
816
  max_containers: Limit on the number of containers that can be concurrently running.
813
817
  buffer_containers: Extra containers to scale up beyond current demand.
@@ -1808,6 +1812,7 @@ class App:
1808
1812
  memory: typing.Union[int, tuple[int, int], None] = None,
1809
1813
  ephemeral_disk: typing.Optional[int] = None,
1810
1814
  target_concurrency: typing.Optional[float] = None,
1815
+ max_concurrency: typing.Optional[int] = None,
1811
1816
  min_containers: typing.Optional[int] = None,
1812
1817
  max_containers: typing.Optional[int] = None,
1813
1818
  buffer_containers: typing.Optional[int] = None,
@@ -1857,6 +1862,9 @@ class App:
1857
1862
  target_concurrency:
1858
1863
  Target number of concurrent requests per container; 0 disables autoscaling. May be
1859
1864
  fractional, e.g. 1.5 to target three concurrent requests per two containers.
1865
+ max_concurrency:
1866
+ Maximum number of concurrent requests per container. Requests above this limit
1867
+ receive a 503 response. If set to 0 or unset, request concurrency is unlimited.
1860
1868
  min_containers: Minimum number of containers to keep running regardless of demand.
1861
1869
  max_containers: Limit on the number of containers that can be concurrently running.
1862
1870
  buffer_containers: Extra containers to scale up beyond current demand.
@@ -22,7 +22,6 @@ from .endpoint import endpoint_cli
22
22
  from .environment import environment_cli
23
23
  from .function import function_cli
24
24
  from .image import image_cli
25
- from .launch import launch_cli
26
25
  from .logo import print_logo
27
26
  from .network_file_system import nfs_cli
28
27
  from .profile import profile_cli
@@ -112,9 +111,6 @@ entrypoint_cli.add_command(run.deploy, "deploy", panel="Commands")
112
111
  entrypoint_cli.add_command(run.serve, "serve", panel="Commands")
113
112
  entrypoint_cli.add_command(shell_module.shell, "shell", panel="Commands")
114
113
  entrypoint_cli.add_command(run.run, "run", panel="Commands")
115
- # launch is hidden as it's experimental and we're tracking towards removing it
116
- entrypoint_cli.add_command(launch_cli, hidden=True)
117
-
118
114
  entrypoint_cli.add_command(app_cli, panel="Deployments")
119
115
  entrypoint_cli.add_command(container_cli, panel="Deployments")
120
116
  entrypoint_cli.add_command(endpoint_cli, panel="Deployments")
@@ -77,7 +77,7 @@ class _Client:
77
77
  server_url: str,
78
78
  client_type: int,
79
79
  credentials: typing.Optional[tuple[str, str]],
80
- version: str = "1.5.6.dev20",
80
+ version: str = "1.5.6.dev22",
81
81
  *,
82
82
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
83
83
  ):
@@ -277,7 +277,7 @@ class Client:
277
277
  server_url: str,
278
278
  client_type: int,
279
279
  credentials: typing.Optional[tuple[str, str]],
280
- version: str = "1.5.6.dev20",
280
+ version: str = "1.5.6.dev22",
281
281
  *,
282
282
  oauth_credentials: typing.Optional[_OAuthCredentials] = None,
283
283
  ):
@@ -30,6 +30,10 @@ from .flash import ( # noqa: F401
30
30
  flash_get_containers,
31
31
  http_server,
32
32
  )
33
+ from .flash_server import ( # noqa: F401
34
+ ServerManager,
35
+ server_forward,
36
+ )
33
37
 
34
38
 
35
39
  @synchronizer.create_blocking
@@ -0,0 +1,66 @@
1
+ # Copyright Modal Labs 2025
2
+
3
+ from typing import Protocol
4
+
5
+ from modal._clustered_functions import get_cluster_info
6
+ from modal_proto import api_pb2
7
+
8
+ from .._runtime.task_lifecycle_manager import UserException
9
+ from ..config import logger
10
+ from ..exception import InvalidError
11
+ from ..experimental.flash_server import server_forward
12
+ from .flash import flash_forward
13
+
14
+
15
+ class _FlashLifecycleManager(Protocol):
16
+ def stop(self) -> None: ...
17
+
18
+ def close(self) -> None: ...
19
+
20
+
21
+ class _FlashContainerEntry:
22
+ """
23
+ A class that manages the lifecycle of Flash manager for Flash containers.
24
+
25
+ It is intentional that stop() runs before exit handlers and close(): new requests stop arriving
26
+ before exit handlers run, and in-flight requests keep working until the exit grace period has
27
+ elapsed. For server containers both calls are no-ops.
28
+ """
29
+
30
+ flash_manager: _FlashLifecycleManager | None
31
+
32
+ def __init__(self, http_config: api_pb2.HTTPConfig, is_server: bool = False):
33
+ self.http_config: api_pb2.HTTPConfig = http_config
34
+ self.flash_manager = None
35
+ self.is_server = is_server
36
+
37
+ def enter(self):
38
+ if self.http_config != api_pb2.HTTPConfig():
39
+ try:
40
+ if self.is_server:
41
+ self.flash_manager = server_forward()
42
+ else:
43
+ try:
44
+ rank = get_cluster_info().rank
45
+ if rank != 0:
46
+ return
47
+ except InvalidError:
48
+ pass
49
+
50
+ self.flash_manager = flash_forward(
51
+ self.http_config.port,
52
+ startup_timeout=self.http_config.startup_timeout,
53
+ exit_grace_period=self.http_config.exit_grace_period,
54
+ h2_enabled=self.http_config.h2_enabled,
55
+ )
56
+ except Exception as e:
57
+ logger.warning(f"[Modal Flash] Startup failed: {e}")
58
+ raise UserException()
59
+
60
+ def stop(self):
61
+ if self.flash_manager:
62
+ self.flash_manager.stop()
63
+
64
+ def close(self):
65
+ if self.flash_manager:
66
+ self.flash_manager.close()
@@ -8,12 +8,10 @@ from collections.abc import Callable
8
8
  from typing import Any
9
9
  from urllib.parse import urlparse
10
10
 
11
- from modal._clustered_functions import get_cluster_info
12
11
  from modal._partial_function import _PartialFunctionFlags
13
12
  from modal.cls import _Cls
14
13
  from modal_proto import api_pb2
15
14
 
16
- from .._runtime.task_lifecycle_manager import UserException
17
15
  from .._server import validate_http_server_config
18
16
  from .._tunnel import _forward as _forward_tunnel
19
17
  from .._utils.async_utils import synchronize_api, synchronizer
@@ -35,17 +33,15 @@ class _FlashManager:
35
33
  startup_timeout: int = 30,
36
34
  exit_grace_period: int = 0,
37
35
  h2_enabled: bool = False,
38
- is_server: bool = False,
39
36
  ):
40
37
  self.client = client
41
38
  self.port = port
42
39
  self.process = process
40
+ self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
43
41
  # Health check is not currently being used
44
42
  self.health_check_url = health_check_url
45
43
  self.startup_timeout = startup_timeout
46
44
  self.exit_grace_period = exit_grace_period
47
- self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
48
- self.is_server = is_server
49
45
  self.stopped = False
50
46
  self.num_heartbeat_failures = 0
51
47
  self.task_id = os.environ["MODAL_TASK_ID"]
@@ -85,16 +81,8 @@ class _FlashManager:
85
81
  host = parsed_url.hostname
86
82
  assert host is not None, f"Tunnel URL has no host: {self.tunnel.url}"
87
83
  port = parsed_url.port or 443
88
-
89
- if self.is_server:
90
- await self._start_server_tunnel()
91
- return
92
84
  await self._start_flash_registration(host, port)
93
85
 
94
- async def _start_server_tunnel(self) -> None:
95
- # Worker-side HTTP relay owns Flash registration and drain for server tasks.
96
- logger.warning(f"[Modal Flash] Server tunnel opened at {self.tunnel.url}.")
97
-
98
86
  async def _start_flash_registration(self, host: str, port: int) -> None:
99
87
  try:
100
88
  await self._wait_for_port_success(host, port)
@@ -231,12 +219,9 @@ class _FlashManager:
231
219
  async def close(self):
232
220
  if not self.stopped:
233
221
  await self.stop()
234
-
235
- # Server tasks drain via the worker-side HTTP relay, so skip the
236
- # Python-side sleep here to avoid double-counting the grace period.
237
- if not self.is_server:
238
- await asyncio.sleep(self.exit_grace_period)
239
-
222
+ # The container is already deregistered at this point, so waiting out the grace period
223
+ # gives in-flight requests time to finish before the tunnel goes away.
224
+ await asyncio.sleep(self.exit_grace_period)
240
225
  logger.warning(f"[Modal Flash] Closing tunnel on {self.tunnel.url}.")
241
226
  await self.tunnel_manager.__aexit__(*sys.exc_info())
242
227
 
@@ -252,7 +237,6 @@ async def flash_forward(
252
237
  startup_timeout: int = 30,
253
238
  exit_grace_period: int = 0,
254
239
  h2_enabled: bool = False,
255
- is_server: bool = False,
256
240
  ) -> _FlashManager:
257
241
  """
258
242
  Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
@@ -269,7 +253,6 @@ async def flash_forward(
269
253
  startup_timeout=startup_timeout,
270
254
  exit_grace_period=exit_grace_period,
271
255
  h2_enabled=h2_enabled,
272
- is_server=is_server,
273
256
  )
274
257
  await manager._start()
275
258
  return manager
@@ -314,7 +297,7 @@ def _http_server(
314
297
  raise InvalidError(
315
298
  "Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.http_server()`."
316
299
  )
317
- validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period, is_server=False)
300
+ validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period)
318
301
 
319
302
  from modal._partial_function import _PartialFunction, _PartialFunctionParams
320
303
 
@@ -342,48 +325,3 @@ def _http_server(
342
325
 
343
326
 
344
327
  http_server = synchronize_api(_http_server, target_module=__name__)
345
-
346
-
347
- class _FlashContainerEntry:
348
- """
349
- A class that manages the lifecycle of Flash manager for Flash containers.
350
-
351
- It is intentional that stop() runs before exit handlers and close().
352
- This ensures the container is deregistered first, preventing new requests from being routed to it
353
- while exit handlers execute and the exit grace period elapses, before finally closing the tunnel.
354
- """
355
-
356
- flash_manager: FlashManager | None # type: ignore
357
-
358
- def __init__(self, http_config: api_pb2.HTTPConfig, is_server: bool = False):
359
- self.http_config: api_pb2.HTTPConfig = http_config
360
- self.flash_manager = None
361
- self.is_server = is_server
362
-
363
- def enter(self):
364
- if self.http_config != api_pb2.HTTPConfig():
365
- try:
366
- rank = get_cluster_info().rank
367
- if rank != 0:
368
- return
369
- except InvalidError:
370
- pass
371
- try:
372
- self.flash_manager = flash_forward(
373
- self.http_config.port,
374
- startup_timeout=self.http_config.startup_timeout,
375
- exit_grace_period=self.http_config.exit_grace_period,
376
- h2_enabled=self.http_config.h2_enabled,
377
- is_server=self.is_server,
378
- )
379
- except Exception as e:
380
- logger.warning(f"[Modal Flash] Startup failed: {e}")
381
- raise UserException()
382
-
383
- def stop(self):
384
- if self.flash_manager:
385
- self.flash_manager.stop()
386
-
387
- def close(self):
388
- if self.flash_manager:
389
- self.flash_manager.close()
@@ -1,5 +1,4 @@
1
1
  import modal.client
2
- import modal_proto.api_pb2
3
2
  import subprocess
4
3
  import typing
5
4
  import typing_extensions
@@ -14,7 +13,6 @@ class _FlashManager:
14
13
  startup_timeout: int = 30,
15
14
  exit_grace_period: int = 0,
16
15
  h2_enabled: bool = False,
17
- is_server: bool = False,
18
16
  ):
19
17
  """Initialize self. See help(type(self)) for accurate signature."""
20
18
  ...
@@ -23,7 +21,6 @@ class _FlashManager:
23
21
  self, process: typing.Optional[subprocess.Popen], timeout: float = 0.5
24
22
  ) -> tuple[bool, typing.Optional[Exception]]: ...
25
23
  async def _start(self): ...
26
- async def _start_server_tunnel(self) -> None: ...
27
24
  async def _start_flash_registration(self, host: str, port: int) -> None: ...
28
25
  async def _deregister(self): ...
29
26
  async def _drain_container(self):
@@ -46,7 +43,6 @@ class FlashManager:
46
43
  startup_timeout: int = 30,
47
44
  exit_grace_period: int = 0,
48
45
  h2_enabled: bool = False,
49
- is_server: bool = False,
50
46
  ): ...
51
47
 
52
48
  class __is_port_connection_healthy_spec(typing_extensions.Protocol):
@@ -65,12 +61,6 @@ class FlashManager:
65
61
 
66
62
  _start: ___start_spec
67
63
 
68
- class ___start_server_tunnel_spec(typing_extensions.Protocol):
69
- def __call__(self, /) -> None: ...
70
- async def aio(self, /) -> None: ...
71
-
72
- _start_server_tunnel: ___start_server_tunnel_spec
73
-
74
64
  class ___start_flash_registration_spec(typing_extensions.Protocol):
75
65
  def __call__(self, /, host: str, port: int) -> None: ...
76
66
  async def aio(self, /, host: str, port: int) -> None: ...
@@ -130,7 +120,6 @@ class __flash_forward_spec(typing_extensions.Protocol):
130
120
  startup_timeout: int = 30,
131
121
  exit_grace_period: int = 0,
132
122
  h2_enabled: bool = False,
133
- is_server: bool = False,
134
123
  ) -> FlashManager:
135
124
  """Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
136
125
  This is a highly experimental method that can break or be removed at any time without warning.
@@ -147,7 +136,6 @@ class __flash_forward_spec(typing_extensions.Protocol):
147
136
  startup_timeout: int = 30,
148
137
  exit_grace_period: int = 0,
149
138
  h2_enabled: bool = False,
150
- is_server: bool = False,
151
139
  ) -> FlashManager:
152
140
  """Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
153
141
  This is a highly experimental method that can break or be removed at any time without warning.
@@ -215,21 +203,3 @@ def http_server(
215
203
  exit_grace_period: The time to wait for the HTTP server to exit gracefully.
216
204
  """
217
205
  ...
218
-
219
- class _FlashContainerEntry:
220
- """A class that manages the lifecycle of Flash manager for Flash containers.
221
-
222
- It is intentional that stop() runs before exit handlers and close().
223
- This ensures the container is deregistered first, preventing new requests from being routed to it
224
- while exit handlers execute and the exit grace period elapses, before finally closing the tunnel.
225
- """
226
-
227
- flash_manager: typing.Optional[FlashManager]
228
-
229
- def __init__(self, http_config: modal_proto.api_pb2.HTTPConfig, is_server: bool = False):
230
- """Initialize self. See help(type(self)) for accurate signature."""
231
- ...
232
-
233
- def enter(self): ...
234
- def stop(self): ...
235
- def close(self): ...
@@ -0,0 +1,39 @@
1
+ # Copyright Modal Labs 2025
2
+ import os
3
+
4
+ from modal_proto import api_pb2
5
+
6
+ from .._utils.async_utils import synchronize_api, synchronizer
7
+ from ..client import _Client
8
+ from ..config import logger
9
+
10
+
11
+ class _ServerManager:
12
+ def __init__(
13
+ self,
14
+ client: _Client,
15
+ ):
16
+ self.client = client
17
+ self.task_id = os.environ["MODAL_TASK_ID"]
18
+
19
+ async def _start(self):
20
+ await self.client.stub.ContainerServerLifecycleReady(api_pb2.ContainerServerLifecycleReadyRequest())
21
+ logger.warning(f"[Modal Server] Ready for task {self.task_id}.")
22
+
23
+ async def stop(self):
24
+ pass
25
+
26
+ async def close(self):
27
+ logger.warning(f"[Modal Server] Drained for task {self.task_id}.")
28
+
29
+
30
+ ServerManager = synchronize_api(_ServerManager, target_module=__name__)
31
+
32
+
33
+ @synchronizer.create_blocking
34
+ async def server_forward() -> _ServerManager:
35
+ client = await _Client.from_env()
36
+
37
+ manager = _ServerManager(client)
38
+ await manager._start()
39
+ return manager
@@ -0,0 +1,38 @@
1
+ import modal.client
2
+ import typing_extensions
3
+
4
+ class _ServerManager:
5
+ def __init__(self, client: modal.client._Client):
6
+ """Initialize self. See help(type(self)) for accurate signature."""
7
+ ...
8
+
9
+ async def _start(self): ...
10
+ async def stop(self): ...
11
+ async def close(self): ...
12
+
13
+ class ServerManager:
14
+ def __init__(self, client: modal.client.Client): ...
15
+
16
+ class ___start_spec(typing_extensions.Protocol):
17
+ def __call__(self, /): ...
18
+ async def aio(self, /): ...
19
+
20
+ _start: ___start_spec
21
+
22
+ class __stop_spec(typing_extensions.Protocol):
23
+ def __call__(self, /): ...
24
+ async def aio(self, /): ...
25
+
26
+ stop: __stop_spec
27
+
28
+ class __close_spec(typing_extensions.Protocol):
29
+ def __call__(self, /): ...
30
+ async def aio(self, /): ...
31
+
32
+ close: __close_spec
33
+
34
+ class __server_forward_spec(typing_extensions.Protocol):
35
+ def __call__(self, /) -> ServerManager: ...
36
+ async def aio(self, /) -> ServerManager: ...
37
+
38
+ server_forward: __server_forward_spec
@@ -653,7 +653,7 @@ class Function(
653
653
 
654
654
  _call_generator: ___call_generator_spec
655
655
 
656
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
656
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
657
657
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
658
658
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
659
659
 
@@ -678,7 +678,7 @@ class Function(
678
678
  """
679
679
  ...
680
680
 
681
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
681
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
682
682
 
683
683
  class __remote_gen_spec(typing_extensions.Protocol):
684
684
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -728,7 +728,7 @@ class Function(
728
728
  """
729
729
  ...
730
730
 
731
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
731
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
732
732
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
733
733
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
734
734
 
@@ -761,7 +761,7 @@ class Function(
761
761
  """
762
762
  ...
763
763
 
764
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
764
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
765
765
 
766
766
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
767
767
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -769,7 +769,7 @@ class Function(
769
769
 
770
770
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
771
771
 
772
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
772
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
773
773
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
774
774
  """Calls the function with the given arguments, without waiting for the results.
775
775
 
@@ -802,7 +802,7 @@ class Function(
802
802
  """
803
803
  ...
804
804
 
805
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
805
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
806
806
 
807
807
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
808
808
  """Return the inner Python object wrapped by this Modal Function.