modAL 1.2.3.dev13__tar.gz → 1.2.3.dev14__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 (200) hide show
  1. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/PKG-INFO +1 -1
  2. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/grpc_utils.py +90 -20
  3. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/client.pyi +2 -2
  4. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/config.py +8 -0
  5. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/PKG-INFO +1 -1
  6. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api.proto +6 -0
  7. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2.py +363 -353
  8. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2.pyi +18 -0
  9. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_version/__init__.py +1 -1
  10. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/LICENSE +0 -0
  11. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/README.md +0 -0
  12. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/__init__.py +0 -0
  13. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/__main__.py +0 -0
  14. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_billing.py +0 -0
  15. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_clustered_functions.py +0 -0
  16. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_clustered_functions.pyi +0 -0
  17. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_container_entrypoint.py +0 -0
  18. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_functions.py +0 -0
  19. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_grpc_client.py +0 -0
  20. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_ipython.py +0 -0
  21. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_load_context.py +0 -0
  22. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_location.py +0 -0
  23. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_object.py +0 -0
  24. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_output.py +0 -0
  25. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_partial_function.py +0 -0
  26. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_pty.py +0 -0
  27. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_resolver.py +0 -0
  28. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_resources.py +0 -0
  29. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/__init__.py +0 -0
  30. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/asgi.py +0 -0
  31. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/container_io_manager.py +0 -0
  32. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
  33. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/execution_context.py +0 -0
  34. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/execution_context.pyi +0 -0
  35. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  36. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/telemetry.py +0 -0
  37. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/user_code_imports.py +0 -0
  38. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_serialization.py +0 -0
  39. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_traceback.py +0 -0
  40. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_tunnel.py +0 -0
  41. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_tunnel.pyi +0 -0
  42. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_type_manager.py +0 -0
  43. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/__init__.py +0 -0
  44. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/app_utils.py +0 -0
  45. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/async_utils.py +0 -0
  46. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/auth_token_manager.py +0 -0
  47. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/blob_utils.py +0 -0
  48. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
  49. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/deprecation.py +0 -0
  50. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/docker_utils.py +0 -0
  51. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/function_utils.py +0 -0
  52. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/git_utils.py +0 -0
  53. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/grpc_testing.py +0 -0
  54. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/hash_utils.py +0 -0
  55. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/http_utils.py +0 -0
  56. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/jwt_utils.py +0 -0
  57. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/logger.py +0 -0
  58. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/mount_utils.py +0 -0
  59. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/name_utils.py +0 -0
  60. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/package_utils.py +0 -0
  61. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/pattern_utils.py +0 -0
  62. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/rand_pb_testing.py +0 -0
  63. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/shell_utils.py +0 -0
  64. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/task_command_router_client.py +0 -0
  65. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/time_utils.py +0 -0
  66. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/__init__.py +0 -0
  67. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  68. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/cloudpickle.py +0 -0
  69. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/tblib.py +0 -0
  70. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_watcher.py +0 -0
  71. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/app.py +0 -0
  72. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/app.pyi +0 -0
  73. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/billing.py +0 -0
  74. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2023.12.312.txt +0 -0
  75. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2023.12.txt +0 -0
  76. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2024.04.txt +0 -0
  77. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2024.10.txt +0 -0
  78. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2025.06.txt +0 -0
  79. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/PREVIEW.txt +0 -0
  80. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/README.md +0 -0
  81. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/base-images.json +0 -0
  82. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/call_graph.py +0 -0
  83. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/__init__.py +0 -0
  84. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/_download.py +0 -0
  85. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/_traceback.py +0 -0
  86. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/app.py +0 -0
  87. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/cluster.py +0 -0
  88. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/config.py +0 -0
  89. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/container.py +0 -0
  90. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/dict.py +0 -0
  91. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/entry_point.py +0 -0
  92. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/environment.py +0 -0
  93. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/import_refs.py +0 -0
  94. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/launch.py +0 -0
  95. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/network_file_system.py +0 -0
  96. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/profile.py +0 -0
  97. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/__init__.py +0 -0
  98. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/launch_instance_ssh.py +0 -0
  99. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/run_jupyter.py +0 -0
  100. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/run_marimo.py +0 -0
  101. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/vscode.py +0 -0
  102. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/queues.py +0 -0
  103. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/run.py +0 -0
  104. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/secret.py +0 -0
  105. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/token.py +0 -0
  106. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/utils.py +0 -0
  107. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/volume.py +0 -0
  108. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/client.py +0 -0
  109. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cloud_bucket_mount.py +0 -0
  110. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cloud_bucket_mount.pyi +0 -0
  111. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cls.py +0 -0
  112. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cls.pyi +0 -0
  113. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/container_process.py +0 -0
  114. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/container_process.pyi +0 -0
  115. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/dict.py +0 -0
  116. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/dict.pyi +0 -0
  117. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/environments.py +0 -0
  118. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/environments.pyi +0 -0
  119. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/exception.py +0 -0
  120. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/__init__.py +0 -0
  121. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/flash.py +0 -0
  122. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/flash.pyi +0 -0
  123. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/ipython.py +0 -0
  124. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/file_io.py +0 -0
  125. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/file_io.pyi +0 -0
  126. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/file_pattern_matcher.py +0 -0
  127. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/functions.py +0 -0
  128. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/functions.pyi +0 -0
  129. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/gpu.py +0 -0
  130. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/image.py +0 -0
  131. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/image.pyi +0 -0
  132. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/io_streams.py +0 -0
  133. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/io_streams.pyi +0 -0
  134. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/mount.py +0 -0
  135. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/mount.pyi +0 -0
  136. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/network_file_system.py +0 -0
  137. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/network_file_system.pyi +0 -0
  138. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/object.py +0 -0
  139. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/object.pyi +0 -0
  140. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/output.py +0 -0
  141. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/parallel_map.py +0 -0
  142. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/parallel_map.pyi +0 -0
  143. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/partial_function.py +0 -0
  144. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/partial_function.pyi +0 -0
  145. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/proxy.py +0 -0
  146. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/proxy.pyi +0 -0
  147. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/py.typed +0 -0
  148. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/queue.py +0 -0
  149. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/queue.pyi +0 -0
  150. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/retries.py +0 -0
  151. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/runner.py +0 -0
  152. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/runner.pyi +0 -0
  153. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/running_app.py +0 -0
  154. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/sandbox.py +0 -0
  155. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/sandbox.pyi +0 -0
  156. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/schedule.py +0 -0
  157. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/scheduler_placement.py +0 -0
  158. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/secret.py +0 -0
  159. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/secret.pyi +0 -0
  160. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/serving.py +0 -0
  161. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/serving.pyi +0 -0
  162. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/snapshot.py +0 -0
  163. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/snapshot.pyi +0 -0
  164. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/stream_type.py +0 -0
  165. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/token_flow.py +0 -0
  166. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/token_flow.pyi +0 -0
  167. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/volume.py +0 -0
  168. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/volume.pyi +0 -0
  169. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/SOURCES.txt +0 -0
  170. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/dependency_links.txt +0 -0
  171. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/entry_points.txt +0 -0
  172. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/requires.txt +0 -0
  173. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/top_level.txt +0 -0
  174. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/__init__.py +0 -0
  175. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/gen_cli_docs.py +0 -0
  176. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/gen_reference_docs.py +0 -0
  177. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/mdmd/__init__.py +0 -0
  178. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/mdmd/mdmd.py +0 -0
  179. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/mdmd/signatures.py +0 -0
  180. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/__init__.py +0 -0
  181. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_grpc.py +0 -0
  182. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2_grpc.py +0 -0
  183. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2_grpc.pyi +0 -0
  184. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/modal_api_grpc.py +0 -0
  185. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/py.typed +0 -0
  186. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router.proto +0 -0
  187. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_grpc.py +0 -0
  188. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2.py +0 -0
  189. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2.pyi +0 -0
  190. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  191. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  192. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router.proto +0 -0
  193. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_grpc.py +0 -0
  194. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2.py +0 -0
  195. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
  196. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  197. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  198. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_version/__main__.py +0 -0
  199. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/pyproject.toml +0 -0
  200. {modal-1.2.3.dev13 → modal-1.2.3.dev14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.3.dev13
3
+ Version: 1.2.3.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -29,6 +29,7 @@ from modal_proto import api_pb2
29
29
  from modal_version import __version__
30
30
 
31
31
  from .._traceback import suppress_tb_frames
32
+ from ..config import config
32
33
  from .async_utils import retry
33
34
  from .logger import logger
34
35
 
@@ -72,6 +73,7 @@ RETRYABLE_GRPC_STATUS_CODES = [
72
73
  Status.INTERNAL,
73
74
  Status.UNKNOWN,
74
75
  ]
76
+ SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
75
77
 
76
78
 
77
79
  @dataclass
@@ -251,6 +253,50 @@ async def retry_transient_errors(
251
253
  return await _retry_transient_errors(fn, req, retry=Retry(max_retries=max_retries))
252
254
 
253
255
 
256
+ def get_server_retry_policy(exc: Exception) -> Optional[api_pb2.RPCRetryPolicy]:
257
+ """Get server retry policy."""
258
+ if not isinstance(exc, GRPCError) or not exc.details:
259
+ return None
260
+
261
+ # Server should not set multiple retry instructions, but if there is more than one, pick the first one
262
+ for entry in exc.details:
263
+ if isinstance(entry, api_pb2.RPCRetryPolicy):
264
+ return entry
265
+ return None
266
+
267
+
268
+ def process_exception_before_retry(
269
+ exc: Exception,
270
+ final_attempt: bool,
271
+ fn_name: str,
272
+ n_retries: int,
273
+ delay: float,
274
+ total_deadline: Optional[float],
275
+ idempotency_key: str,
276
+ ):
277
+ """Process exception before retry, used by `_retry_transient_errors`."""
278
+ with suppress_tb_frames(1):
279
+ if final_attempt:
280
+ logger.debug(
281
+ f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} "
282
+ f"{total_deadline=} for {fn_name} ({idempotency_key[:8]})"
283
+ )
284
+ if isinstance(exc, OSError):
285
+ raise ConnectionError(str(exc))
286
+ elif isinstance(exc, asyncio.TimeoutError):
287
+ raise ConnectionError(str(exc))
288
+ else:
289
+ raise exc
290
+
291
+ if isinstance(exc, AttributeError) and "_write_appdata" not in str(exc):
292
+ # StreamTerminatedError are not properly raised in grpclib<=0.4.7
293
+ # fixed in https://github.com/vmagamedov/grpclib/issues/185
294
+ # TODO: update to newer version (>=0.4.8) once stable
295
+ raise exc
296
+
297
+ logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})")
298
+
299
+
254
300
  async def _retry_transient_errors(
255
301
  fn: typing.Union[
256
302
  "modal._grpc_client.UnaryUnaryWrapper[RequestType, ResponseType]",
@@ -273,12 +319,15 @@ async def _retry_transient_errors(
273
319
 
274
320
  delay = retry.base_delay
275
321
  n_retries = 0
322
+ n_throttled_retries = 0
276
323
 
277
324
  status_codes = [*RETRYABLE_GRPC_STATUS_CODES, *retry.additional_status_codes]
278
325
 
279
326
  idempotency_key = str(uuid.uuid4())
280
327
 
281
328
  t0 = time.time()
329
+ last_server_retry_warning_time = None
330
+
282
331
  if retry.total_timeout is not None:
283
332
  total_deadline = t0 + retry.total_timeout
284
333
  else:
@@ -290,14 +339,18 @@ async def _retry_transient_errors(
290
339
  attempt_metadata = [
291
340
  ("x-idempotency-key", idempotency_key),
292
341
  ("x-retry-attempt", str(n_retries)),
342
+ ("x-throttle-retry-attempt", str(n_throttled_retries)),
293
343
  *metadata,
294
344
  ]
295
345
  if n_retries > 0:
296
346
  attempt_metadata.append(("x-retry-delay", str(time.time() - t0)))
347
+ if n_throttled_retries > 0:
348
+ attempt_metadata.append(("x-throttle-retry-delay", str(time.time() - t0)))
349
+
297
350
  timeouts = []
298
351
  if retry.attempt_timeout is not None:
299
352
  timeouts.append(retry.attempt_timeout)
300
- if retry.total_timeout is not None and total_deadline is not None:
353
+ if total_deadline is not None:
301
354
  timeouts.append(max(total_deadline - time.time(), retry.attempt_timeout_floor))
302
355
  if timeouts:
303
356
  timeout = min(timeouts) # In case the function provided both types of timeouts
@@ -307,6 +360,39 @@ async def _retry_transient_errors(
307
360
  with suppress_tb_frames(1):
308
361
  return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
309
362
  except (StreamTerminatedError, GRPCError, OSError, asyncio.TimeoutError, AttributeError) as exc:
363
+ # Server side instruction for retries
364
+ if isinstance(exc, GRPCError) and (server_retry_policy := get_server_retry_policy(exc)):
365
+ server_delay = server_retry_policy.retry_after_secs
366
+
367
+ # When `total_deadline` is not defined and server gives instruction for retries, then use
368
+ # `max_throttle_wait`.
369
+ if total_deadline is None and (max_throttle_wait := config.get("max_throttle_wait")):
370
+ total_deadline = t0 + max_throttle_wait
371
+
372
+ final_attempt = (
373
+ total_deadline is not None
374
+ and time.time() + server_delay + retry.attempt_timeout_floor >= total_deadline
375
+ )
376
+ with suppress_tb_frames(1):
377
+ process_exception_before_retry(
378
+ exc, final_attempt, fn.name, n_retries, server_delay, total_deadline, idempotency_key
379
+ )
380
+
381
+ now = time.time()
382
+ if last_server_retry_warning_time is None or (
383
+ now - last_server_retry_warning_time >= SERVER_RETRY_WARNING_TIME_INTERVAL
384
+ ):
385
+ last_server_retry_warning_time = now
386
+ logger.warning(
387
+ f"Warning: Received failure {exc.status}: {exc.message}. "
388
+ f"Will retry in {server_delay:0.2f} seconds."
389
+ )
390
+
391
+ n_throttled_retries += 1
392
+ await asyncio.sleep(server_delay)
393
+ continue
394
+
395
+ # Client handles retry
310
396
  if isinstance(exc, GRPCError) and exc.status not in status_codes:
311
397
  if exc.status == Status.UNAUTHENTICATED:
312
398
  raise AuthError(exc.message)
@@ -320,25 +406,9 @@ async def _retry_transient_errors(
320
406
  final_attempt = False
321
407
 
322
408
  with suppress_tb_frames(1):
323
- if final_attempt:
324
- logger.debug(
325
- f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} "
326
- f"{total_deadline=} for {fn.name} ({idempotency_key[:8]})"
327
- )
328
- if isinstance(exc, OSError):
329
- raise ConnectionError(str(exc))
330
- elif isinstance(exc, asyncio.TimeoutError):
331
- raise ConnectionError(str(exc))
332
- else:
333
- raise exc
334
-
335
- if isinstance(exc, AttributeError) and "_write_appdata" not in str(exc):
336
- # StreamTerminatedError are not properly raised in grpclib<=0.4.7
337
- # fixed in https://github.com/vmagamedov/grpclib/issues/185
338
- # TODO: update to newer version (>=0.4.8) once stable
339
- raise exc
340
-
341
- logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn.name} ({idempotency_key[:8]})")
409
+ process_exception_before_retry(
410
+ exc, final_attempt, fn.name, n_retries, delay, total_deadline, idempotency_key
411
+ )
342
412
 
343
413
  n_retries += 1
344
414
 
@@ -32,7 +32,7 @@ class _Client:
32
32
  server_url: str,
33
33
  client_type: int,
34
34
  credentials: typing.Optional[tuple[str, str]],
35
- version: str = "1.2.3.dev13",
35
+ version: str = "1.2.3.dev14",
36
36
  ):
37
37
  """mdmd:hidden
38
38
  The Modal client object is not intended to be instantiated directly by users.
@@ -163,7 +163,7 @@ class Client:
163
163
  server_url: str,
164
164
  client_type: int,
165
165
  credentials: typing.Optional[tuple[str, str]],
166
- version: str = "1.2.3.dev13",
166
+ version: str = "1.2.3.dev14",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -217,6 +217,13 @@ def _enforce_suffix_rules(x: str) -> str:
217
217
  return x
218
218
 
219
219
 
220
+ def _int_or_none(x: str) -> Optional[int]:
221
+ if not x:
222
+ return None
223
+ x_int = int(x)
224
+ return x_int if x_int > 0 else None
225
+
226
+
220
227
  class _Setting(typing.NamedTuple):
221
228
  default: typing.Any = None
222
229
  transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
@@ -258,6 +265,7 @@ _SETTINGS = {
258
265
  transform=lambda s: _check_value(["pickle", "cbor"])(s.lower()),
259
266
  ),
260
267
  "dev_suffix": _Setting("", transform=_enforce_suffix_rules),
268
+ "max_throttle_wait": _Setting(None, transform=_int_or_none),
261
269
  }
262
270
 
263
271
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.3.dev13
3
+ Version: 1.2.3.dev14
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -2642,6 +2642,12 @@ message QueuePutRequest {
2642
2642
  int32 partition_ttl_seconds = 6;
2643
2643
  }
2644
2644
 
2645
+ // Retry repolicy used by GRPCError.details for the server to give instructions
2646
+ // for the client to retry.
2647
+ message RPCRetryPolicy {
2648
+ float retry_after_secs = 1;
2649
+ }
2650
+
2645
2651
  // A copy google.rpc.Status for GRPCError.details:
2646
2652
  // https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
2647
2653
  // RPCStatus is compatible with google.rpc.Status, so one can encode messages using