modal 1.5.2.dev21__tar.gz → 1.5.3.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 (231) hide show
  1. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/PKG-INFO +1 -1
  2. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/grpc_utils.py +19 -4
  3. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/task_command_router_client.py +18 -34
  4. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/_help.py +7 -1
  5. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/changelog.py +7 -7
  6. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/environment.py +9 -9
  7. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/client.pyi +2 -2
  8. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/functions.pyi +6 -6
  9. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/parallel_map.py +56 -51
  10. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox.py +26 -12
  11. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox.pyi +12 -3
  12. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/PKG-INFO +1 -1
  13. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_grpc.py +64 -0
  14. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2.py +979 -861
  15. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2.pyi +283 -6
  16. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2_grpc.py +133 -1
  17. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2_grpc.pyi +43 -3
  18. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/modal_api_grpc.py +4 -0
  19. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_version/__init__.py +1 -1
  20. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/LICENSE +0 -0
  21. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/README.md +0 -0
  22. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/__init__.py +0 -0
  23. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/__main__.py +0 -0
  24. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_billing.py +0 -0
  25. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_clustered_functions.py +0 -0
  26. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_clustered_functions.pyi +0 -0
  27. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_container_entrypoint.py +0 -0
  28. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_environments.py +0 -0
  29. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_function_variants.py +0 -0
  30. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_functions.py +0 -0
  31. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_grpc_client.py +0 -0
  32. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_image.py +0 -0
  33. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_ipython.py +0 -0
  34. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_load_context.py +0 -0
  35. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_location.py +0 -0
  36. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_logs.py +0 -0
  37. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_object.py +0 -0
  38. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/__init__.py +0 -0
  39. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/manager.py +0 -0
  40. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/pty.py +0 -0
  41. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/rich.py +0 -0
  42. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/status.py +0 -0
  43. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_partial_function.py +0 -0
  44. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_resolver.py +0 -0
  45. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_resources.py +0 -0
  46. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/__init__.py +0 -0
  47. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/asgi.py +0 -0
  48. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/container_io_manager.py +0 -0
  49. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
  50. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/execution_context.py +0 -0
  51. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/execution_context.pyi +0 -0
  52. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  53. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/task_lifecycle_manager.py +0 -0
  54. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  55. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/telemetry.py +0 -0
  56. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
  57. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/user_code_imports.py +0 -0
  58. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_serialization.py +0 -0
  59. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_server.py +0 -0
  60. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_traceback.py +0 -0
  61. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_tunnel.py +0 -0
  62. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_tunnel.pyi +0 -0
  63. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_type_manager.py +0 -0
  64. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/__init__.py +0 -0
  65. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/app_utils.py +0 -0
  66. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/async_utils.py +0 -0
  67. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/auth_token_manager.py +0 -0
  68. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/blob_utils.py +0 -0
  69. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/browser_utils.py +0 -0
  70. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
  71. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/curl_utils.py +0 -0
  72. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/deprecation.py +0 -0
  73. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/docker_utils.py +0 -0
  74. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/function_utils.py +0 -0
  75. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/git_utils.py +0 -0
  76. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/grpc_testing.py +0 -0
  77. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/hash_utils.py +0 -0
  78. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/http_utils.py +0 -0
  79. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/jwt_utils.py +0 -0
  80. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/logger.py +0 -0
  81. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/mount_utils.py +0 -0
  82. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/name_utils.py +0 -0
  83. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/package_utils.py +0 -0
  84. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/pattern_utils.py +0 -0
  85. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/proxy_support.py +0 -0
  86. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/rand_pb_testing.py +0 -0
  87. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
  88. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/shell_utils.py +0 -0
  89. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/time_utils.py +0 -0
  90. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/__init__.py +0 -0
  91. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  92. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/cloudpickle.py +0 -0
  93. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/tblib.py +0 -0
  94. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/version.py +0 -0
  95. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_watcher.py +0 -0
  96. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_workspace.py +0 -0
  97. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/app.py +0 -0
  98. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/app.pyi +0 -0
  99. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/billing.py +0 -0
  100. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/billing.pyi +0 -0
  101. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2023.12.312.txt +0 -0
  102. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2023.12.txt +0 -0
  103. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2024.04.txt +0 -0
  104. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2024.10.txt +0 -0
  105. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2025.06.txt +0 -0
  106. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/PREVIEW.txt +0 -0
  107. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/README.md +0 -0
  108. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/base-images.json +0 -0
  109. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/call_graph.py +0 -0
  110. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/__init__.py +0 -0
  111. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/_download.py +0 -0
  112. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/_traceback.py +0 -0
  113. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/app.py +0 -0
  114. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/billing.py +0 -0
  115. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/bootstrap.py +0 -0
  116. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/cluster.py +0 -0
  117. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/config.py +0 -0
  118. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/container.py +0 -0
  119. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/curl.py +0 -0
  120. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/dashboard.py +0 -0
  121. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/dict.py +0 -0
  122. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/endpoint.py +0 -0
  123. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/entry_point.py +0 -0
  124. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/image.py +0 -0
  125. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/import_refs.py +0 -0
  126. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/launch.py +0 -0
  127. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/logo.py +0 -0
  128. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/network_file_system.py +0 -0
  129. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/profile.py +0 -0
  130. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/programs/__init__.py +0 -0
  131. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/programs/run_jupyter.py +0 -0
  132. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/programs/vscode.py +0 -0
  133. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/queues.py +0 -0
  134. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/run.py +0 -0
  135. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/secret.py +0 -0
  136. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/selector.py +0 -0
  137. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/shell.py +0 -0
  138. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/skills.py +0 -0
  139. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/token.py +0 -0
  140. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/utils.py +0 -0
  141. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/volume.py +0 -0
  142. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/workspace.py +0 -0
  143. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/client.py +0 -0
  144. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cloud_bucket_mount.py +0 -0
  145. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cloud_bucket_mount.pyi +0 -0
  146. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cls.py +0 -0
  147. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cls.pyi +0 -0
  148. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/config.py +0 -0
  149. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/container_process.py +0 -0
  150. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/container_process.pyi +0 -0
  151. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/dict.py +0 -0
  152. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/dict.pyi +0 -0
  153. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/environments.py +0 -0
  154. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/environments.pyi +0 -0
  155. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/exception.py +0 -0
  156. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/__init__.py +0 -0
  157. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/flash.py +0 -0
  158. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/flash.pyi +0 -0
  159. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/ipython.py +0 -0
  160. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/file_io.py +0 -0
  161. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/file_io.pyi +0 -0
  162. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/file_pattern_matcher.py +0 -0
  163. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/functions.py +0 -0
  164. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/image.py +0 -0
  165. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/image.pyi +0 -0
  166. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/io_streams.py +0 -0
  167. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/io_streams.pyi +0 -0
  168. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/mount.py +0 -0
  169. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/mount.pyi +0 -0
  170. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/network_file_system.py +0 -0
  171. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/network_file_system.pyi +0 -0
  172. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/object.py +0 -0
  173. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/object.pyi +0 -0
  174. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/output.py +0 -0
  175. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/parallel_map.pyi +0 -0
  176. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/partial_function.py +0 -0
  177. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/partial_function.pyi +0 -0
  178. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/proxy.py +0 -0
  179. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/proxy.pyi +0 -0
  180. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/py.typed +0 -0
  181. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/queue.py +0 -0
  182. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/queue.pyi +0 -0
  183. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/retries.py +0 -0
  184. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/runner.py +0 -0
  185. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/runner.pyi +0 -0
  186. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/running_app.py +0 -0
  187. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox_fs.py +0 -0
  188. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox_fs.pyi +0 -0
  189. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/schedule.py +0 -0
  190. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/scheduler_placement.py +0 -0
  191. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/secret.py +0 -0
  192. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/secret.pyi +0 -0
  193. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/server.py +0 -0
  194. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/server.pyi +0 -0
  195. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/serving.py +0 -0
  196. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/serving.pyi +0 -0
  197. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/skills/modal/SKILL.md +0 -0
  198. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/snapshot.py +0 -0
  199. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/snapshot.pyi +0 -0
  200. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/stream_type.py +0 -0
  201. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/token_flow.py +0 -0
  202. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/token_flow.pyi +0 -0
  203. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/types.py +0 -0
  204. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/volume.py +0 -0
  205. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/volume.pyi +0 -0
  206. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/workspace.py +0 -0
  207. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/workspace.pyi +0 -0
  208. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/SOURCES.txt +0 -0
  209. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/dependency_links.txt +0 -0
  210. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/entry_points.txt +0 -0
  211. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/requires.txt +0 -0
  212. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/top_level.txt +0 -0
  213. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/__init__.py +0 -0
  214. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_cli_docs.py +0 -0
  215. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
  216. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_reference_docs.py +0 -0
  217. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
  218. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/__init__.py +0 -0
  219. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/mdmd.py +0 -0
  220. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/signatures.py +0 -0
  221. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/types.py +0 -0
  222. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/__init__.py +0 -0
  223. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/py.typed +0 -0
  224. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_grpc.py +0 -0
  225. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2.py +0 -0
  226. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
  227. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  228. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  229. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_version/__main__.py +0 -0
  230. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/pyproject.toml +0 -0
  231. {modal-1.5.2.dev21 → modal-1.5.3.dev0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev21
3
+ Version: 1.5.3.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -164,6 +164,9 @@ RETRYABLE_GRPC_STATUS_CODES = [
164
164
  ]
165
165
  SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
166
166
  DEFAULT_MAX_RETRIES = 3
167
+ GRPC_KEEPALIVE_TIME_SECS = 30.0
168
+ GRPC_KEEPALIVE_TIMEOUT_SECS = 10.0
169
+ GRPC_WINDOW_SIZE = 64 * 1024 * 1024 # 64 MiB
167
170
 
168
171
 
169
172
  @dataclass
@@ -251,6 +254,21 @@ class CustomProtoStatusDetailsCodec(StatusDetailsCodecBase):
251
254
  custom_detail_codec = CustomProtoStatusDetailsCodec()
252
255
 
253
256
 
257
+ def create_channel_config() -> grpclib.config.Configuration:
258
+ """Shared grpclib channel settings for Modal client connections.
259
+
260
+ HTTP/2 keepalive probes keep idle transports warm through stateful
261
+ middleboxes and surface dead connections before retries reuse them.
262
+ """
263
+ return grpclib.config.Configuration(
264
+ _keepalive_time=GRPC_KEEPALIVE_TIME_SECS,
265
+ _keepalive_timeout=GRPC_KEEPALIVE_TIMEOUT_SECS,
266
+ _keepalive_permit_without_calls=True,
267
+ http2_connection_window_size=GRPC_WINDOW_SIZE,
268
+ http2_stream_window_size=GRPC_WINDOW_SIZE,
269
+ )
270
+
271
+
254
272
  def create_channel(
255
273
  server_url: str,
256
274
  metadata: dict[str, str] = {},
@@ -262,10 +280,7 @@ def create_channel(
262
280
  o = urllib.parse.urlparse(server_url)
263
281
 
264
282
  channel: grpclib.client.Channel
265
- config = grpclib.config.Configuration(
266
- http2_connection_window_size=64 * 1024 * 1024, # 64 MiB
267
- http2_stream_window_size=64 * 1024 * 1024, # 64 MiB
268
- )
283
+ config = create_channel_config()
269
284
 
270
285
  if o.scheme == "unix":
271
286
  channel = grpclib.client.Channel(path=o.path, config=config, status_details_codec=custom_detail_codec)
@@ -13,7 +13,6 @@ from contextlib import suppress
13
13
  from typing import TypeVar
14
14
 
15
15
  import grpclib.client
16
- import grpclib.config
17
16
  from grpclib import GRPCError, Status
18
17
  from grpclib.exceptions import StreamTerminatedError
19
18
 
@@ -23,7 +22,7 @@ from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
23
22
  from modal_proto.task_command_router_grpc import TaskCommandRouterStub
24
23
 
25
24
  from .._grpc_client import grpc_error_converter
26
- from .._utils.grpc_utils import ModalChannel
25
+ from .._utils.grpc_utils import ModalChannel, create_channel_config
27
26
  from .async_utils import aclosing, retry
28
27
  from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
29
28
 
@@ -222,10 +221,7 @@ class TaskCommandRouterClient:
222
221
  host,
223
222
  port,
224
223
  ssl=ssl_context,
225
- config=grpclib.config.Configuration(
226
- http2_connection_window_size=64 * 1024 * 1024, # 64 MiB
227
- http2_stream_window_size=64 * 1024 * 1024, # 64 MiB
228
- ),
224
+ config=create_channel_config(),
229
225
  closed_error_message="Unable to perform operation on a detached sandbox",
230
226
  )
231
227
 
@@ -501,16 +497,7 @@ class TaskCommandRouterClient:
501
497
  TimeoutError: If the sandbox does not become ready within `timeout`.
502
498
  """
503
499
  request = sr_pb2.SandboxWaitUntilReadyTcrRequest(task_id=task_id, timeout=timeout)
504
- with grpc_error_converter():
505
- try:
506
- return await asyncio.wait_for(
507
- call_with_retries_on_transient_errors(
508
- lambda: self._call_with_auth_retry(self._stub.SandboxWaitUntilReady, request, timeout=timeout),
509
- ),
510
- timeout=timeout,
511
- )
512
- except asyncio.TimeoutError:
513
- raise ModalTimeoutError("Timeout expired")
500
+ return await self._unary_call_with_deadline(self._stub.SandboxWaitUntilReady, request, timeout=timeout)
514
501
 
515
502
  async def exec_poll(self, task_id: str, exec_id: str, deadline: float | None = None) -> sr_pb2.TaskExecPollResponse:
516
503
  """Poll for the exit status of an exec'd command, properly retrying on transient errors.
@@ -820,21 +807,14 @@ class TaskCommandRouterClient:
820
807
  window, the call is cancelled and a `modal.exception.TimeoutError` is raised.
821
808
  """
822
809
  request = sr_pb2.TaskReloadVolumesRequest(task_id=task_id)
823
- with grpc_error_converter():
824
- try:
825
- return await asyncio.wait_for(
826
- call_with_retries_on_transient_errors(
827
- lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request, timeout=timeout),
828
- ),
829
- timeout=timeout,
830
- )
831
- except asyncio.TimeoutError:
832
- raise ModalTimeoutError("Timeout expired")
810
+ return await self._unary_call_with_deadline(self._stub.TaskReloadVolumes, request, timeout=timeout)
811
+
812
+ async def _unary_call_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
813
+ """Call a unary RPC with transient-error retries bounded by a total deadline.
833
814
 
834
- async def _snapshot_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
835
- # helper method for snapshot_directory and snapshot_filesystem to handle grpc
836
- # deadlines in a consistent way, converting any error to TimeoutError after passing
837
- # the total deadline budget
815
+ Raises `modal.exception.TimeoutError` once the deadline has elapsed, or when
816
+ the server responds with `DEADLINE_EXCEEDED`.
817
+ """
838
818
  timeout_deadline = time.monotonic() + timeout
839
819
 
840
820
  def call():
@@ -852,17 +832,21 @@ class TaskCommandRouterClient:
852
832
  exclude_status_codes=[Status.DEADLINE_EXCEEDED, Status.CANCELLED],
853
833
  timeout_deadline=timeout_deadline,
854
834
  )
855
- except Exception:
856
- if time.monotonic() >= timeout_deadline:
835
+ except Exception as exc:
836
+ if time.monotonic() >= timeout_deadline or getattr(exc, "_grpc_status", None) == Status.DEADLINE_EXCEEDED:
857
837
  raise ModalTimeoutError("Timeout expired")
858
838
  raise
859
839
 
860
840
  async def snapshot_directory(
861
841
  self, request: sr_pb2.TaskSnapshotDirectoryRequest, *, timeout: float, **kwargs
862
842
  ) -> sr_pb2.TaskSnapshotDirectoryResponse:
863
- return await self._snapshot_with_deadline(self._stub.TaskSnapshotDirectory, request, timeout=timeout, **kwargs)
843
+ return await self._unary_call_with_deadline(
844
+ self._stub.TaskSnapshotDirectory, request, timeout=timeout, **kwargs
845
+ )
864
846
 
865
847
  async def snapshot_filesystem(
866
848
  self, request: sr_pb2.TaskSnapshotFilesystemRequest, *, timeout: float, **kwargs
867
849
  ) -> sr_pb2.TaskSnapshotFilesystemResponse:
868
- return await self._snapshot_with_deadline(self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs)
850
+ return await self._unary_call_with_deadline(
851
+ self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
852
+ )
@@ -70,7 +70,13 @@ def _option_label(param: click.Parameter, ctx: click.Context) -> Text:
70
70
  text.append(opt, style=_OPTION_FLAG_STYLE)
71
71
  if not getattr(param, "is_flag", False) and not getattr(param, "count", False):
72
72
  text.append(" ")
73
- text.append(param.make_metavar(ctx), style=_OPTION_METAVAR_STYLE)
73
+ if "ctx" in inspect.signature(click.Parameter.make_metavar).parameters:
74
+ # click >= 8.2.0
75
+ metavar = param.make_metavar(ctx)
76
+ else:
77
+ # backwards compatibility
78
+ metavar = param.make_metavar() # type: ignore[call-arg]
79
+ text.append(metavar, style=_OPTION_METAVAR_STYLE)
74
80
  return text
75
81
 
76
82
 
@@ -120,17 +120,17 @@ def changelog(
120
120
 
121
121
  Examples:
122
122
 
123
- ```bash
124
- modal changelog --since 1.2.0 # Show updates added after a specific version
123
+ ```bash
124
+ modal changelog --since 1.2.0 # Show updates added after a specific version
125
125
 
126
- modal changelog --since 2026-01-01 # Show updates added after a specific date
126
+ modal changelog --since 2026-01-01 # Show updates added after a specific date
127
127
 
128
- modal changelog --newer # Show updates released after the currently installed version
128
+ modal changelog --newer # Show updates released after the currently installed version
129
129
 
130
- modal changelog --last 3 # Show updates included in the 3 most recent releases
130
+ modal changelog --last 3 # Show updates included in the 3 most recent releases
131
131
 
132
- modal changelog --for 1.3.1 # Show the changelog for a specific release
133
- ```
132
+ modal changelog --for 1.3.1 # Show the changelog for a specific release
133
+ ```
134
134
 
135
135
  Note: when using `--since` or `--last`, only changes up to the currently installed version are shown.
136
136
 
@@ -242,21 +242,21 @@ async def environment_billing(
242
242
 
243
243
  Examples:
244
244
 
245
- ```bash
246
- modal environment billing report --start 2025-12-01 --end 2026-01-01
245
+ ```bash
246
+ modal environment billing report --start 2025-12-01 --end 2026-01-01
247
247
 
248
- modal environment billing report --for "last month" --tag-names team,project
248
+ modal environment billing report --for "last month" --tag-names team,project
249
249
 
250
- modal environment billing report test_env --for today --resolution h
250
+ modal environment billing report test_env --for today --resolution h
251
251
 
252
- modal environment billing report test_env --for "this month" --show-resources
252
+ modal environment billing report test_env --for "this month" --show-resources
253
253
 
254
- modal environment billing report prod_env --for yesterday -r h --tz local
254
+ modal environment billing report prod_env --for yesterday -r h --tz local
255
255
 
256
- modal environment billing report main_env --for "last month" --csv > report.csv
256
+ modal environment billing report main_env --for "last month" --csv > report.csv
257
257
 
258
- modal environment billing report main_env --start 2025-12-01 --json > report.json
259
- ```
258
+ modal environment billing report main_env --start 2025-12-01 --json > report.json
259
+ ```
260
260
 
261
261
  """
262
262
  if json and csv:
@@ -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.5.2.dev21",
38
+ version: str = "1.5.3.dev0",
39
39
  ):
40
40
  """mdmd:hidden
41
41
  The Modal client object is not intended to be instantiated directly by users.
@@ -205,7 +205,7 @@ class Client:
205
205
  server_url: str,
206
206
  client_type: int,
207
207
  credentials: typing.Optional[tuple[str, str]],
208
- version: str = "1.5.2.dev21",
208
+ version: str = "1.5.3.dev0",
209
209
  ):
210
210
  """mdmd:hidden
211
211
  The Modal client object is not intended to be instantiated directly by users.
@@ -491,7 +491,7 @@ class Function(
491
491
 
492
492
  _call_generator: ___call_generator_spec
493
493
 
494
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
494
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
495
495
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
496
496
  """Calls the function remotely, executing it with the given arguments and returning the execution's result.
497
497
 
@@ -516,7 +516,7 @@ class Function(
516
516
  """
517
517
  ...
518
518
 
519
- remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
519
+ remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
520
520
 
521
521
  class __remote_gen_spec(typing_extensions.Protocol):
522
522
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -566,7 +566,7 @@ class Function(
566
566
  """
567
567
  ...
568
568
 
569
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
569
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
570
570
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
571
571
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
572
572
 
@@ -599,7 +599,7 @@ class Function(
599
599
  """
600
600
  ...
601
601
 
602
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
602
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
603
603
 
604
604
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
605
605
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -607,7 +607,7 @@ class Function(
607
607
 
608
608
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
609
609
 
610
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
610
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
611
611
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
612
612
  """Calls the function with the given arguments, without waiting for the results.
613
613
 
@@ -640,7 +640,7 @@ class Function(
640
640
  """
641
641
  ...
642
642
 
643
- spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
643
+ spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
644
644
 
645
645
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
646
646
  """Return the inner Python object wrapped by this Modal Function.
@@ -654,16 +654,14 @@ async def _map_invocation_inputplane(
654
654
  stale_retry_duplicates = 0
655
655
  already_complete_duplicates = 0
656
656
  retried_outputs = 0
657
- input_queue_size = 0
658
657
  last_entry_id = ""
659
658
 
660
659
  # The input-plane server returns this after the first request.
661
660
  map_token = None
662
661
  map_token_received = asyncio.Event()
663
662
 
664
- # Single priority queue that holds *both* fresh inputs (timestamp == now)
665
- # and future retries (timestamp > now).
666
- queue: TimestampPriorityQueue[api_pb2.MapStartOrContinueItem] = TimestampPriorityQueue()
663
+ input_queue: asyncio.Queue[api_pb2.MapStartOrContinueItem | None] = asyncio.Queue()
664
+ retry_queue: TimestampPriorityQueue[api_pb2.MapStartOrContinueItem] = TimestampPriorityQueue()
667
665
 
668
666
  # Maximum number of inputs that may be in-flight (the server sends this in
669
667
  # the first response – fall back to the default if we never receive it for
@@ -682,7 +680,7 @@ async def _map_invocation_inputplane(
682
680
  map_items_manager = _MapItemsManager(
683
681
  retry_policy=retry_policy,
684
682
  function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
685
- retry_queue=queue,
683
+ retry_queue=retry_queue,
686
684
  sync_client_retries_enabled=True,
687
685
  max_inputs_outstanding=MAX_INPUTS_OUTSTANDING_DEFAULT,
688
686
  is_input_plane_instance=True,
@@ -729,61 +727,67 @@ async def _map_invocation_inputplane(
729
727
  async_map_ordered(input_iter(), create_input, concurrency=BLOB_MAX_PARALLELISM)
730
728
  ) as streamer:
731
729
  async for q_item in streamer:
732
- await queue.put(time.time(), q_item)
730
+ await input_queue.put(q_item)
733
731
 
734
732
  # All inputs have been read.
733
+ await input_queue.put(None)
735
734
  update_counters(set_have_all_inputs=True)
736
735
  yield
737
736
 
738
- async def pump_inputs():
737
+ async def send_map_start_or_continue(request_items: list[api_pb2.MapStartOrContinueItem]):
739
738
  nonlocal map_token, max_inputs_outstanding
740
- async for batch in queue_batch_iterator(queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
741
- # Convert the queued items into the proto format expected by the RPC.
742
- request_items: list[api_pb2.MapStartOrContinueItem] = [
743
- api_pb2.MapStartOrContinueItem(input=qi.input, attempt_token=qi.attempt_token) for qi in batch
744
- ]
739
+ request = api_pb2.MapStartOrContinueRequest(
740
+ function_id=function.object_id,
741
+ map_token=map_token or "",
742
+ parent_input_id=current_input_id() or "",
743
+ items=request_items,
744
+ )
745
745
 
746
- await map_items_manager.add_items_inputplane(request_items)
746
+ metadata = await client.get_input_plane_metadata(function._input_plane_region)
747
747
 
748
- # Build request
749
- request = api_pb2.MapStartOrContinueRequest(
750
- function_id=function.object_id,
751
- map_token=map_token,
752
- parent_input_id=current_input_id() or "",
753
- items=request_items,
754
- )
748
+ response: api_pb2.MapStartOrContinueResponse = await input_plane_stub.MapStartOrContinue(
749
+ request,
750
+ retry=Retry(
751
+ additional_status_codes=[Status.RESOURCE_EXHAUSTED],
752
+ max_delay=PUMP_INPUTS_MAX_RETRY_DELAY,
753
+ max_retries=None,
754
+ ),
755
+ metadata=metadata,
756
+ )
755
757
 
756
- metadata = await client.get_input_plane_metadata(function._input_plane_region)
758
+ # match response items to the corresponding request item index
759
+ response_items_idx_tuple = [
760
+ (request_items[idx].input.idx, attempt_token) for idx, attempt_token in enumerate(response.attempt_tokens)
761
+ ]
757
762
 
758
- response: api_pb2.MapStartOrContinueResponse = await input_plane_stub.MapStartOrContinue(
759
- request,
760
- retry=Retry(
761
- additional_status_codes=[Status.RESOURCE_EXHAUSTED],
762
- max_delay=PUMP_INPUTS_MAX_RETRY_DELAY,
763
- max_retries=None,
764
- ),
765
- metadata=metadata,
766
- )
763
+ map_items_manager.handle_put_continue_response(response_items_idx_tuple)
767
764
 
768
- # match response items to the corresponding request item index
769
- response_items_idx_tuple = [
770
- (request_items[idx].input.idx, attempt_token)
771
- for idx, attempt_token in enumerate(response.attempt_tokens)
772
- ]
765
+ # Set the function call id and actual retry policy with the data from the first response.
766
+ if map_token is None:
767
+ map_token = response.map_token
768
+ map_token_received.set()
769
+ max_inputs_outstanding = response.max_inputs_outstanding or MAX_INPUTS_OUTSTANDING_DEFAULT
770
+ map_items_manager.set_retry_policy(response.retry_policy)
771
+ # Update the retry policy for the first batch of inputs.
772
+ # Subsequent batches will have the correct user-specified retry policy
773
+ # set by the updated _MapItemsManager.
774
+ map_items_manager.update_items_retry_policy(response.retry_policy)
773
775
 
774
- map_items_manager.handle_put_continue_response(response_items_idx_tuple)
776
+ async def pump_inputs():
777
+ async for batch in queue_batch_iterator(input_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
778
+ request_items: list[api_pb2.MapStartOrContinueItem] = list(batch)
779
+ await map_items_manager.add_items_inputplane(request_items)
780
+ await send_map_start_or_continue(request_items)
781
+ yield
775
782
 
776
- # Set the function call id and actual retry policy with the data from the first response.
777
- # This conditional is skipped for subsequent iterations of this for-loop.
778
- if map_token is None:
779
- map_token = response.map_token
780
- map_token_received.set()
781
- max_inputs_outstanding = response.max_inputs_outstanding or MAX_INPUTS_OUTSTANDING_DEFAULT
782
- map_items_manager.set_retry_policy(response.retry_policy)
783
- # Update the retry policy for the first batch of inputs.
784
- # Subsequent batches will have the correct user-specified retry policy
785
- # set by the updated _MapItemsManager.
786
- map_items_manager.update_items_retry_policy(response.retry_policy)
783
+ async def retry_inputs():
784
+ while True:
785
+ retry_item = await retry_queue.get()
786
+ if retry_item is None:
787
+ break
788
+ request_items = [retry_item]
789
+ await map_items_manager.add_items_inputplane(request_items)
790
+ await send_map_start_or_continue(request_items)
787
791
  yield
788
792
 
789
793
  async def check_lost_inputs():
@@ -897,8 +901,8 @@ async def _map_invocation_inputplane(
897
901
  async for item in stream:
898
902
  yield item
899
903
  finally:
900
- await queue.close()
901
- pass
904
+ await input_queue.put(None)
905
+ await retry_queue.close()
902
906
 
903
907
  async def fetch_output(item: api_pb2.FunctionGetOutputsItem) -> tuple[int, Any]:
904
908
  try:
@@ -945,7 +949,8 @@ async def _map_invocation_inputplane(
945
949
  f"no_context_duplicates={no_context_duplicates} stale_retry_duplicates={stale_retry_duplicates} "
946
950
  f"already_complete_duplicates={already_complete_duplicates} retried_outputs={retried_outputs} "
947
951
  f"map_token={map_token} max_inputs_outstanding={max_inputs_outstanding} "
948
- f"map_items_manager_size={len(map_items_manager)} input_queue_size={input_queue_size}"
952
+ f"map_items_manager_size={len(map_items_manager)} input_queue_size={input_queue.qsize()} "
953
+ f"retry_queue_size={retry_queue.qsize()}"
949
954
  )
950
955
 
951
956
  while True:
@@ -960,7 +965,7 @@ async def _map_invocation_inputplane(
960
965
  log_task = asyncio.create_task(log_debug_stats())
961
966
 
962
967
  async with aclosing(
963
- async_merge(drain_input_generator(), pump_inputs(), poll_outputs(), check_lost_inputs())
968
+ async_merge(drain_input_generator(), pump_inputs(), retry_inputs(), poll_outputs(), check_lost_inputs())
964
969
  ) as merged:
965
970
  async for maybe_output in merged:
966
971
  if maybe_output is not None: # ignore None sentinels
@@ -816,6 +816,7 @@ class _Sandbox(_Object, type_prefix="sb"):
816
816
  *args: str,
817
817
  app: "modal.app._App | None" = None,
818
818
  name: str | None = None,
819
+ tags: dict[str, str] | None = None,
819
820
  image: _Image | None = None,
820
821
  env: dict[str, str | None] | None = None,
821
822
  secrets: Collection[_Secret] | None = None,
@@ -851,7 +852,7 @@ class _Sandbox(_Object, type_prefix="sb"):
851
852
  `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
852
853
  filesystem snapshots.
853
854
 
854
- Features like tags, memory snapshots, network file systems, GPUs, and custom
855
+ Features like memory snapshots, network file systems, GPUs, and custom
855
856
  domains are not supported.
856
857
 
857
858
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
@@ -993,10 +994,12 @@ class _Sandbox(_Object, type_prefix="sb"):
993
994
  ),
994
995
  )
995
996
 
997
+ tag_protos = [api_pb2.SandboxTag(tag_name=k, tag_value=v) for k, v in tags.items()] if tags else []
996
998
  create_req = api_pb2.SandboxCreateV2Request(
997
999
  app_id=load_context.app_id,
998
1000
  definition=definition,
999
1001
  ephemeral_secrets=api_pb2.StringMap(contents=env_dict) if env_dict else None,
1002
+ tags=tag_protos,
1000
1003
  )
1001
1004
  assert load_context.client._auth_token_manager
1002
1005
  auth_token = await load_context.client._auth_token_manager.get_token()
@@ -1267,22 +1270,27 @@ class _Sandbox(_Object, type_prefix="sb"):
1267
1270
  Returns:
1268
1271
  Tags as a map from tag name to tag value.
1269
1272
  """
1270
- self._ensure_v1("get_tags")
1271
1273
  req = api_pb2.SandboxTagsGetRequest(sandbox_id=self.object_id)
1272
- resp = await self._client.stub.SandboxTagsGet(req)
1274
+ stub = self._client.stub
1275
+ if self._is_v2:
1276
+ assert self._client._auth_token_manager
1277
+ auth_token = await self._client._auth_token_manager.get_token()
1278
+ resp = await stub.SandboxTagsGetV2(req, metadata=[("x-modal-auth-token", auth_token)])
1279
+ else:
1280
+ resp = await stub.SandboxTagsGet(req)
1273
1281
 
1274
1282
  return {tag.tag_name: tag.tag_value for tag in resp.tags}
1275
1283
 
1276
1284
  async def set_tags(self, tags: dict[str, str], *, client: _Client | None = None) -> None:
1277
1285
  """Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
1278
1286
 
1287
+ Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
1288
+
1279
1289
  Args:
1280
1290
  tags: Tag names and values to set on this sandbox.
1281
1291
  client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
1282
1292
 
1283
1293
  """
1284
- self._ensure_v1("set_tags")
1285
- environment_name = _get_environment_name()
1286
1294
  if client is not None:
1287
1295
  deprecation_warning(
1288
1296
  (2025, 9, 18),
@@ -1291,13 +1299,19 @@ class _Sandbox(_Object, type_prefix="sb"):
1291
1299
  )
1292
1300
 
1293
1301
  tags_list = [api_pb2.SandboxTag(tag_name=name, tag_value=value) for name, value in tags.items()]
1294
-
1295
- req = api_pb2.SandboxTagsSetRequest(
1296
- environment_name=environment_name,
1297
- sandbox_id=self.object_id,
1298
- tags=tags_list,
1299
- )
1300
- await self._client.stub.SandboxTagsSet(req)
1302
+ stub = self._client.stub
1303
+ if self._is_v2:
1304
+ assert self._client._auth_token_manager
1305
+ auth_token = await self._client._auth_token_manager.get_token()
1306
+ req = api_pb2.SandboxTagsSetRequest(sandbox_id=self.object_id, tags=tags_list)
1307
+ await stub.SandboxTagsSetV2(req, metadata=[("x-modal-auth-token", auth_token)])
1308
+ else:
1309
+ req = api_pb2.SandboxTagsSetRequest(
1310
+ environment_name=_get_environment_name(),
1311
+ sandbox_id=self.object_id,
1312
+ tags=tags_list,
1313
+ )
1314
+ await stub.SandboxTagsSet(req)
1301
1315
 
1302
1316
  async def _experimental_set_outbound_network_policy(
1303
1317
  self,
@@ -361,6 +361,7 @@ class _Sandbox(modal._object._Object):
361
361
  *args: str,
362
362
  app: typing.Optional[modal.app._App] = None,
363
363
  name: typing.Optional[str] = None,
364
+ tags: typing.Optional[dict[str, str]] = None,
364
365
  image: typing.Optional[modal._image._Image] = None,
365
366
  env: typing.Optional[dict[str, typing.Optional[str]]] = None,
366
367
  secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
@@ -399,7 +400,7 @@ class _Sandbox(modal._object._Object):
399
400
  `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
400
401
  filesystem snapshots.
401
402
 
402
- Features like tags, memory snapshots, network file systems, GPUs, and custom
403
+ Features like memory snapshots, network file systems, GPUs, and custom
403
404
  domains are not supported.
404
405
 
405
406
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
@@ -518,6 +519,8 @@ class _Sandbox(modal._object._Object):
518
519
  async def set_tags(self, tags: dict[str, str], *, client: typing.Optional[modal.client._Client] = None) -> None:
519
520
  """Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
520
521
 
522
+ Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
523
+
521
524
  Args:
522
525
  tags: Tag names and values to set on this sandbox.
523
526
  client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
@@ -1711,6 +1714,7 @@ class Sandbox(modal.object.Object):
1711
1714
  *args: str,
1712
1715
  app: typing.Optional[modal.app.App] = None,
1713
1716
  name: typing.Optional[str] = None,
1717
+ tags: typing.Optional[dict[str, str]] = None,
1714
1718
  image: typing.Optional[modal.image.Image] = None,
1715
1719
  env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1716
1720
  secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
@@ -1749,7 +1753,7 @@ class Sandbox(modal.object.Object):
1749
1753
  `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
1750
1754
  filesystem snapshots.
1751
1755
 
1752
- Features like tags, memory snapshots, network file systems, GPUs, and custom
1756
+ Features like memory snapshots, network file systems, GPUs, and custom
1753
1757
  domains are not supported.
1754
1758
 
1755
1759
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
@@ -1772,6 +1776,7 @@ class Sandbox(modal.object.Object):
1772
1776
  *args: str,
1773
1777
  app: typing.Optional[modal.app.App] = None,
1774
1778
  name: typing.Optional[str] = None,
1779
+ tags: typing.Optional[dict[str, str]] = None,
1775
1780
  image: typing.Optional[modal.image.Image] = None,
1776
1781
  env: typing.Optional[dict[str, typing.Optional[str]]] = None,
1777
1782
  secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
@@ -1810,7 +1815,7 @@ class Sandbox(modal.object.Object):
1810
1815
  `secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
1811
1816
  filesystem snapshots.
1812
1817
 
1813
- Features like tags, memory snapshots, network file systems, GPUs, and custom
1818
+ Features like memory snapshots, network file systems, GPUs, and custom
1814
1819
  domains are not supported.
1815
1820
 
1816
1821
  Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
@@ -2035,6 +2040,8 @@ class Sandbox(modal.object.Object):
2035
2040
  def __call__(self, /, tags: dict[str, str], *, client: typing.Optional[modal.client.Client] = None) -> None:
2036
2041
  """Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
2037
2042
 
2043
+ Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
2044
+
2038
2045
  Args:
2039
2046
  tags: Tag names and values to set on this sandbox.
2040
2047
  client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
@@ -2044,6 +2051,8 @@ class Sandbox(modal.object.Object):
2044
2051
  async def aio(self, /, tags: dict[str, str], *, client: typing.Optional[modal.client.Client] = None) -> None:
2045
2052
  """Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
2046
2053
 
2054
+ Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
2055
+
2047
2056
  Args:
2048
2057
  tags: Tag names and values to set on this sandbox.
2049
2058
  client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.2.dev21
3
+ Version: 1.5.3.dev0
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0