modal 1.1.4.dev29__tar.gz → 1.1.4.dev33__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 (190) hide show
  1. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/PKG-INFO +1 -1
  2. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/client.pyi +2 -2
  3. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/flash.py +81 -8
  4. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/flash.pyi +51 -6
  5. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/sandbox.py +16 -0
  6. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/sandbox.pyi +1 -0
  7. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/PKG-INFO +1 -1
  8. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_version/__init__.py +1 -1
  9. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/LICENSE +0 -0
  10. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/README.md +0 -0
  11. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/__init__.py +0 -0
  12. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/__main__.py +0 -0
  13. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_clustered_functions.py +0 -0
  14. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_clustered_functions.pyi +0 -0
  15. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_container_entrypoint.py +0 -0
  16. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_functions.py +0 -0
  17. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_ipython.py +0 -0
  18. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_location.py +0 -0
  19. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_object.py +0 -0
  20. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_output.py +0 -0
  21. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_partial_function.py +0 -0
  22. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_pty.py +0 -0
  23. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_resolver.py +0 -0
  24. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_resources.py +0 -0
  25. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/__init__.py +0 -0
  26. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/asgi.py +0 -0
  27. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/container_io_manager.py +0 -0
  28. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/container_io_manager.pyi +0 -0
  29. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/execution_context.py +0 -0
  30. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/execution_context.pyi +0 -0
  31. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  32. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/telemetry.py +0 -0
  33. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/user_code_imports.py +0 -0
  34. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_serialization.py +0 -0
  35. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_traceback.py +0 -0
  36. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_tunnel.py +0 -0
  37. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_tunnel.pyi +0 -0
  38. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_type_manager.py +0 -0
  39. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/__init__.py +0 -0
  40. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/app_utils.py +0 -0
  41. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/async_utils.py +0 -0
  42. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/auth_token_manager.py +0 -0
  43. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/blob_utils.py +0 -0
  44. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/bytes_io_segment_payload.py +0 -0
  45. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/deprecation.py +0 -0
  46. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/docker_utils.py +0 -0
  47. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/function_utils.py +0 -0
  48. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/git_utils.py +0 -0
  49. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/grpc_testing.py +0 -0
  50. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/grpc_utils.py +0 -0
  51. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/hash_utils.py +0 -0
  52. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/http_utils.py +0 -0
  53. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/jwt_utils.py +0 -0
  54. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/logger.py +0 -0
  55. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/mount_utils.py +0 -0
  56. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/name_utils.py +0 -0
  57. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/package_utils.py +0 -0
  58. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/pattern_utils.py +0 -0
  59. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/rand_pb_testing.py +0 -0
  60. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/shell_utils.py +0 -0
  61. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/time_utils.py +0 -0
  62. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/__init__.py +0 -0
  63. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  64. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/cloudpickle.py +0 -0
  65. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/tblib.py +0 -0
  66. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_watcher.py +0 -0
  67. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/app.py +0 -0
  68. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/app.pyi +0 -0
  69. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2023.12.312.txt +0 -0
  70. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2023.12.txt +0 -0
  71. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2024.04.txt +0 -0
  72. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2024.10.txt +0 -0
  73. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2025.06.txt +0 -0
  74. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/PREVIEW.txt +0 -0
  75. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/README.md +0 -0
  76. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/base-images.json +0 -0
  77. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/call_graph.py +0 -0
  78. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/__init__.py +0 -0
  79. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/_download.py +0 -0
  80. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/_traceback.py +0 -0
  81. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/app.py +0 -0
  82. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/cluster.py +0 -0
  83. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/config.py +0 -0
  84. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/container.py +0 -0
  85. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/dict.py +0 -0
  86. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/entry_point.py +0 -0
  87. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/environment.py +0 -0
  88. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/import_refs.py +0 -0
  89. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/launch.py +0 -0
  90. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/network_file_system.py +0 -0
  91. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/profile.py +0 -0
  92. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/__init__.py +0 -0
  93. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/launch_instance_ssh.py +0 -0
  94. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/run_jupyter.py +0 -0
  95. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/run_marimo.py +0 -0
  96. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/vscode.py +0 -0
  97. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/queues.py +0 -0
  98. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/run.py +0 -0
  99. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/secret.py +0 -0
  100. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/token.py +0 -0
  101. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/utils.py +0 -0
  102. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/volume.py +0 -0
  103. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/client.py +0 -0
  104. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cloud_bucket_mount.py +0 -0
  105. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cloud_bucket_mount.pyi +0 -0
  106. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cls.py +0 -0
  107. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cls.pyi +0 -0
  108. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/config.py +0 -0
  109. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/container_process.py +0 -0
  110. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/container_process.pyi +0 -0
  111. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/dict.py +0 -0
  112. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/dict.pyi +0 -0
  113. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/environments.py +0 -0
  114. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/environments.pyi +0 -0
  115. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/exception.py +0 -0
  116. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/__init__.py +0 -0
  117. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/ipython.py +0 -0
  118. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/file_io.py +0 -0
  119. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/file_io.pyi +0 -0
  120. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/file_pattern_matcher.py +0 -0
  121. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/functions.py +0 -0
  122. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/functions.pyi +0 -0
  123. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/gpu.py +0 -0
  124. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/image.py +0 -0
  125. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/image.pyi +0 -0
  126. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/io_streams.py +0 -0
  127. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/io_streams.pyi +0 -0
  128. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/mount.py +0 -0
  129. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/mount.pyi +0 -0
  130. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/network_file_system.py +0 -0
  131. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/network_file_system.pyi +0 -0
  132. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/object.py +0 -0
  133. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/object.pyi +0 -0
  134. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/output.py +0 -0
  135. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/parallel_map.py +0 -0
  136. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/parallel_map.pyi +0 -0
  137. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/partial_function.py +0 -0
  138. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/partial_function.pyi +0 -0
  139. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/proxy.py +0 -0
  140. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/proxy.pyi +0 -0
  141. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/py.typed +0 -0
  142. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/queue.py +0 -0
  143. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/queue.pyi +0 -0
  144. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/retries.py +0 -0
  145. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/runner.py +0 -0
  146. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/runner.pyi +0 -0
  147. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/running_app.py +0 -0
  148. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/schedule.py +0 -0
  149. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/scheduler_placement.py +0 -0
  150. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/secret.py +0 -0
  151. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/secret.pyi +0 -0
  152. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/serving.py +0 -0
  153. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/serving.pyi +0 -0
  154. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/snapshot.py +0 -0
  155. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/snapshot.pyi +0 -0
  156. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/stream_type.py +0 -0
  157. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/token_flow.py +0 -0
  158. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/token_flow.pyi +0 -0
  159. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/volume.py +0 -0
  160. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/volume.pyi +0 -0
  161. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/SOURCES.txt +0 -0
  162. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/dependency_links.txt +0 -0
  163. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/entry_points.txt +0 -0
  164. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/requires.txt +0 -0
  165. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/top_level.txt +0 -0
  166. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/__init__.py +0 -0
  167. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/gen_cli_docs.py +0 -0
  168. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/gen_reference_docs.py +0 -0
  169. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/mdmd/__init__.py +0 -0
  170. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/mdmd/mdmd.py +0 -0
  171. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/mdmd/signatures.py +0 -0
  172. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/__init__.py +0 -0
  173. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api.proto +0 -0
  174. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_grpc.py +0 -0
  175. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2.py +0 -0
  176. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2.pyi +0 -0
  177. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2_grpc.py +0 -0
  178. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2_grpc.pyi +0 -0
  179. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/modal_api_grpc.py +0 -0
  180. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/modal_options_grpc.py +0 -0
  181. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options.proto +0 -0
  182. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_grpc.py +0 -0
  183. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2.py +0 -0
  184. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2.pyi +0 -0
  185. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2_grpc.py +0 -0
  186. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2_grpc.pyi +0 -0
  187. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/py.typed +0 -0
  188. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_version/__main__.py +0 -0
  189. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/pyproject.toml +0 -0
  190. {modal-1.1.4.dev29 → modal-1.1.4.dev33}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.4.dev29
3
+ Version: 1.1.4.dev33
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.4.dev29",
36
+ version: str = "1.1.4.dev33",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.4.dev29",
167
+ version: str = "1.1.4.dev33",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
@@ -1,6 +1,8 @@
1
1
  # Copyright Modal Labs 2025
2
2
  import asyncio
3
3
  import math
4
+ import os
5
+ import subprocess
4
6
  import sys
5
7
  import time
6
8
  import traceback
@@ -19,28 +21,87 @@ from ..client import _Client
19
21
  from ..config import logger
20
22
  from ..exception import InvalidError
21
23
 
24
+ MAX_FAILURES = 3
25
+
22
26
 
23
27
  class _FlashManager:
24
- def __init__(self, client: _Client, port: int, health_check_url: Optional[str] = None):
28
+ def __init__(
29
+ self,
30
+ client: _Client,
31
+ port: int,
32
+ process: Optional[subprocess.Popen] = None,
33
+ health_check_url: Optional[str] = None,
34
+ ):
25
35
  self.client = client
26
36
  self.port = port
37
+ # Health check is not currently being used
27
38
  self.health_check_url = health_check_url
39
+ self.process = process
28
40
  self.tunnel_manager = _forward_tunnel(port, client=client)
29
41
  self.stopped = False
42
+ self.num_failures = 0
43
+ self.task_id = os.environ["MODAL_TASK_ID"]
44
+
45
+ async def check_port_connection(self, process: Optional[subprocess.Popen], timeout: int = 10):
46
+ import socket
47
+
48
+ start_time = time.monotonic()
49
+
50
+ while time.monotonic() - start_time < timeout:
51
+ try:
52
+ if process is not None and process.poll() is not None:
53
+ return Exception(f"Process {process.pid} exited with code {process.returncode}")
54
+ with socket.create_connection(("localhost", self.port), timeout=1):
55
+ return
56
+ except (ConnectionRefusedError, OSError):
57
+ await asyncio.sleep(0.1)
58
+
59
+ return Exception(f"Waited too long for port {self.port} to start accepting connections")
30
60
 
31
61
  async def _start(self):
32
62
  self.tunnel = await self.tunnel_manager.__aenter__()
33
-
34
63
  parsed_url = urlparse(self.tunnel.url)
35
64
  host = parsed_url.hostname
36
65
  port = parsed_url.port or 443
37
66
 
38
67
  self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
68
+ self.drain_task = asyncio.create_task(self._drain_container())
69
+
70
+ async def _drain_container(self):
71
+ """
72
+ Background task that checks if we've encountered too many failures and drains the container if so.
73
+ """
74
+ while True:
75
+ try:
76
+ # Check if the container should be drained (e.g., too many failures)
77
+ if self.num_failures > MAX_FAILURES:
78
+ logger.warning(
79
+ f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
80
+ )
81
+ await self.stop()
82
+ # handle close upon container exit
83
+
84
+ if self.task_id:
85
+ await self.client.stub.ContainerStop(api_pb2.ContainerStopRequest(task_id=self.task_id))
86
+ return
87
+ except asyncio.CancelledError:
88
+ logger.warning("[Modal Flash] Shutting down...")
89
+ return
90
+ except Exception as e:
91
+ logger.error(f"[Modal Flash] Error draining container: {e}")
92
+ await asyncio.sleep(1)
93
+
94
+ try:
95
+ await asyncio.sleep(1)
96
+ except asyncio.CancelledError:
97
+ logger.warning("[Modal Flash] Shutting down...")
98
+ return
39
99
 
40
100
  async def _run_heartbeat(self, host: str, port: int):
41
101
  first_registration = True
42
102
  while True:
43
103
  try:
104
+ await self.check_port_connection(process=self.process)
44
105
  resp = await self.client.stub.FlashContainerRegister(
45
106
  api_pb2.FlashContainerRegisterRequest(
46
107
  priority=10,
@@ -50,14 +111,25 @@ class _FlashManager:
50
111
  ),
51
112
  timeout=10,
52
113
  )
114
+ self.num_failures = 0
53
115
  if first_registration:
54
- logger.warning(f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url}")
116
+ logger.warning(
117
+ f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
118
+ )
55
119
  first_registration = False
56
120
  except asyncio.CancelledError:
57
121
  logger.warning("[Modal Flash] Shutting down...")
58
122
  break
59
123
  except Exception as e:
60
124
  logger.error(f"[Modal Flash] Heartbeat failed: {e}")
125
+ self.num_failures += 1
126
+ logger.error(
127
+ f"[Modal Flash] Deregistering container {self.tunnel.url}, num_failures: {self.num_failures}"
128
+ )
129
+ await retry_transient_errors(
130
+ self.client.stub.FlashContainerDeregister,
131
+ api_pb2.FlashContainerDeregisterRequest(),
132
+ )
61
133
 
62
134
  try:
63
135
  await asyncio.sleep(1)
@@ -94,16 +166,17 @@ FlashManager = synchronize_api(_FlashManager)
94
166
 
95
167
 
96
168
  @synchronizer.create_blocking
97
- async def flash_forward(port: int, health_check_url: Optional[str] = None) -> _FlashManager:
169
+ async def flash_forward(
170
+ port: int, process: Optional[subprocess.Popen] = None, health_check_url: Optional[str] = None
171
+ ) -> _FlashManager:
98
172
  """
99
173
  Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
100
-
101
174
  This is a highly experimental method that can break or be removed at any time without warning.
102
175
  Do not use this method unless explicitly instructed to do so by Modal support.
103
176
  """
104
177
  client = await _Client.from_env()
105
178
 
106
- manager = _FlashManager(client, port, health_check_url)
179
+ manager = _FlashManager(client, port, process=process, health_check_url=health_check_url)
107
180
  await manager._start()
108
181
  return manager
109
182
 
@@ -127,6 +200,8 @@ class _FlashPrometheusAutoscaler:
127
200
  scale_down_stabilization_window_seconds: int,
128
201
  autoscaling_interval_seconds: int,
129
202
  ):
203
+ import aiohttp
204
+
130
205
  if scale_up_stabilization_window_seconds > self._max_window_seconds:
131
206
  raise InvalidError(
132
207
  f"scale_up_stabilization_window_seconds must be less than or equal to {self._max_window_seconds}"
@@ -138,8 +213,6 @@ class _FlashPrometheusAutoscaler:
138
213
  if target_metric_value <= 0:
139
214
  raise InvalidError("target_metric_value must be greater than 0")
140
215
 
141
- import aiohttp
142
-
143
216
  self.client = client
144
217
  self.app_name = app_name
145
218
  self.cls_name = cls_name
@@ -1,14 +1,26 @@
1
1
  import modal.client
2
2
  import modal_proto.api_pb2
3
+ import subprocess
3
4
  import typing
4
5
  import typing_extensions
5
6
 
6
7
  class _FlashManager:
7
- def __init__(self, client: modal.client._Client, port: int, health_check_url: typing.Optional[str] = None):
8
+ def __init__(
9
+ self,
10
+ client: modal.client._Client,
11
+ port: int,
12
+ process: typing.Optional[subprocess.Popen] = None,
13
+ health_check_url: typing.Optional[str] = None,
14
+ ):
8
15
  """Initialize self. See help(type(self)) for accurate signature."""
9
16
  ...
10
17
 
18
+ async def check_port_connection(self, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
11
19
  async def _start(self): ...
20
+ async def _drain_container(self):
21
+ """Background task that checks if we've encountered too many failures and drains the container if so."""
22
+ ...
23
+
12
24
  async def _run_heartbeat(self, host: str, port: int): ...
13
25
  def get_container_url(self): ...
14
26
  async def stop(self): ...
@@ -17,7 +29,19 @@ class _FlashManager:
17
29
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
18
30
 
19
31
  class FlashManager:
20
- def __init__(self, client: modal.client.Client, port: int, health_check_url: typing.Optional[str] = None): ...
32
+ def __init__(
33
+ self,
34
+ client: modal.client.Client,
35
+ port: int,
36
+ process: typing.Optional[subprocess.Popen] = None,
37
+ health_check_url: typing.Optional[str] = None,
38
+ ): ...
39
+
40
+ class __check_port_connection_spec(typing_extensions.Protocol[SUPERSELF]):
41
+ def __call__(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
42
+ async def aio(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
43
+
44
+ check_port_connection: __check_port_connection_spec[typing_extensions.Self]
21
45
 
22
46
  class ___start_spec(typing_extensions.Protocol[SUPERSELF]):
23
47
  def __call__(self, /): ...
@@ -25,6 +49,17 @@ class FlashManager:
25
49
 
26
50
  _start: ___start_spec[typing_extensions.Self]
27
51
 
52
+ class ___drain_container_spec(typing_extensions.Protocol[SUPERSELF]):
53
+ def __call__(self, /):
54
+ """Background task that checks if we've encountered too many failures and drains the container if so."""
55
+ ...
56
+
57
+ async def aio(self, /):
58
+ """Background task that checks if we've encountered too many failures and drains the container if so."""
59
+ ...
60
+
61
+ _drain_container: ___drain_container_spec[typing_extensions.Self]
62
+
28
63
  class ___run_heartbeat_spec(typing_extensions.Protocol[SUPERSELF]):
29
64
  def __call__(self, /, host: str, port: int): ...
30
65
  async def aio(self, /, host: str, port: int): ...
@@ -46,17 +81,27 @@ class FlashManager:
46
81
  close: __close_spec[typing_extensions.Self]
47
82
 
48
83
  class __flash_forward_spec(typing_extensions.Protocol):
49
- def __call__(self, /, port: int, health_check_url: typing.Optional[str] = None) -> FlashManager:
84
+ def __call__(
85
+ self,
86
+ /,
87
+ port: int,
88
+ process: typing.Optional[subprocess.Popen] = None,
89
+ health_check_url: typing.Optional[str] = None,
90
+ ) -> FlashManager:
50
91
  """Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
51
-
52
92
  This is a highly experimental method that can break or be removed at any time without warning.
53
93
  Do not use this method unless explicitly instructed to do so by Modal support.
54
94
  """
55
95
  ...
56
96
 
57
- async def aio(self, /, port: int, health_check_url: typing.Optional[str] = None) -> FlashManager:
97
+ async def aio(
98
+ self,
99
+ /,
100
+ port: int,
101
+ process: typing.Optional[subprocess.Popen] = None,
102
+ health_check_url: typing.Optional[str] = None,
103
+ ) -> FlashManager:
58
104
  """Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
59
-
60
105
  This is a highly experimental method that can break or be removed at any time without warning.
61
106
  Do not use this method unless explicitly instructed to do so by Modal support.
62
107
  """
@@ -24,6 +24,7 @@ from ._utils.async_utils import TaskContext, synchronize_api
24
24
  from ._utils.deprecation import deprecation_warning
25
25
  from ._utils.grpc_utils import retry_transient_errors
26
26
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
27
+ from ._utils.name_utils import is_valid_object_name
27
28
  from .client import _Client
28
29
  from .config import config
29
30
  from .container_process import _ContainerProcess
@@ -73,6 +74,16 @@ def _validate_exec_args(args: Sequence[str]) -> None:
73
74
  )
74
75
 
75
76
 
77
+ def _warn_if_invalid_name(name: str) -> None:
78
+ if not is_valid_object_name(name):
79
+ deprecation_warning(
80
+ (2025, 9, 3),
81
+ f"Sandbox name '{name}' will be considered invalid in a future release."
82
+ "\n\nNames may contain only alphanumeric characters, dashes, periods, and underscores,"
83
+ " must be shorter than 64 characters, and cannot conflict with App ID strings.",
84
+ )
85
+
86
+
76
87
  class DefaultSandboxNameOverride(str):
77
88
  """A singleton class that represents the default sandbox name override.
78
89
 
@@ -404,6 +415,8 @@ class _Sandbox(_Object, type_prefix="sb"):
404
415
  from .app import _App
405
416
 
406
417
  _validate_exec_args(args)
418
+ if name is not None:
419
+ _warn_if_invalid_name(name)
407
420
 
408
421
  if block_network and (encrypted_ports or h2_ports or unencrypted_ports):
409
422
  raise InvalidError("Cannot specify open ports when `block_network` is enabled")
@@ -787,6 +800,9 @@ class _Sandbox(_Object, type_prefix="sb"):
787
800
  ):
788
801
  client = client or await _Client.from_env()
789
802
 
803
+ if name is not None and name != _DEFAULT_SANDBOX_NAME_OVERRIDE:
804
+ _warn_if_invalid_name(name)
805
+
790
806
  if name is _DEFAULT_SANDBOX_NAME_OVERRIDE:
791
807
  restore_req = api_pb2.SandboxRestoreRequest(
792
808
  snapshot_id=snapshot.object_id,
@@ -26,6 +26,7 @@ import typing
26
26
  import typing_extensions
27
27
 
28
28
  def _validate_exec_args(args: collections.abc.Sequence[str]) -> None: ...
29
+ def _warn_if_invalid_name(name: str) -> None: ...
29
30
 
30
31
  class DefaultSandboxNameOverride(str):
31
32
  """A singleton class that represents the default sandbox name override.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.4.dev29
3
+ Version: 1.1.4.dev33
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.1.4.dev29"
4
+ __version__ = "1.1.4.dev33"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes