modAL 1.2.3.dev12__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.dev12 → modal-1.2.3.dev14}/PKG-INFO +1 -1
  2. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_functions.py +1 -0
  3. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_load_context.py +2 -1
  4. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_object.py +12 -6
  5. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/grpc_utils.py +90 -20
  6. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/client.py +9 -9
  7. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/client.pyi +8 -9
  8. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/config.py +8 -0
  9. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/functions.pyi +6 -6
  10. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal.egg-info/PKG-INFO +1 -1
  11. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/api.proto +6 -0
  12. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/api_pb2.py +363 -353
  13. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/api_pb2.pyi +18 -0
  14. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_version/__init__.py +1 -1
  15. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/LICENSE +0 -0
  16. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/README.md +0 -0
  17. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/__init__.py +0 -0
  18. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/__main__.py +0 -0
  19. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_billing.py +0 -0
  20. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_clustered_functions.py +0 -0
  21. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_clustered_functions.pyi +0 -0
  22. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_container_entrypoint.py +0 -0
  23. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_grpc_client.py +0 -0
  24. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_ipython.py +0 -0
  25. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_location.py +0 -0
  26. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_output.py +0 -0
  27. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_partial_function.py +0 -0
  28. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_pty.py +0 -0
  29. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_resolver.py +0 -0
  30. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_resources.py +0 -0
  31. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/__init__.py +0 -0
  32. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/asgi.py +0 -0
  33. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/container_io_manager.py +0 -0
  34. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
  35. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/execution_context.py +0 -0
  36. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/execution_context.pyi +0 -0
  37. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  38. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/telemetry.py +0 -0
  39. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_runtime/user_code_imports.py +0 -0
  40. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_serialization.py +0 -0
  41. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_traceback.py +0 -0
  42. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_tunnel.py +0 -0
  43. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_tunnel.pyi +0 -0
  44. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_type_manager.py +0 -0
  45. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/__init__.py +0 -0
  46. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/app_utils.py +0 -0
  47. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/async_utils.py +0 -0
  48. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/auth_token_manager.py +0 -0
  49. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/blob_utils.py +0 -0
  50. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
  51. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/deprecation.py +0 -0
  52. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/docker_utils.py +0 -0
  53. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/function_utils.py +0 -0
  54. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/git_utils.py +0 -0
  55. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/grpc_testing.py +0 -0
  56. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/hash_utils.py +0 -0
  57. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/http_utils.py +0 -0
  58. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/jwt_utils.py +0 -0
  59. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/logger.py +0 -0
  60. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/mount_utils.py +0 -0
  61. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/name_utils.py +0 -0
  62. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/package_utils.py +0 -0
  63. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/pattern_utils.py +0 -0
  64. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/rand_pb_testing.py +0 -0
  65. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/shell_utils.py +0 -0
  66. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/task_command_router_client.py +0 -0
  67. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_utils/time_utils.py +0 -0
  68. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_vendor/__init__.py +0 -0
  69. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  70. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_vendor/cloudpickle.py +0 -0
  71. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_vendor/tblib.py +0 -0
  72. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/_watcher.py +0 -0
  73. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/app.py +0 -0
  74. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/app.pyi +0 -0
  75. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/billing.py +0 -0
  76. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/2023.12.312.txt +0 -0
  77. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/2023.12.txt +0 -0
  78. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/2024.04.txt +0 -0
  79. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/2024.10.txt +0 -0
  80. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/2025.06.txt +0 -0
  81. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/PREVIEW.txt +0 -0
  82. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/README.md +0 -0
  83. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/builder/base-images.json +0 -0
  84. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/call_graph.py +0 -0
  85. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/__init__.py +0 -0
  86. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/_download.py +0 -0
  87. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/_traceback.py +0 -0
  88. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/app.py +0 -0
  89. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/cluster.py +0 -0
  90. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/config.py +0 -0
  91. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/container.py +0 -0
  92. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/dict.py +0 -0
  93. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/entry_point.py +0 -0
  94. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/environment.py +0 -0
  95. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/import_refs.py +0 -0
  96. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/launch.py +0 -0
  97. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/network_file_system.py +0 -0
  98. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/profile.py +0 -0
  99. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/programs/__init__.py +0 -0
  100. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/programs/launch_instance_ssh.py +0 -0
  101. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/programs/run_jupyter.py +0 -0
  102. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/programs/run_marimo.py +0 -0
  103. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/programs/vscode.py +0 -0
  104. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/queues.py +0 -0
  105. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/run.py +0 -0
  106. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/secret.py +0 -0
  107. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/token.py +0 -0
  108. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/utils.py +0 -0
  109. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cli/volume.py +0 -0
  110. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cloud_bucket_mount.py +0 -0
  111. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cloud_bucket_mount.pyi +0 -0
  112. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cls.py +0 -0
  113. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/cls.pyi +0 -0
  114. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/container_process.py +0 -0
  115. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/container_process.pyi +0 -0
  116. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/dict.py +0 -0
  117. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/dict.pyi +0 -0
  118. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/environments.py +0 -0
  119. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/environments.pyi +0 -0
  120. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/exception.py +0 -0
  121. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/experimental/__init__.py +0 -0
  122. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/experimental/flash.py +0 -0
  123. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/experimental/flash.pyi +0 -0
  124. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/experimental/ipython.py +0 -0
  125. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/file_io.py +0 -0
  126. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/file_io.pyi +0 -0
  127. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/file_pattern_matcher.py +0 -0
  128. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/functions.py +0 -0
  129. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/gpu.py +0 -0
  130. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/image.py +0 -0
  131. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/image.pyi +0 -0
  132. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/io_streams.py +0 -0
  133. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/io_streams.pyi +0 -0
  134. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/mount.py +0 -0
  135. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/mount.pyi +0 -0
  136. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/network_file_system.py +0 -0
  137. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/network_file_system.pyi +0 -0
  138. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/object.py +0 -0
  139. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/object.pyi +0 -0
  140. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/output.py +0 -0
  141. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/parallel_map.py +0 -0
  142. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/parallel_map.pyi +0 -0
  143. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/partial_function.py +0 -0
  144. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/partial_function.pyi +0 -0
  145. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/proxy.py +0 -0
  146. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/proxy.pyi +0 -0
  147. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/py.typed +0 -0
  148. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/queue.py +0 -0
  149. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/queue.pyi +0 -0
  150. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/retries.py +0 -0
  151. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/runner.py +0 -0
  152. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/runner.pyi +0 -0
  153. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/running_app.py +0 -0
  154. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/sandbox.py +0 -0
  155. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/sandbox.pyi +0 -0
  156. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/schedule.py +0 -0
  157. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/scheduler_placement.py +0 -0
  158. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/secret.py +0 -0
  159. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/secret.pyi +0 -0
  160. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/serving.py +0 -0
  161. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/serving.pyi +0 -0
  162. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/snapshot.py +0 -0
  163. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/snapshot.pyi +0 -0
  164. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/stream_type.py +0 -0
  165. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/token_flow.py +0 -0
  166. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/token_flow.pyi +0 -0
  167. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/volume.py +0 -0
  168. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal/volume.pyi +0 -0
  169. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal.egg-info/SOURCES.txt +0 -0
  170. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal.egg-info/dependency_links.txt +0 -0
  171. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal.egg-info/entry_points.txt +0 -0
  172. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal.egg-info/requires.txt +0 -0
  173. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal.egg-info/top_level.txt +0 -0
  174. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_docs/__init__.py +0 -0
  175. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_docs/gen_cli_docs.py +0 -0
  176. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_docs/gen_reference_docs.py +0 -0
  177. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_docs/mdmd/__init__.py +0 -0
  178. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_docs/mdmd/mdmd.py +0 -0
  179. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_docs/mdmd/signatures.py +0 -0
  180. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/__init__.py +0 -0
  181. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/api_grpc.py +0 -0
  182. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/api_pb2_grpc.py +0 -0
  183. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/api_pb2_grpc.pyi +0 -0
  184. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/modal_api_grpc.py +0 -0
  185. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/py.typed +0 -0
  186. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/sandbox_router.proto +0 -0
  187. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/sandbox_router_grpc.py +0 -0
  188. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2.py +0 -0
  189. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2.pyi +0 -0
  190. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  191. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  192. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/task_command_router.proto +0 -0
  193. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/task_command_router_grpc.py +0 -0
  194. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2.py +0 -0
  195. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
  196. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  197. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  198. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/modal_version/__main__.py +0 -0
  199. {modal-1.2.3.dev12 → modal-1.2.3.dev14}/pyproject.toml +0 -0
  200. {modal-1.2.3.dev12 → 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.dev12
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
@@ -536,6 +536,7 @@ class _InputPlaneInvocation:
536
536
  async def _get_metadata(input_plane_region: str, client: _Client) -> list[tuple[str, str]]:
537
537
  if not input_plane_region:
538
538
  return []
539
+ assert client._auth_token_manager, "Client is not open"
539
540
  token = await client._auth_token_manager.get_token()
540
541
  return [("x-modal-input-plane-region", input_plane_region), ("x-modal-auth-token", token)]
541
542
 
@@ -66,8 +66,9 @@ class LoadContext:
66
66
 
67
67
  Returns a new instance (no in place mutation)"""
68
68
 
69
+ is_valid_client = self._client is not None and not self._client._snapshotted
69
70
  return LoadContext(
70
- client=await _Client.from_env() if self._client is None else self.client,
71
+ client=self.client if is_valid_client else await _Client.from_env(),
71
72
  environment_name=self._environment_name or config.get("environment") or "",
72
73
  app_id=self._app_id,
73
74
  )
@@ -43,7 +43,7 @@ class _Object:
43
43
  _prefix_to_type: ClassVar[dict[str, type]] = {}
44
44
 
45
45
  # For constructors
46
- _load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]]
46
+ _load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None
47
47
  _preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]]
48
48
  _rep: str
49
49
  _is_another_app: bool
@@ -308,11 +308,17 @@ class _Object:
308
308
  # memory snapshots capture references which must be rehydrated
309
309
  # on restore to handle staleness.
310
310
  logger.debug(f"rehydrating {self} after snapshot")
311
- self._is_hydrated = False # un-hydrate and re-resolve
312
- # Set the client on LoadContext before loading
313
- root_load_context = LoadContext(client=client)
314
- resolver = Resolver()
315
- await resolver.load(typing.cast(_Object, self), root_load_context)
311
+ if self._hydrate_lazily:
312
+ logger.debug(f"reloading lazy {self} from server")
313
+ self._is_hydrated = False # un-hydrate and re-resolve
314
+ # we don't set an explicit Client here, relying on the default
315
+ # env client to be applied by LoadContext.apply_default
316
+ root_load_context = LoadContext.empty()
317
+ resolver = Resolver()
318
+ await resolver.load(typing.cast(_Object, self), root_load_context)
319
+ else:
320
+ logger.debug(f"reloading non-lazy {self} by replacing client")
321
+ self._client = client or await _Client.from_env()
316
322
  self._is_rehydrated = True
317
323
  logger.debug(f"rehydrated {self} with client {id(self.client)}")
318
324
  elif not self._hydrate_lazily:
@@ -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
 
@@ -5,7 +5,7 @@ import platform
5
5
  import sys
6
6
  import urllib.parse
7
7
  import warnings
8
- from collections.abc import AsyncGenerator, AsyncIterator, Collection, Mapping
8
+ from collections.abc import AsyncGenerator, Collection, Mapping
9
9
  from typing import Any, ClassVar, Optional, TypeVar, Union
10
10
 
11
11
  import grpclib.client
@@ -14,7 +14,7 @@ from google.protobuf.message import Message
14
14
  from synchronicity.async_wrap import asynccontextmanager
15
15
 
16
16
  from modal._utils.async_utils import synchronizer
17
- from modal_proto import api_grpc, api_pb2, modal_api_grpc
17
+ from modal_proto import api_pb2, modal_api_grpc
18
18
  from modal_version import __version__
19
19
 
20
20
  from ._traceback import print_server_warnings
@@ -70,10 +70,10 @@ class _Client:
70
70
  _client_from_env: ClassVar[Optional["_Client"]] = None
71
71
  _client_from_env_lock: ClassVar[Optional[asyncio.Lock]] = None
72
72
  _cancellation_context: TaskContext
73
- _cancellation_context_event_loop: asyncio.AbstractEventLoop = None
74
- _stub: Optional[api_grpc.ModalClientStub]
75
- _auth_token_manager: _AuthTokenManager = None
76
- _snapshotted: bool
73
+ _cancellation_context_event_loop: Optional[asyncio.AbstractEventLoop] = None
74
+ _stub: Optional[modal_api_grpc.ModalClientModal]
75
+ _auth_token_manager: Optional[_AuthTokenManager] = None
76
+ _snapshotted: bool = False
77
77
 
78
78
  def __init__(
79
79
  self,
@@ -90,8 +90,8 @@ class _Client:
90
90
  self._credentials = credentials
91
91
  self.version = version
92
92
  self._closed = False
93
- self._stub: Optional[modal_api_grpc.ModalClientModal] = None
94
- self._auth_token_manager: Optional[_AuthTokenManager] = None
93
+ self._stub = None
94
+ self._auth_token_manager = None
95
95
  self._snapshotted = False
96
96
  self._owner_pid = None
97
97
 
@@ -163,7 +163,7 @@ class _Client:
163
163
 
164
164
  @classmethod
165
165
  @asynccontextmanager
166
- async def anonymous(cls, server_url: str) -> AsyncIterator["_Client"]:
166
+ async def anonymous(cls, server_url: str) -> AsyncGenerator["_Client", None]:
167
167
  """mdmd:hidden
168
168
  Create a connection with no credentials; to be used for token creation.
169
169
  """
@@ -5,7 +5,6 @@ import google.protobuf.message
5
5
  import grpclib.client
6
6
  import modal._utils.async_utils
7
7
  import modal._utils.auth_token_manager
8
- import modal_proto.api_grpc
9
8
  import modal_proto.modal_api_grpc
10
9
  import synchronicity.combined_types
11
10
  import typing
@@ -23,9 +22,9 @@ class _Client:
23
22
  _client_from_env: typing.ClassVar[typing.Optional[_Client]]
24
23
  _client_from_env_lock: typing.ClassVar[typing.Optional[asyncio.locks.Lock]]
25
24
  _cancellation_context: modal._utils.async_utils.TaskContext
26
- _cancellation_context_event_loop: asyncio.events.AbstractEventLoop
27
- _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
28
- _auth_token_manager: modal._utils.auth_token_manager._AuthTokenManager
25
+ _cancellation_context_event_loop: typing.Optional[asyncio.events.AbstractEventLoop]
26
+ _stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
27
+ _auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
29
28
  _snapshotted: bool
30
29
 
31
30
  def __init__(
@@ -33,7 +32,7 @@ class _Client:
33
32
  server_url: str,
34
33
  client_type: int,
35
34
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.2.3.dev12",
35
+ version: str = "1.2.3.dev14",
37
36
  ):
38
37
  """mdmd:hidden
39
38
  The Modal client object is not intended to be instantiated directly by users.
@@ -154,9 +153,9 @@ class Client:
154
153
  _client_from_env: typing.ClassVar[typing.Optional[Client]]
155
154
  _client_from_env_lock: typing.ClassVar[typing.Optional[asyncio.locks.Lock]]
156
155
  _cancellation_context: modal._utils.async_utils.TaskContext
157
- _cancellation_context_event_loop: asyncio.events.AbstractEventLoop
158
- _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
159
- _auth_token_manager: modal._utils.auth_token_manager._AuthTokenManager
156
+ _cancellation_context_event_loop: typing.Optional[asyncio.events.AbstractEventLoop]
157
+ _stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
158
+ _auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
160
159
  _snapshotted: bool
161
160
 
162
161
  def __init__(
@@ -164,7 +163,7 @@ class Client:
164
163
  server_url: str,
165
164
  client_type: int,
166
165
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.2.3.dev12",
166
+ version: str = "1.2.3.dev14",
168
167
  ):
169
168
  """mdmd:hidden
170
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
 
@@ -408,7 +408,7 @@ class Function(
408
408
 
409
409
  _call_generator: ___call_generator_spec[typing_extensions.Self]
410
410
 
411
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
411
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
412
412
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
413
413
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
414
414
  ...
@@ -417,7 +417,7 @@ class Function(
417
417
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
418
418
  ...
419
419
 
420
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
420
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
421
421
 
422
422
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
423
423
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -444,7 +444,7 @@ class Function(
444
444
  """
445
445
  ...
446
446
 
447
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
447
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
448
448
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
449
449
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
450
450
 
@@ -468,7 +468,7 @@ class Function(
468
468
  ...
469
469
 
470
470
  _experimental_spawn: ___experimental_spawn_spec[
471
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
471
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
472
472
  ]
473
473
 
474
474
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -477,7 +477,7 @@ class Function(
477
477
 
478
478
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
479
479
 
480
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
480
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
481
481
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
482
482
  """Calls the function with the given arguments, without waiting for the results.
483
483
 
@@ -498,7 +498,7 @@ class Function(
498
498
  """
499
499
  ...
500
500
 
501
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
501
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
502
502
 
503
503
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
504
504
  """Return the inner Python object wrapped by this Modal Function."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.3.dev12
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