modal 1.3.1.dev35__tar.gz → 1.3.2.dev0__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.dev35 → modal-1.3.2.dev0}/PKG-INFO +1 -1
  2. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_server.py +1 -1
  3. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/task_command_router_client.py +3 -3
  4. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/token.py +2 -0
  5. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/client.py +6 -0
  6. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/client.pyi +12 -2
  7. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cls.py +57 -44
  8. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cls.pyi +1 -0
  9. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/config.py +0 -2
  10. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/flash.py +84 -36
  11. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/flash.pyi +14 -0
  12. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/queue.py +11 -1
  13. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/queue.pyi +33 -3
  14. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/sandbox.py +3 -6
  15. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/PKG-INFO +1 -1
  16. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api.proto +2 -0
  17. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2.py +158 -158
  18. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2.pyi +4 -1
  19. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_version/__init__.py +1 -1
  20. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/LICENSE +0 -0
  21. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/README.md +0 -0
  22. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/__init__.py +0 -0
  23. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/__main__.py +0 -0
  24. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_billing.py +0 -0
  25. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_clustered_functions.py +0 -0
  26. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_clustered_functions.pyi +0 -0
  27. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_container_entrypoint.py +0 -0
  28. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_functions.py +0 -0
  29. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_grpc_client.py +0 -0
  30. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_ipython.py +0 -0
  31. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_load_context.py +0 -0
  32. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_location.py +0 -0
  33. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_object.py +0 -0
  34. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_output.py +0 -0
  35. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_partial_function.py +0 -0
  36. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_pty.py +0 -0
  37. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_resolver.py +0 -0
  38. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_resources.py +0 -0
  39. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/__init__.py +0 -0
  40. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/asgi.py +0 -0
  41. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/container_io_manager.py +0 -0
  42. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  43. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/execution_context.py +0 -0
  44. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/execution_context.pyi +0 -0
  45. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  46. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/telemetry.py +0 -0
  47. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  48. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_runtime/user_code_imports.py +0 -0
  49. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_serialization.py +0 -0
  50. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_traceback.py +0 -0
  51. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_tunnel.py +0 -0
  52. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_tunnel.pyi +0 -0
  53. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_type_manager.py +0 -0
  54. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/__init__.py +0 -0
  55. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/app_utils.py +0 -0
  56. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/async_utils.py +0 -0
  57. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/auth_token_manager.py +0 -0
  58. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/blob_utils.py +0 -0
  59. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  60. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/deprecation.py +0 -0
  61. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/docker_utils.py +0 -0
  62. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/function_utils.py +0 -0
  63. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/git_utils.py +0 -0
  64. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/grpc_testing.py +0 -0
  65. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/grpc_utils.py +0 -0
  66. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/hash_utils.py +0 -0
  67. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/http_utils.py +0 -0
  68. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/jwt_utils.py +0 -0
  69. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/logger.py +0 -0
  70. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/mount_utils.py +0 -0
  71. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/name_utils.py +0 -0
  72. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/package_utils.py +0 -0
  73. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/pattern_utils.py +0 -0
  74. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  75. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/shell_utils.py +0 -0
  76. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_utils/time_utils.py +0 -0
  77. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/__init__.py +0 -0
  78. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  79. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/cloudpickle.py +0 -0
  80. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_vendor/tblib.py +0 -0
  81. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/_watcher.py +0 -0
  82. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/app.py +0 -0
  83. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/app.pyi +0 -0
  84. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/billing.py +0 -0
  85. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2023.12.312.txt +0 -0
  86. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2023.12.txt +0 -0
  87. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2024.04.txt +0 -0
  88. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2024.10.txt +0 -0
  89. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/2025.06.txt +0 -0
  90. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/PREVIEW.txt +0 -0
  91. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/README.md +0 -0
  92. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/builder/base-images.json +0 -0
  93. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/call_graph.py +0 -0
  94. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/__init__.py +0 -0
  95. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/_download.py +0 -0
  96. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/_traceback.py +0 -0
  97. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/app.py +0 -0
  98. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/cluster.py +0 -0
  99. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/config.py +0 -0
  100. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/container.py +0 -0
  101. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/dict.py +0 -0
  102. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/entry_point.py +0 -0
  103. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/environment.py +0 -0
  104. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/import_refs.py +0 -0
  105. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/launch.py +0 -0
  106. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/network_file_system.py +0 -0
  107. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/profile.py +0 -0
  108. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/programs/__init__.py +0 -0
  109. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  110. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/programs/vscode.py +0 -0
  111. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/queues.py +0 -0
  112. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/run.py +0 -0
  113. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/secret.py +0 -0
  114. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/shell.py +0 -0
  115. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/utils.py +0 -0
  116. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cli/volume.py +0 -0
  117. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cloud_bucket_mount.py +0 -0
  118. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  119. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/container_process.py +0 -0
  120. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/container_process.pyi +0 -0
  121. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/dict.py +0 -0
  122. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/dict.pyi +0 -0
  123. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/environments.py +0 -0
  124. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/environments.pyi +0 -0
  125. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/exception.py +0 -0
  126. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/__init__.py +0 -0
  127. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/experimental/ipython.py +0 -0
  128. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/file_io.py +0 -0
  129. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/file_io.pyi +0 -0
  130. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/file_pattern_matcher.py +0 -0
  131. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/functions.py +0 -0
  132. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/functions.pyi +0 -0
  133. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/gpu.py +0 -0
  134. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/image.py +0 -0
  135. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/image.pyi +0 -0
  136. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/io_streams.py +0 -0
  137. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/io_streams.pyi +0 -0
  138. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/mount.py +0 -0
  139. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/mount.pyi +0 -0
  140. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/network_file_system.py +0 -0
  141. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/network_file_system.pyi +0 -0
  142. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/object.py +0 -0
  143. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/object.pyi +0 -0
  144. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/output.py +0 -0
  145. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/parallel_map.py +0 -0
  146. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/parallel_map.pyi +0 -0
  147. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/partial_function.py +0 -0
  148. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/partial_function.pyi +0 -0
  149. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/proxy.py +0 -0
  150. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/proxy.pyi +0 -0
  151. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/py.typed +0 -0
  152. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/retries.py +0 -0
  153. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/runner.py +0 -0
  154. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/runner.pyi +0 -0
  155. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/running_app.py +0 -0
  156. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/sandbox.pyi +3 -3
  157. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/schedule.py +0 -0
  158. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/scheduler_placement.py +0 -0
  159. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/secret.py +0 -0
  160. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/secret.pyi +0 -0
  161. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/server.py +0 -0
  162. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/server.pyi +0 -0
  163. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/serving.py +0 -0
  164. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/serving.pyi +0 -0
  165. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/snapshot.py +0 -0
  166. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/snapshot.pyi +0 -0
  167. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/stream_type.py +0 -0
  168. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/token_flow.py +0 -0
  169. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/token_flow.pyi +0 -0
  170. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/volume.py +0 -0
  171. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal/volume.pyi +0 -0
  172. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/SOURCES.txt +0 -0
  173. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/dependency_links.txt +0 -0
  174. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/entry_points.txt +0 -0
  175. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/requires.txt +0 -0
  176. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal.egg-info/top_level.txt +0 -0
  177. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/__init__.py +0 -0
  178. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/gen_cli_docs.py +0 -0
  179. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/gen_reference_docs.py +0 -0
  180. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/mdmd/__init__.py +0 -0
  181. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  182. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_docs/mdmd/signatures.py +0 -0
  183. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/__init__.py +0 -0
  184. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_grpc.py +0 -0
  185. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2_grpc.py +0 -0
  186. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
  187. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/modal_api_grpc.py +0 -0
  188. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/py.typed +0 -0
  189. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router.proto +0 -0
  190. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  191. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  192. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  193. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  194. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  195. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/modal_version/__main__.py +0 -0
  196. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/pyproject.toml +0 -0
  197. {modal-1.3.1.dev35 → modal-1.3.2.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.1.dev35
3
+ Version: 1.3.2.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -84,7 +84,7 @@ class _Server:
84
84
 
85
85
  return {
86
86
  _extract_region_from_url(url): url
87
- for url in await self._get_service_function()._experimental_get_flash_urls()
87
+ for url in await self._get_service_function()._experimental_get_flash_urls() or []
88
88
  }
89
89
 
90
90
  @live_method
@@ -15,7 +15,7 @@ import grpclib.events
15
15
  from grpclib import GRPCError, Status
16
16
  from grpclib.exceptions import StreamTerminatedError
17
17
 
18
- from modal.config import config, logger
18
+ from modal.config import logger
19
19
  from modal.exception import ConflictError, ExecTimeoutError
20
20
  from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
21
21
  from modal_proto.task_command_router_grpc import TaskCommandRouterStub
@@ -152,8 +152,8 @@ class TaskCommandRouterClient:
152
152
  ssl_context = ssl.create_default_context()
153
153
 
154
154
  # Allow insecure TLS when explicitly enabled via config.
155
- if config["task_command_router_insecure"]:
156
- logger.warning("Using insecure TLS for task command router due to MODAL_TASK_COMMAND_ROUTER_INSECURE")
155
+ if server_client._is_localhost:
156
+ logger.warning("Using insecure TLS for task command router because server client points to localhost")
157
157
  ssl_context.check_hostname = False
158
158
  ssl_context.verify_mode = ssl.CERT_NONE
159
159
 
@@ -87,6 +87,8 @@ async def info():
87
87
  console.print(f"[dim](Using {env_vars_str} environment variable{plural})[/dim]")
88
88
 
89
89
  console.print(f"[bold]Token:[/bold] {resp.token_id}")
90
+ if resp.token_name:
91
+ console.print(f"[bold]Name:[/bold] {resp.token_name}")
90
92
  console.print(f"[bold]Workspace:[/bold] {resp.workspace_name} [dim]({resp.workspace_id})[/dim]")
91
93
 
92
94
  if resp.HasField("user_identity"):
@@ -100,6 +100,12 @@ class _Client:
100
100
  def is_closed(self) -> bool:
101
101
  return self._closed
102
102
 
103
+ @property
104
+ def _is_localhost(self) -> bool:
105
+ """Returns True if the server URL points to localhost."""
106
+ hostname = urllib.parse.urlparse(self.server_url).hostname
107
+ return hostname in {"localhost", "127.0.0.1", "::1", "172.21.0.1"}
108
+
103
109
  @property
104
110
  def stub(self) -> modal_api_grpc.ModalClientModal:
105
111
  """mdmd:hidden
@@ -35,7 +35,7 @@ class _Client:
35
35
  server_url: str,
36
36
  client_type: int,
37
37
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.3.1.dev35",
38
+ version: str = "1.3.2.dev0",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -43,6 +43,11 @@ class _Client:
43
43
  ...
44
44
 
45
45
  def is_closed(self) -> bool: ...
46
+ @property
47
+ def _is_localhost(self) -> bool:
48
+ """Returns True if the server URL points to localhost."""
49
+ ...
50
+
46
51
  @property
47
52
  def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
48
53
  """mdmd:hidden
@@ -166,7 +171,7 @@ class Client:
166
171
  server_url: str,
167
172
  client_type: int,
168
173
  credentials: typing.Optional[tuple[str, str]],
169
- version: str = "1.3.1.dev35",
174
+ version: str = "1.3.2.dev0",
170
175
  ):
171
176
  """mdmd:hidden
172
177
  The Modal client object is not intended to be instantiated directly by users.
@@ -174,6 +179,11 @@ class Client:
174
179
  ...
175
180
 
176
181
  def is_closed(self) -> bool: ...
182
+ @property
183
+ def _is_localhost(self) -> bool:
184
+ """Returns True if the server URL points to localhost."""
185
+ ...
186
+
177
187
  @property
178
188
  def stub(self) -> modal_proto.modal_api_grpc.ModalClientModal:
179
189
  """mdmd:hidden
@@ -99,21 +99,27 @@ class _ServiceOptions:
99
99
  """Implement protobuf-like MergeFrom semantics for this dataclass.
100
100
 
101
101
  This mostly exists to support "stacking" of `.with_options()` calls.
102
+ Returns a new _ServiceOptions instance without modifying self.
102
103
  """
104
+ # Create a shallow copy of self to start with
105
+ merged = dataclasses.replace(self)
106
+
103
107
  # Don't use dataclasses.asdict() because it does a deepcopy(), which chokes on a hydrated object
104
108
  new_options_dict = {k.name: getattr(new_options, k.name) for k in dataclasses.fields(new_options)}
105
109
 
106
110
  # Resources needs special merge handling because individual fields are parameters in the public API
107
111
  merged_resources = api_pb2.Resources()
108
- if self.resources:
109
- merged_resources.MergeFrom(self.resources)
112
+ if merged.resources:
113
+ merged_resources.MergeFrom(merged.resources)
110
114
  if new_resources := new_options_dict.pop("resources"):
111
115
  merged_resources.MergeFrom(new_resources)
112
- self.resources = merged_resources
116
+ merged.resources = merged_resources
113
117
 
114
118
  for key, value in new_options_dict.items():
115
119
  if value: # Only overwrite data when the value was set in the new options
116
- setattr(self, key, value)
120
+ setattr(merged, key, value)
121
+
122
+ return merged
117
123
 
118
124
 
119
125
  def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name: str):
@@ -743,32 +749,6 @@ More information on class parameterization can be found here: https://modal.com/
743
749
  " please use the `.with_concurrency` method instead.",
744
750
  )
745
751
 
746
- async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
747
- # this is a bit confusing, the cls will always have the same metadata
748
- # since it has the same *class* service function (i.e. "template")
749
- # But the (instance) service function for each Obj will be different
750
- # since it will rebind to whatever `_options` have been assigned on
751
- # the particular Cls parent
752
- if not self.is_hydrated:
753
- # this should only happen for Cls.from_name instances
754
- # other classes should already be hydrated!
755
- await resolver.load(self, load_context)
756
-
757
- new_cls._initialize_from_other(self)
758
-
759
- def _deps():
760
- return []
761
-
762
- cls = _Cls._from_loader(
763
- _load_from_base,
764
- rep=f"{self._name}.with_options(...)",
765
- is_another_app=True,
766
- deps=_deps,
767
- load_context_overrides=self._load_context_overrides,
768
- hydrate_lazily=True,
769
- )
770
- cls._initialize_from_other(self)
771
-
772
752
  # Validate volumes
773
753
  validated_volumes = validate_volumes(volumes)
774
754
  cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
@@ -801,8 +781,37 @@ More information on class parameterization can be found here: https://modal.com/
801
781
  target_concurrent_inputs=allow_concurrent_inputs,
802
782
  )
803
783
 
804
- cls._options.merge_options(new_options)
805
- return cls
784
+ combined_options = self._options.merge_options(new_options)
785
+
786
+ async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
787
+ # this is a bit confusing, the cls will always have the same metadata
788
+ # since it has the same *class* service function (i.e. "template")
789
+ # But the (instance) service function for each Obj will be different
790
+ # since it will rebind to whatever `_options` have been assigned on
791
+ # the particular Cls parent
792
+ if not self.is_hydrated:
793
+ # this should only happen for Cls.from_name instances
794
+ # other classes should already be hydrated!
795
+ await resolver.load(self, load_context)
796
+
797
+ new_cls._initialize_from_other(self)
798
+ # Restore the merged options after _initialize_from_other overwrites them
799
+ new_cls._options = combined_options
800
+
801
+ def _deps():
802
+ return []
803
+
804
+ new_cls = _Cls._from_loader(
805
+ _load_from_base,
806
+ rep=f"{self._name}.with_options(...)",
807
+ is_another_app=True,
808
+ deps=_deps,
809
+ load_context_overrides=self._load_context_overrides,
810
+ hydrate_lazily=True,
811
+ )
812
+ new_cls._initialize_from_other(self)
813
+ new_cls._options = combined_options
814
+ return new_cls
806
815
 
807
816
  def with_concurrency(self: "_Cls", *, max_inputs: int, target_inputs: Optional[int] = None) -> "_Cls":
808
817
  """Create an instance of the Cls with input concurrency enabled or overridden with new values.
@@ -815,16 +824,20 @@ More information on class parameterization can be found here: https://modal.com/
815
824
  ModelUsingGPU().generate.remote(42) # will run on an A100 GPU with input concurrency enabled
816
825
  ```
817
826
  """
827
+ concurrency_options = _ServiceOptions(max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs)
828
+ combined_options = self._options.merge_options(concurrency_options)
818
829
 
819
830
  async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
820
831
  if not self.is_hydrated:
821
832
  await resolver.load(self, load_context)
822
833
  new_cls._initialize_from_other(self)
834
+ # Restore the merged options after _initialize_from_other overwrites them
835
+ new_cls._options = combined_options
823
836
 
824
837
  def _deps():
825
838
  return []
826
839
 
827
- cls = _Cls._from_loader(
840
+ new_cls = _Cls._from_loader(
828
841
  _load_from_base,
829
842
  rep=f"{self._name}.with_concurrency(...)",
830
843
  is_another_app=True,
@@ -832,11 +845,9 @@ More information on class parameterization can be found here: https://modal.com/
832
845
  load_context_overrides=self._load_context_overrides,
833
846
  hydrate_lazily=True,
834
847
  )
835
- cls._initialize_from_other(self)
836
-
837
- concurrency_options = _ServiceOptions(max_concurrent_inputs=max_inputs, target_concurrent_inputs=target_inputs)
838
- cls._options.merge_options(concurrency_options)
839
- return cls
848
+ new_cls._initialize_from_other(self)
849
+ new_cls._options = combined_options
850
+ return new_cls
840
851
 
841
852
  def with_batching(self: "_Cls", *, max_batch_size: int, wait_ms: int) -> "_Cls":
842
853
  """Create an instance of the Cls with dynamic batching enabled or overridden with new values.
@@ -849,16 +860,20 @@ More information on class parameterization can be found here: https://modal.com/
849
860
  ModelUsingGPU().generate.remote(42) # will run on an A100 GPU with input concurrency enabled
850
861
  ```
851
862
  """
863
+ batching_options = _ServiceOptions(batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
864
+ combined_options = self._options.merge_options(batching_options)
852
865
 
853
866
  async def _load_from_base(new_cls, resolver, load_context, existing_object_id):
854
867
  if not self.is_hydrated:
855
868
  await resolver.load(self, load_context)
856
869
  new_cls._initialize_from_other(self)
870
+ # Restore the merged options after _initialize_from_other overwrites them
871
+ new_cls._options = combined_options
857
872
 
858
873
  def _deps():
859
874
  return []
860
875
 
861
- cls = _Cls._from_loader(
876
+ new_cls = _Cls._from_loader(
862
877
  _load_from_base,
863
878
  rep=f"{self._name}.with_batching(...)",
864
879
  is_another_app=True,
@@ -866,11 +881,9 @@ More information on class parameterization can be found here: https://modal.com/
866
881
  load_context_overrides=self._load_context_overrides,
867
882
  hydrate_lazily=True,
868
883
  )
869
- cls._initialize_from_other(self)
870
-
871
- batching_options = _ServiceOptions(batch_max_size=max_batch_size, batch_wait_ms=wait_ms)
872
- cls._options.merge_options(batching_options)
873
- return cls
884
+ new_cls._initialize_from_other(self)
885
+ new_cls._options = combined_options
886
+ return new_cls
874
887
 
875
888
  @synchronizer.no_input_translation
876
889
  def __call__(self, *args, **kwargs) -> _Obj:
@@ -47,6 +47,7 @@ class _ServiceOptions:
47
47
  """Implement protobuf-like MergeFrom semantics for this dataclass.
48
48
 
49
49
  This mostly exists to support "stacking" of `.with_options()` calls.
50
+ Returns a new _ServiceOptions instance without modifying self.
50
51
  """
51
52
  ...
52
53
 
@@ -251,8 +251,6 @@ _SETTINGS = {
251
251
  "traceback": _Setting(False, transform=_to_boolean),
252
252
  "image_builder_version": _Setting(),
253
253
  "strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
254
- # Allow insecure TLS for the task command router when running locally (testing/dev only)
255
- "task_command_router_insecure": _Setting(False, transform=_to_boolean),
256
254
  "snapshot_debug": _Setting(False, transform=_to_boolean),
257
255
  "cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
258
256
  "build_validation": _Setting("error", transform=_check_value(["error", "warn", "ignore"])),
@@ -15,6 +15,7 @@ from modal.cls import _Cls
15
15
  from modal.dict import _Dict
16
16
  from modal_proto import api_pb2
17
17
 
18
+ from .._runtime.container_io_manager import UserException
18
19
  from .._server import validate_http_server_config
19
20
  from .._tunnel import _forward as _forward_tunnel
20
21
  from .._utils.async_utils import synchronize_api, synchronizer
@@ -45,14 +46,12 @@ class _FlashManager:
45
46
  self.exit_grace_period = exit_grace_period
46
47
  self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
47
48
  self.stopped = False
48
- self.num_failures = 0
49
+ self.num_heartbeat_failures = 0
49
50
  self.task_id = os.environ["MODAL_TASK_ID"]
50
51
 
51
52
  async def is_port_connection_healthy(
52
53
  self, process: Optional[subprocess.Popen], timeout: float = 0.5
53
54
  ) -> tuple[bool, Optional[Exception]]:
54
- import socket
55
-
56
55
  start_time = time.monotonic()
57
56
 
58
57
  def check_process_is_running() -> Optional[Exception]:
@@ -64,10 +63,16 @@ class _FlashManager:
64
63
  try:
65
64
  if error := check_process_is_running():
66
65
  return False, error
67
- # TODO(claudia): use asyncio socket create connection
68
- with socket.create_connection(("localhost", self.port), timeout=0.5):
69
- return True, None
70
- except (ConnectionRefusedError, OSError):
66
+ _, writer = await asyncio.wait_for(asyncio.open_connection("localhost", self.port), timeout=0.5)
67
+ try:
68
+ writer.close()
69
+ await writer.wait_closed()
70
+ except Exception:
71
+ pass
72
+ return True, None
73
+ except asyncio.CancelledError:
74
+ raise
75
+ except (OSError, asyncio.TimeoutError):
71
76
  await asyncio.sleep(0.1)
72
77
 
73
78
  return False, Exception(f"Waited too long for port {self.port} to start accepting connections")
@@ -78,9 +83,25 @@ class _FlashManager:
78
83
  host = parsed_url.hostname
79
84
  port = parsed_url.port or 443
80
85
 
86
+ try:
87
+ await self._wait_for_port_success(host, port)
88
+ except (Exception, KeyboardInterrupt, asyncio.CancelledError):
89
+ await self._deregister()
90
+ await self.tunnel_manager.__aexit__(*sys.exc_info())
91
+ raise
92
+
81
93
  self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
82
94
  self.drain_task = asyncio.create_task(self._drain_container())
83
95
 
96
+ async def _deregister(self):
97
+ await asyncio.shield(
98
+ self.client.stub.FlashContainerDeregister(
99
+ api_pb2.FlashContainerDeregisterRequest(),
100
+ timeout=2,
101
+ retry=None,
102
+ )
103
+ )
104
+
84
105
  async def _drain_container(self):
85
106
  """
86
107
  Background task that checks if we've encountered too many failures and drains the container if so.
@@ -88,7 +109,7 @@ class _FlashManager:
88
109
  while True:
89
110
  try:
90
111
  # Check if the container should be drained (e.g., too many failures)
91
- if self.num_failures > _MAX_FAILURES:
112
+ if self.num_heartbeat_failures > _MAX_FAILURES:
92
113
  logger.warning(
93
114
  f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
94
115
  )
@@ -111,9 +132,37 @@ class _FlashManager:
111
132
  logger.warning("[Modal Flash] Shutting down...")
112
133
  return
113
134
 
114
- async def _run_heartbeat(self, host: str, port: int):
115
- first_registration = True
135
+ async def _wait_for_port_success(self, host: str, port: int) -> bool:
116
136
  start_time = time.monotonic()
137
+ while time.monotonic() - start_time < self.startup_timeout:
138
+ try:
139
+ port_check_resp, _ = await self.is_port_connection_healthy(process=self.process)
140
+ if port_check_resp:
141
+ resp = await self.client.stub.FlashContainerRegister(
142
+ api_pb2.FlashContainerRegisterRequest(
143
+ priority=10,
144
+ weight=5,
145
+ host=host,
146
+ port=port,
147
+ ),
148
+ timeout=10,
149
+ retry=None,
150
+ )
151
+ logger.info(f"Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}")
152
+ return True
153
+ except asyncio.CancelledError:
154
+ logger.warning("Waited too long for port to start accepting connections. Shutting down...")
155
+ raise
156
+ except Exception as e:
157
+ logger.error(f"Error waiting for port to start accepting connections: {e}")
158
+ try:
159
+ await asyncio.sleep(1)
160
+ except asyncio.CancelledError:
161
+ logger.warning("Waited too long for port to start accepting connections. Shutting down...")
162
+ raise
163
+ raise TimeoutError("Waited too long for port to start accepting connections. Shutting down...")
164
+
165
+ async def _run_heartbeat(self, host: str, port: int):
117
166
  while True:
118
167
  try:
119
168
  port_check_resp, port_check_error = await self.is_port_connection_healthy(process=self.process)
@@ -128,32 +177,24 @@ class _FlashManager:
128
177
  timeout=10,
129
178
  retry=None,
130
179
  )
131
- self.num_failures = 0
132
- if first_registration:
133
- logger.warning(
134
- f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
135
- )
136
- first_registration = False
180
+ self.num_heartbeat_failures = 0
137
181
  else:
138
- if first_registration and (time.monotonic() - start_time < self.startup_timeout):
139
- continue
140
- else:
141
- logger.error(
142
- f"[Modal Flash] Deregistering container {self.task_id} on {self.tunnel.url} "
143
- f"due to error: {port_check_error}, num_failures: {self.num_failures}"
144
- )
145
- self.num_failures += 1
146
- await self.client.stub.FlashContainerDeregister(api_pb2.FlashContainerDeregisterRequest())
182
+ logger.error(
183
+ f"[Modal Flash] Deregistering container {self.task_id} on {self.tunnel.url} "
184
+ f"due to error: {port_check_error}, num_heartbeat_failures: {self.num_heartbeat_failures}"
185
+ )
186
+ self.num_heartbeat_failures += 1
187
+ await self._deregister()
147
188
  except asyncio.CancelledError:
148
189
  logger.warning("[Modal Flash] Shutting down...")
190
+ await self._deregister()
149
191
  break
150
192
  except Exception as e:
151
193
  logger.error(f"[Modal Flash] Heartbeat failed: {e}")
152
-
153
194
  try:
154
195
  await asyncio.sleep(1)
155
196
  except asyncio.CancelledError:
156
- logger.warning("[Modal Flash] Shutting down...")
197
+ await self._deregister()
157
198
  break
158
199
 
159
200
  def get_container_url(self):
@@ -162,11 +203,14 @@ class _FlashManager:
162
203
 
163
204
  async def stop(self):
164
205
  try:
165
- self.heartbeat_task.cancel()
206
+ if self.heartbeat_task:
207
+ self.heartbeat_task.cancel()
208
+ try:
209
+ await asyncio.wait_for(self.heartbeat_task, timeout=5)
210
+ except (asyncio.TimeoutError, asyncio.CancelledError):
211
+ logger.warning("[Modal Flash] Heartbeat task did not stop within 5s.")
166
212
  except Exception as e:
167
213
  logger.error(f"[Modal Flash] Error stopping: {e}")
168
-
169
- await self.client.stub.FlashContainerDeregister(api_pb2.FlashContainerDeregisterRequest())
170
214
  self.stopped = True
171
215
  logger.warning(f"[Modal Flash] No longer accepting new requests on {self.tunnel.url}.")
172
216
 
@@ -716,12 +760,16 @@ class _FlashContainerEntry:
716
760
 
717
761
  def enter(self):
718
762
  if self.http_config != api_pb2.HTTPConfig():
719
- self.flash_manager = flash_forward(
720
- self.http_config.port,
721
- startup_timeout=self.http_config.startup_timeout,
722
- exit_grace_period=self.http_config.exit_grace_period,
723
- h2_enabled=self.http_config.h2_enabled,
724
- )
763
+ try:
764
+ self.flash_manager = flash_forward(
765
+ self.http_config.port,
766
+ startup_timeout=self.http_config.startup_timeout,
767
+ exit_grace_period=self.http_config.exit_grace_period,
768
+ h2_enabled=self.http_config.h2_enabled,
769
+ )
770
+ except Exception as e:
771
+ logger.warning(f"[Modal Flash] Startup failed: {e}")
772
+ raise UserException()
725
773
 
726
774
  def stop(self):
727
775
  if self.flash_manager:
@@ -22,10 +22,12 @@ class _FlashManager:
22
22
  self, process: typing.Optional[subprocess.Popen], timeout: float = 0.5
23
23
  ) -> tuple[bool, typing.Optional[Exception]]: ...
24
24
  async def _start(self): ...
25
+ async def _deregister(self): ...
25
26
  async def _drain_container(self):
26
27
  """Background task that checks if we've encountered too many failures and drains the container if so."""
27
28
  ...
28
29
 
30
+ async def _wait_for_port_success(self, host: str, port: int) -> bool: ...
29
31
  async def _run_heartbeat(self, host: str, port: int): ...
30
32
  def get_container_url(self): ...
31
33
  async def stop(self): ...
@@ -59,6 +61,12 @@ class FlashManager:
59
61
 
60
62
  _start: ___start_spec
61
63
 
64
+ class ___deregister_spec(typing_extensions.Protocol):
65
+ def __call__(self, /): ...
66
+ async def aio(self, /): ...
67
+
68
+ _deregister: ___deregister_spec
69
+
62
70
  class ___drain_container_spec(typing_extensions.Protocol):
63
71
  def __call__(self, /):
64
72
  """Background task that checks if we've encountered too many failures and drains the container if so."""
@@ -70,6 +78,12 @@ class FlashManager:
70
78
 
71
79
  _drain_container: ___drain_container_spec
72
80
 
81
+ class ___wait_for_port_success_spec(typing_extensions.Protocol):
82
+ def __call__(self, /, host: str, port: int) -> bool: ...
83
+ async def aio(self, /, host: str, port: int) -> bool: ...
84
+
85
+ _wait_for_port_success: ___wait_for_port_success_spec
86
+
73
87
  class ___run_heartbeat_spec(typing_extensions.Protocol):
74
88
  def __call__(self, /, host: str, port: int): ...
75
89
  async def aio(self, /, host: str, port: int): ...
@@ -469,7 +469,17 @@ class _Queue(_Object, type_prefix="qu"):
469
469
 
470
470
  @live_method
471
471
  async def clear(self, *, partition: Optional[str] = None, all: bool = False) -> None:
472
- """Clear the contents of a single partition or all partitions."""
472
+ """Clear the contents of a single partition or all partitions.
473
+
474
+ Warning: this is a destructive operation and will irrevocably delete data.
475
+
476
+ **Examples:**
477
+
478
+ ```python
479
+ q = modal.Queue.from_name("my-queue", create_if_missing=True)
480
+ q.clear()
481
+ ```
482
+ """
473
483
  if partition and all:
474
484
  raise InvalidError("Partition must be null when requesting to clear all.")
475
485
  request = api_pb2.QueueClearRequest(
@@ -510,7 +510,17 @@ class _Queue(modal._object._Object):
510
510
  self, partition: typing.Optional[str], timeout: typing.Optional[float], n_values: int
511
511
  ) -> list[typing.Any]: ...
512
512
  async def clear(self, *, partition: typing.Optional[str] = None, all: bool = False) -> None:
513
- """Clear the contents of a single partition or all partitions."""
513
+ """Clear the contents of a single partition or all partitions.
514
+
515
+ Warning: this is a destructive operation and will irrevocably delete data.
516
+
517
+ **Examples:**
518
+
519
+ ```python
520
+ q = modal.Queue.from_name("my-queue", create_if_missing=True)
521
+ q.clear()
522
+ ```
523
+ """
514
524
  ...
515
525
 
516
526
  async def get(
@@ -840,11 +850,31 @@ class Queue(modal.object.Object):
840
850
 
841
851
  class __clear_spec(typing_extensions.Protocol):
842
852
  def __call__(self, /, *, partition: typing.Optional[str] = None, all: bool = False) -> None:
843
- """Clear the contents of a single partition or all partitions."""
853
+ """Clear the contents of a single partition or all partitions.
854
+
855
+ Warning: this is a destructive operation and will irrevocably delete data.
856
+
857
+ **Examples:**
858
+
859
+ ```python
860
+ q = modal.Queue.from_name("my-queue", create_if_missing=True)
861
+ q.clear()
862
+ ```
863
+ """
844
864
  ...
845
865
 
846
866
  async def aio(self, /, *, partition: typing.Optional[str] = None, all: bool = False) -> None:
847
- """Clear the contents of a single partition or all partitions."""
867
+ """Clear the contents of a single partition or all partitions.
868
+
869
+ Warning: this is a destructive operation and will irrevocably delete data.
870
+
871
+ **Examples:**
872
+
873
+ ```python
874
+ q = modal.Queue.from_name("my-queue", create_if_missing=True)
875
+ q.clear()
876
+ ```
877
+ """
848
878
  ...
849
879
 
850
880
  clear: __clear_spec
@@ -75,8 +75,7 @@ def _validate_exec_args(args: Sequence[str]) -> None:
75
75
  total_arg_len = sum(len(arg) for arg in args)
76
76
  if total_arg_len > ARG_MAX_BYTES:
77
77
  raise InvalidError(
78
- f"Total length of CMD arguments must be less than {ARG_MAX_BYTES} bytes (ARG_MAX). "
79
- f"Got {total_arg_len} bytes."
78
+ f"Total length of CMD arguments cannot exceed {ARG_MAX_BYTES} bytes (ARG_MAX). Got {total_arg_len} bytes."
80
79
  )
81
80
 
82
81
 
@@ -307,6 +306,8 @@ class _Sandbox(_Object, type_prefix="sb"):
307
306
  h2_ports: Sequence[int] = [],
308
307
  # List of ports to tunnel into the sandbox without encryption.
309
308
  unencrypted_ports: Sequence[int] = [],
309
+ # Allow connections to the Sandbox via a subdomain of this parent rather than a default Modal domain.
310
+ custom_domain: Optional[str] = None,
310
311
  # Reference to a Modal Proxy to use in front of this Sandbox.
311
312
  proxy: Optional[_Proxy] = None,
312
313
  # Enable verbose logging for sandbox operations.
@@ -317,10 +318,6 @@ class _Sandbox(_Object, type_prefix="sb"):
317
318
  client: Optional[_Client] = None,
318
319
  environment_name: Optional[str] = None, # *DEPRECATED* Optionally override the default environment
319
320
  pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
320
- # If set, connections to this sandbox will be subdomains of this domain rather than the default.
321
- # Custom domains must be configured manually by Modal. They are different from the custom domains
322
- # in the Modal dashboard.
323
- custom_domain: Optional[str] = None,
324
321
  ) -> "_Sandbox":
325
322
  """
326
323
  Create a new Sandbox to run untrusted, arbitrary code.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.3.1.dev35
3
+ Version: 1.3.2.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -3366,6 +3366,8 @@ message TokenInfoGetResponse {
3366
3366
  // Token metadata
3367
3367
  google.protobuf.Timestamp created_at = 6;
3368
3368
  google.protobuf.Timestamp expires_at = 7;
3369
+
3370
+ string token_name = 8;
3369
3371
  }
3370
3372
 
3371
3373
  message TunnelData {