modal 1.5.3.dev2__tar.gz → 1.5.3.dev4__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.3.dev2 → modal-1.5.3.dev4}/PKG-INFO +1 -1
  2. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_container_entrypoint.py +2 -4
  3. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_functions.py +26 -3
  4. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/container_io_manager.py +6 -30
  5. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/container_io_manager.pyi +5 -16
  6. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/grpc_utils.py +1 -1
  7. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/client.py +13 -7
  8. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/client.pyi +15 -13
  9. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cls.py +3 -0
  10. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/config.py +30 -1
  11. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/functions.pyi +16 -0
  12. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/sandbox.py +40 -7
  13. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/sandbox.pyi +95 -20
  14. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal.egg-info/PKG-INFO +1 -1
  15. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/api_grpc.py +16 -0
  16. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/api_pb2.py +1170 -1137
  17. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/api_pb2.pyi +83 -22
  18. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/api_pb2_grpc.py +33 -0
  19. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/api_pb2_grpc.pyi +10 -0
  20. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/modal_api_grpc.py +1 -0
  21. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/task_command_router_grpc.py +16 -0
  22. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2.py +33 -13
  23. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2.pyi +31 -0
  24. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2_grpc.py +37 -0
  25. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/task_command_router_pb2_grpc.pyi +20 -0
  26. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_version/__init__.py +1 -1
  27. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/LICENSE +0 -0
  28. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/README.md +0 -0
  29. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/__init__.py +0 -0
  30. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/__main__.py +0 -0
  31. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_billing.py +0 -0
  32. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_clustered_functions.py +0 -0
  33. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_clustered_functions.pyi +0 -0
  34. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_environments.py +0 -0
  35. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_function_variants.py +0 -0
  36. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_grpc_client.py +0 -0
  37. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_image.py +0 -0
  38. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_ipython.py +0 -0
  39. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_load_context.py +0 -0
  40. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_location.py +0 -0
  41. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_logs.py +0 -0
  42. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_object.py +0 -0
  43. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_output/__init__.py +0 -0
  44. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_output/manager.py +0 -0
  45. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_output/pty.py +0 -0
  46. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_output/rich.py +0 -0
  47. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_output/status.py +0 -0
  48. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_partial_function.py +0 -0
  49. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_resolver.py +0 -0
  50. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_resources.py +0 -0
  51. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/__init__.py +0 -0
  52. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/asgi.py +0 -0
  53. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/execution_context.py +0 -0
  54. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/execution_context.pyi +0 -0
  55. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  56. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/task_lifecycle_manager.py +0 -0
  57. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
  58. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/telemetry.py +0 -0
  59. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/user_code_event_loop.py +0 -0
  60. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_runtime/user_code_imports.py +0 -0
  61. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_serialization.py +0 -0
  62. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_server.py +0 -0
  63. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_traceback.py +0 -0
  64. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_tunnel.py +0 -0
  65. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_tunnel.pyi +0 -0
  66. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_type_manager.py +0 -0
  67. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/__init__.py +0 -0
  68. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/app_utils.py +0 -0
  69. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/async_utils.py +0 -0
  70. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/auth_token_manager.py +0 -0
  71. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/blob_utils.py +0 -0
  72. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/browser_utils.py +0 -0
  73. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
  74. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/curl_utils.py +0 -0
  75. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/deprecation.py +0 -0
  76. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/docker_utils.py +0 -0
  77. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/function_utils.py +0 -0
  78. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/git_utils.py +0 -0
  79. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/grpc_testing.py +0 -0
  80. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/hash_utils.py +0 -0
  81. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/http_utils.py +0 -0
  82. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/jwt_utils.py +0 -0
  83. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/logger.py +0 -0
  84. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/mount_utils.py +0 -0
  85. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/name_utils.py +0 -0
  86. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/package_utils.py +0 -0
  87. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/pattern_utils.py +0 -0
  88. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/proxy_support.py +0 -0
  89. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/rand_pb_testing.py +0 -0
  90. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/sandbox_fs_utils.py +0 -0
  91. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/shell_utils.py +0 -0
  92. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/task_command_router_client.py +0 -0
  93. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_utils/time_utils.py +0 -0
  94. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_vendor/__init__.py +0 -0
  95. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  96. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_vendor/cloudpickle.py +0 -0
  97. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_vendor/tblib.py +0 -0
  98. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_vendor/version.py +0 -0
  99. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_watcher.py +0 -0
  100. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/_workspace.py +0 -0
  101. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/app.py +0 -0
  102. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/app.pyi +0 -0
  103. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/billing.py +0 -0
  104. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/billing.pyi +0 -0
  105. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/2023.12.312.txt +0 -0
  106. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/2023.12.txt +0 -0
  107. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/2024.04.txt +0 -0
  108. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/2024.10.txt +0 -0
  109. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/2025.06.txt +0 -0
  110. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/PREVIEW.txt +0 -0
  111. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/README.md +0 -0
  112. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/builder/base-images.json +0 -0
  113. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/call_graph.py +0 -0
  114. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/__init__.py +0 -0
  115. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/_download.py +0 -0
  116. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/_help.py +0 -0
  117. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/_traceback.py +0 -0
  118. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/app.py +0 -0
  119. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/billing.py +0 -0
  120. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/bootstrap.py +0 -0
  121. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/changelog.py +0 -0
  122. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/cluster.py +0 -0
  123. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/config.py +0 -0
  124. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/container.py +0 -0
  125. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/curl.py +0 -0
  126. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/dashboard.py +0 -0
  127. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/dict.py +0 -0
  128. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/endpoint.py +0 -0
  129. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/entry_point.py +0 -0
  130. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/environment.py +0 -0
  131. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/image.py +0 -0
  132. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/import_refs.py +0 -0
  133. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/launch.py +0 -0
  134. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/logo.py +0 -0
  135. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/network_file_system.py +0 -0
  136. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/profile.py +0 -0
  137. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/programs/__init__.py +0 -0
  138. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/programs/run_jupyter.py +0 -0
  139. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/programs/vscode.py +0 -0
  140. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/queues.py +0 -0
  141. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/run.py +0 -0
  142. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/secret.py +0 -0
  143. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/selector.py +0 -0
  144. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/shell.py +0 -0
  145. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/skills.py +0 -0
  146. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/token.py +0 -0
  147. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/utils.py +0 -0
  148. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/volume.py +0 -0
  149. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cli/workspace.py +0 -0
  150. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cloud_bucket_mount.py +0 -0
  151. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cloud_bucket_mount.pyi +0 -0
  152. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/cls.pyi +0 -0
  153. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/container_process.py +0 -0
  154. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/container_process.pyi +0 -0
  155. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/dict.py +0 -0
  156. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/dict.pyi +0 -0
  157. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/environments.py +0 -0
  158. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/environments.pyi +0 -0
  159. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/exception.py +0 -0
  160. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/experimental/__init__.py +0 -0
  161. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/experimental/flash.py +0 -0
  162. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/experimental/flash.pyi +0 -0
  163. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/experimental/ipython.py +0 -0
  164. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/file_io.py +0 -0
  165. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/file_io.pyi +0 -0
  166. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/file_pattern_matcher.py +0 -0
  167. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/functions.py +0 -0
  168. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/image.py +0 -0
  169. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/image.pyi +0 -0
  170. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/io_streams.py +0 -0
  171. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/io_streams.pyi +0 -0
  172. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/mount.py +0 -0
  173. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/mount.pyi +0 -0
  174. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/network_file_system.py +0 -0
  175. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/network_file_system.pyi +0 -0
  176. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/object.py +0 -0
  177. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/object.pyi +0 -0
  178. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/output.py +0 -0
  179. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/parallel_map.py +0 -0
  180. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/parallel_map.pyi +0 -0
  181. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/partial_function.py +0 -0
  182. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/partial_function.pyi +0 -0
  183. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/proxy.py +0 -0
  184. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/proxy.pyi +0 -0
  185. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/py.typed +0 -0
  186. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/queue.py +0 -0
  187. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/queue.pyi +0 -0
  188. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/retries.py +0 -0
  189. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/runner.py +0 -0
  190. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/runner.pyi +0 -0
  191. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/running_app.py +0 -0
  192. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/sandbox_fs.py +0 -0
  193. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/sandbox_fs.pyi +0 -0
  194. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/schedule.py +0 -0
  195. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/scheduler_placement.py +0 -0
  196. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/secret.py +0 -0
  197. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/secret.pyi +0 -0
  198. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/server.py +0 -0
  199. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/server.pyi +0 -0
  200. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/serving.py +0 -0
  201. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/serving.pyi +0 -0
  202. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/skills/modal/SKILL.md +0 -0
  203. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/snapshot.py +0 -0
  204. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/snapshot.pyi +0 -0
  205. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/stream_type.py +0 -0
  206. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/token_flow.py +0 -0
  207. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/token_flow.pyi +0 -0
  208. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/types.py +0 -0
  209. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/volume.py +0 -0
  210. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/volume.pyi +0 -0
  211. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/workspace.py +0 -0
  212. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal/workspace.pyi +0 -0
  213. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal.egg-info/SOURCES.txt +0 -0
  214. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal.egg-info/dependency_links.txt +0 -0
  215. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal.egg-info/entry_points.txt +0 -0
  216. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal.egg-info/requires.txt +0 -0
  217. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal.egg-info/top_level.txt +0 -0
  218. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/__init__.py +0 -0
  219. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/gen_cli_docs.py +0 -0
  220. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/gen_cli_docs_main.py +0 -0
  221. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/gen_reference_docs.py +0 -0
  222. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/gen_reference_docs_main.py +0 -0
  223. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/mdmd/__init__.py +0 -0
  224. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/mdmd/mdmd.py +0 -0
  225. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/mdmd/signatures.py +0 -0
  226. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_docs/mdmd/types.py +0 -0
  227. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/__init__.py +0 -0
  228. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_proto/py.typed +0 -0
  229. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/modal_version/__main__.py +0 -0
  230. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/pyproject.toml +0 -0
  231. {modal-1.5.3.dev2 → modal-1.5.3.dev4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.5.3.dev2
3
+ Version: 1.5.3.dev4
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License-Expression: Apache-2.0
@@ -145,7 +145,7 @@ def call_function(
145
145
  item_count += 1
146
146
 
147
147
  await container_io_manager._send_outputs.aio(
148
- started_at, io_context.output_items_generator_done(started_at, item_count)
148
+ io_context.output_items_generator_done(started_at, item_count)
149
149
  )
150
150
  else:
151
151
  value = await io_context.call_function_async()
@@ -182,9 +182,7 @@ def call_function(
182
182
  container_io_manager._queue_put(generator_queue, value)
183
183
  item_count += 1
184
184
 
185
- container_io_manager._send_outputs(
186
- started_at, io_context.output_items_generator_done(started_at, item_count)
187
- )
185
+ container_io_manager._send_outputs(io_context.output_items_generator_done(started_at, item_count))
188
186
  else:
189
187
  values = io_context.call_function_sync()
190
188
  container_io_manager.push_outputs(io_context, started_at, values)
@@ -627,6 +627,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
627
627
  _metadata: api_pb2.FunctionHandleMetadata | None = None
628
628
  _options: _FunctionOptions
629
629
  _base_function: "_Function | None" = None
630
+ _app_id: str | None = None
630
631
 
631
632
  @staticmethod
632
633
  def from_local(
@@ -1403,6 +1404,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1403
1404
  metadata.max_object_size_bytes if metadata.HasField("max_object_size_bytes") else MAX_OBJECT_SIZE_BYTES
1404
1405
  )
1405
1406
  self._experimental_flash_urls = metadata._experimental_flash_urls
1407
+ self._app_id = metadata.app_id or None
1406
1408
 
1407
1409
  # Invalidate the Function variant cache when we load new metadata, since the base Function handle
1408
1410
  # is now in sync with the server but any previously-cached variants may be stale.
@@ -1412,6 +1414,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1412
1414
  # Overridden concrete implementation of base class method
1413
1415
  assert self._function_name, f"Function name must be set before metadata can be retrieved for {self}"
1414
1416
  return api_pb2.FunctionHandleMetadata(
1417
+ app_id=self._app_id or "",
1415
1418
  function_name=self._function_name,
1416
1419
  function_type=get_function_type(self._is_generator),
1417
1420
  web_url=self._web_url or "",
@@ -1673,6 +1676,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1673
1676
  )
1674
1677
  fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, None)
1675
1678
  fc._num_inputs = num_inputs # set the cached value of num_inputs
1679
+ fc._app_id = self._app_id
1680
+ fc._function_id = self.object_id
1676
1681
  return fc
1677
1682
 
1678
1683
  async def _call_function(self, args, kwargs) -> ReturnType:
@@ -1884,6 +1889,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1884
1889
  invocation.function_call_id, invocation.client, None
1885
1890
  )
1886
1891
  fc._is_generator = self._is_generator if self._is_generator else False
1892
+ fc._app_id = self._app_id
1893
+ fc._function_id = self.object_id
1887
1894
  return fc
1888
1895
 
1889
1896
  @synchronizer.no_input_translation
@@ -1920,6 +1927,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1920
1927
  fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(
1921
1928
  invocation.function_call_id, invocation.client, None
1922
1929
  )
1930
+ fc._app_id = self._app_id
1931
+ fc._function_id = self.object_id
1923
1932
  return fc
1924
1933
 
1925
1934
  def get_raw_f(self) -> Callable[..., Any]:
@@ -1979,10 +1988,18 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1979
1988
 
1980
1989
  _is_generator: bool = False
1981
1990
  _num_inputs: int | None = None
1991
+ _app_id: str | None = None
1992
+ _function_id: str | None = None
1982
1993
 
1983
1994
  def _invocation(self):
1984
1995
  return _Invocation(self.client.stub, self.object_id, self.client)
1985
1996
 
1997
+ async def _hydrate_from_id_metadata(self) -> None:
1998
+ """Hydrate metadata only when needed for FunctionCall fields."""
1999
+ request = api_pb2.FunctionCallFromIdRequest(function_call_id=self.object_id)
2000
+ resp = await self.client.stub.FunctionCallFromId(request)
2001
+ self._hydrate_metadata(resp)
2002
+
1986
2003
  @live_method
1987
2004
  async def num_inputs(self) -> int:
1988
2005
  """Get the number of inputs in the function call.
@@ -1991,9 +2008,8 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1991
2008
  How many inputs this function call includes (e.g. `1` for `.spawn()`, more for `.spawn_map()`).
1992
2009
  """
1993
2010
  if self._num_inputs is None:
1994
- request = api_pb2.FunctionCallFromIdRequest(function_call_id=self.object_id)
1995
- resp = await self.client.stub.FunctionCallFromId(request)
1996
- self._num_inputs = resp.num_inputs # cached
2011
+ await self._hydrate_from_id_metadata()
2012
+ assert self._num_inputs is not None
1997
2013
  return self._num_inputs
1998
2014
 
1999
2015
  @live_method
@@ -2135,6 +2151,13 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2135
2151
  )
2136
2152
  return typing.cast("modal.functions.FunctionCall[Any]", synchronizer._translate_out(impl_instance))
2137
2153
 
2154
+ def _hydrate_metadata(self, metadata: Message | None):
2155
+ # Overridden concrete implementation of base class method
2156
+ if isinstance(metadata, api_pb2.FunctionCallFromIdResponse):
2157
+ self._num_inputs = metadata.num_inputs
2158
+ self._app_id = metadata.metadata.app_id or None
2159
+ self._function_id = metadata.metadata.function_id or None
2160
+
2138
2161
  @staticmethod
2139
2162
  async def gather(*function_calls: "_FunctionCall[T]") -> typing.Sequence[T]:
2140
2163
  """Wait until all Modal FunctionCall objects have results before returning.
@@ -1,7 +1,6 @@
1
1
  # Copyright Modal Labs 2024
2
2
  import asyncio
3
3
  import inspect
4
- import math
5
4
  import sys
6
5
  import time
7
6
  import traceback
@@ -48,8 +47,6 @@ DYNAMIC_CONCURRENCY_INTERVAL_SECS = 3
48
47
  DYNAMIC_CONCURRENCY_TIMEOUT_SECS = 10
49
48
  MAX_OUTPUT_BATCH_SIZE: int = 49
50
49
 
51
- RTT_S: float = 0.5 # conservative estimate of RTT in seconds.
52
-
53
50
 
54
51
  class Sentinel:
55
52
  """Used to get type-stubs to work with this object."""
@@ -474,8 +471,6 @@ class _ContainerIOManager:
474
471
  _task_lifecycle_manager: _TaskLifecycleManager
475
472
  input_plane_server_url: str | None
476
473
 
477
- calls_completed: int
478
- total_user_time: float
479
474
  current_input_id: str | None
480
475
  current_inputs: dict[str, IOContext] # input_id -> IOContext
481
476
  current_input_started_at: float | None
@@ -510,8 +505,6 @@ class _ContainerIOManager:
510
505
 
511
506
  self.input_plane_server_url = container_args.input_plane_server_url
512
507
 
513
- self.calls_completed = 0
514
- self.total_user_time = 0.0
515
508
  self.current_input_id = None
516
509
  self.current_inputs = {}
517
510
  self.current_input_started_at = None
@@ -791,18 +784,6 @@ class _ContainerIOManager:
791
784
  """Put a value onto a queue, using the synchronicity event loop."""
792
785
  await queue.put(value)
793
786
 
794
- def get_average_call_time(self) -> float:
795
- if self.calls_completed == 0:
796
- return 0
797
-
798
- return self.total_user_time / self.calls_completed
799
-
800
- def get_max_inputs_to_fetch(self):
801
- if self.calls_completed == 0:
802
- return 1
803
-
804
- return math.ceil(RTT_S / max(self.get_average_call_time(), 1e-6))
805
-
806
787
  @synchronizer.no_io_translation
807
788
  async def _generate_inputs(
808
789
  self,
@@ -814,8 +795,6 @@ class _ContainerIOManager:
814
795
  while self._fetching_inputs:
815
796
  await self._input_slots.acquire()
816
797
 
817
- request.average_call_time = self.get_average_call_time()
818
- request.max_values = self.get_max_inputs_to_fetch() # Deprecated; remove.
819
798
  request.input_concurrency = self.get_input_concurrency()
820
799
  request.batch_max_size, request.batch_linger_ms = batch_max_size, batch_wait_ms
821
800
 
@@ -888,7 +867,7 @@ class _ContainerIOManager:
888
867
  # collect all active input slots, meaning all inputs have wrapped up.
889
868
  await self._input_slots.close()
890
869
 
891
- async def _send_outputs(self, started_at: float, outputs: list[api_pb2.FunctionPutOutputsItem]) -> None:
870
+ async def _send_outputs(self, outputs: list[api_pb2.FunctionPutOutputsItem]) -> None:
892
871
  """Send pre-built output items with retry and chunking."""
893
872
  # There are multiple outputs for a single IOContext in the case of @modal.batched.
894
873
  # Limit the batch size to 20 to stay within message size limits and buffer size limits.
@@ -902,7 +881,7 @@ class _ContainerIOManager:
902
881
  ),
903
882
  )
904
883
  input_ids = [output.input_id for output in outputs]
905
- self.exit_context(started_at, input_ids)
884
+ self.exit_context(input_ids)
906
885
 
907
886
  @asynccontextmanager
908
887
  async def handle_input_exception(
@@ -922,7 +901,7 @@ class _ContainerIOManager:
922
901
  raise
923
902
  except (InputCancellation, asyncio.CancelledError):
924
903
  outputs = await io_context.output_items_cancellation(started_at)
925
- await self._send_outputs(started_at, outputs)
904
+ await self._send_outputs(outputs)
926
905
  logger.warning(f"Successfully canceled input {io_context.input_ids}")
927
906
  return
928
907
  except BaseException as exc:
@@ -933,12 +912,9 @@ class _ContainerIOManager:
933
912
  # print exception so it's logged
934
913
  print_exception(*sys.exc_info())
935
914
  outputs = await io_context.output_items_exception(started_at, self.task_id, exc)
936
- await self._send_outputs(started_at, outputs)
937
-
938
- def exit_context(self, started_at, input_ids: list[str]):
939
- self.total_user_time += time.time() - started_at
940
- self.calls_completed += 1
915
+ await self._send_outputs(outputs)
941
916
 
917
+ def exit_context(self, input_ids: list[str]):
942
918
  for input_id in input_ids:
943
919
  self.current_inputs.pop(input_id)
944
920
 
@@ -954,7 +930,7 @@ class _ContainerIOManager:
954
930
  ) -> None:
955
931
  # The standard output encoding+sending method for successful function outputs
956
932
  outputs = await io_context.output_items(started_at, output_data)
957
- await self._send_outputs(started_at, outputs)
933
+ await self._send_outputs(outputs)
958
934
 
959
935
  @asynccontextmanager
960
936
  async def snapshot_context_manager(self) -> AsyncGenerator[None, None]:
@@ -100,8 +100,6 @@ class _ContainerIOManager:
100
100
  app_id: str
101
101
  _task_lifecycle_manager: modal._runtime.task_lifecycle_manager._TaskLifecycleManager
102
102
  input_plane_server_url: typing.Optional[str]
103
- calls_completed: int
104
- total_user_time: float
105
103
  current_input_id: typing.Optional[str]
106
104
  current_inputs: dict[str, IOContext]
107
105
  current_input_started_at: typing.Optional[float]
@@ -185,8 +183,6 @@ class _ContainerIOManager:
185
183
  """Put a value onto a queue, using the synchronicity event loop."""
186
184
  ...
187
185
 
188
- def get_average_call_time(self) -> float: ...
189
- def get_max_inputs_to_fetch(self): ...
190
186
  def _generate_inputs(
191
187
  self, batch_max_size: int, batch_wait_ms: int
192
188
  ) -> collections.abc.AsyncIterator[list[tuple[str, int, str, str, modal_proto.api_pb2.FunctionInput]]]: ...
@@ -196,7 +192,7 @@ class _ContainerIOManager:
196
192
  batch_max_size: int = 0,
197
193
  batch_wait_ms: int = 0,
198
194
  ) -> collections.abc.AsyncIterator[IOContext]: ...
199
- async def _send_outputs(self, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
195
+ async def _send_outputs(self, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
200
196
  """Send pre-built output items with retry and chunking."""
201
197
  ...
202
198
 
@@ -204,7 +200,7 @@ class _ContainerIOManager:
204
200
  """Handle an exception while processing a function input."""
205
201
  ...
206
202
 
207
- def exit_context(self, started_at, input_ids: list[str]): ...
203
+ def exit_context(self, input_ids: list[str]): ...
208
204
  async def push_outputs(self, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
209
205
  def snapshot_context_manager(self) -> typing.AsyncContextManager[None]: ...
210
206
  async def interact(self, from_breakpoint: bool = False): ...
@@ -246,8 +242,6 @@ class ContainerIOManager:
246
242
  app_id: str
247
243
  _task_lifecycle_manager: modal._runtime.task_lifecycle_manager.TaskLifecycleManager
248
244
  input_plane_server_url: typing.Optional[str]
249
- calls_completed: int
250
- total_user_time: float
251
245
  current_input_id: typing.Optional[str]
252
246
  current_inputs: dict[str, IOContext]
253
247
  current_input_started_at: typing.Optional[float]
@@ -413,9 +407,6 @@ class ContainerIOManager:
413
407
 
414
408
  _queue_put: ___queue_put_spec
415
409
 
416
- def get_average_call_time(self) -> float: ...
417
- def get_max_inputs_to_fetch(self): ...
418
-
419
410
  class ___generate_inputs_spec(typing_extensions.Protocol):
420
411
  def __call__(
421
412
  self, /, batch_max_size: int, batch_wait_ms: int
@@ -445,11 +436,11 @@ class ContainerIOManager:
445
436
  run_inputs_outputs: __run_inputs_outputs_spec
446
437
 
447
438
  class ___send_outputs_spec(typing_extensions.Protocol):
448
- def __call__(self, /, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
439
+ def __call__(self, /, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
449
440
  """Send pre-built output items with retry and chunking."""
450
441
  ...
451
442
 
452
- async def aio(self, /, started_at: float, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
443
+ async def aio(self, /, outputs: list[modal_proto.api_pb2.FunctionPutOutputsItem]) -> None:
453
444
  """Send pre-built output items with retry and chunking."""
454
445
  ...
455
446
 
@@ -468,7 +459,7 @@ class ContainerIOManager:
468
459
 
469
460
  handle_input_exception: __handle_input_exception_spec
470
461
 
471
- def exit_context(self, started_at, input_ids: list[str]): ...
462
+ def exit_context(self, input_ids: list[str]): ...
472
463
 
473
464
  class __push_outputs_spec(typing_extensions.Protocol):
474
465
  def __call__(self, /, io_context: IOContext, started_at: float, output_data: list[typing.Any]) -> None: ...
@@ -517,5 +508,3 @@ class ContainerIOManager:
517
508
  def stop_fetching_inputs(cls): ...
518
509
 
519
510
  MAX_OUTPUT_BATCH_SIZE: int
520
-
521
- RTT_S: float
@@ -325,7 +325,7 @@ def create_channel(
325
325
  async def create_channel_with_fallbacks(
326
326
  server_url: str,
327
327
  metadata: dict[str, str] = {},
328
- stagger_delay: float = 1.0,
328
+ stagger_delay: float = 3.0,
329
329
  ) -> grpclib.client.Channel:
330
330
  """Create a connected channel, supporting comma-separated fallback URLs.
331
331
 
@@ -6,7 +6,7 @@ import sys
6
6
  import urllib.parse
7
7
  import warnings
8
8
  from collections.abc import AsyncGenerator, Collection, Mapping
9
- from typing import Any, ClassVar, TypeVar
9
+ from typing import Any, ClassVar, Coroutine, TypeVar
10
10
 
11
11
  import grpclib.client
12
12
  from google.protobuf import empty_pb2
@@ -41,7 +41,7 @@ def _get_metadata(client_type: int, credentials: tuple[str, str] | None, version
41
41
  # sys.version_info is structured unlike sys.version or platform.python_version()
42
42
  python_version = "%d.%d.%d" % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro)
43
43
 
44
- metadata = {
44
+ metadata: dict[str, str] = {
45
45
  "x-modal-client-version": version,
46
46
  "x-modal-client-type": str(client_type),
47
47
  "x-modal-python-version": python_version,
@@ -58,9 +58,15 @@ def _get_metadata(client_type: int, credentials: tuple[str, str] | None, version
58
58
  )
59
59
  if agent_env := _agent_environment():
60
60
  metadata["x-modal-agent-harness"] = urllib.parse.quote(agent_env)
61
+
62
+ override_headers = config.get("override_headers")
63
+ if override_headers:
64
+ metadata.update(override_headers)
65
+
61
66
  return metadata
62
67
 
63
68
 
69
+ T = TypeVar("T")
64
70
  ReturnType = TypeVar("ReturnType")
65
71
  _Value = str | bytes
66
72
  _MetadataLike = Mapping[str, _Value] | Collection[tuple[str, _Value]]
@@ -308,7 +314,7 @@ class _Client:
308
314
  ("x-modal-auth-token", token),
309
315
  ]
310
316
 
311
- async def _call_safely(self, coro, readable_method: str):
317
+ async def _call_safely(self, coro: Coroutine[Any, Any, T], readable_method: str) -> T:
312
318
  """Runs coroutine wrapped in a task that's part of the client's task context
313
319
 
314
320
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -364,11 +370,11 @@ class _Client:
364
370
  async def _call_unary(
365
371
  self,
366
372
  grpclib_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
367
- request: Any,
373
+ request: RequestType,
368
374
  *,
369
375
  timeout: float | None = None,
370
376
  metadata: _MetadataLike | None = None,
371
- ) -> Any:
377
+ ) -> ResponseType:
372
378
  coro = grpclib_method(request, timeout=timeout, metadata=metadata)
373
379
  return await self._call_safely(coro, grpclib_method.name)
374
380
 
@@ -376,10 +382,10 @@ class _Client:
376
382
  async def _call_stream(
377
383
  self,
378
384
  grpclib_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
379
- request: Any,
385
+ request: RequestType,
380
386
  *,
381
387
  metadata: _MetadataLike | None,
382
- ) -> AsyncGenerator[Any, None]:
388
+ ) -> AsyncGenerator[ResponseType, None]:
383
389
  stream_context = grpclib_method.open(metadata=metadata)
384
390
  stream = await self._call_safely(stream_context.__aenter__(), f"{grpclib_method.name}.open")
385
391
  try:
@@ -13,6 +13,8 @@ import typing_extensions
13
13
 
14
14
  def _get_metadata(client_type: int, credentials: typing.Optional[tuple[str, str]], version: str) -> dict[str, str]: ...
15
15
 
16
+ T = typing.TypeVar("T")
17
+
16
18
  ReturnType = typing.TypeVar("ReturnType")
17
19
 
18
20
  RequestType = typing.TypeVar("RequestType", bound="google.protobuf.message.Message")
@@ -35,7 +37,7 @@ class _Client:
35
37
  server_url: str,
36
38
  client_type: int,
37
39
  credentials: typing.Optional[tuple[str, str]],
38
- version: str = "1.5.3.dev2",
40
+ version: str = "1.5.3.dev4",
39
41
  ):
40
42
  """mdmd:hidden
41
43
  The Modal client object is not intended to be instantiated directly by users.
@@ -154,7 +156,7 @@ class _Client:
154
156
  """
155
157
  ...
156
158
 
157
- async def _call_safely(self, coro, readable_method: str):
159
+ async def _call_safely(self, coro: typing.Coroutine[typing.Any, typing.Any, T], readable_method: str) -> T:
158
160
  """Runs coroutine wrapped in a task that's part of the client's task context
159
161
 
160
162
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -168,7 +170,7 @@ class _Client:
168
170
  async def _call_unary(
169
171
  self,
170
172
  grpclib_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
171
- request: typing.Any,
173
+ request: RequestType,
172
174
  *,
173
175
  timeout: typing.Optional[float] = None,
174
176
  metadata: typing.Union[
@@ -176,18 +178,18 @@ class _Client:
176
178
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
177
179
  None,
178
180
  ] = None,
179
- ) -> typing.Any: ...
181
+ ) -> ResponseType: ...
180
182
  def _call_stream(
181
183
  self,
182
184
  grpclib_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
183
- request: typing.Any,
185
+ request: RequestType,
184
186
  *,
185
187
  metadata: typing.Union[
186
188
  collections.abc.Mapping[str, typing.Union[str, bytes]],
187
189
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
188
190
  None,
189
191
  ],
190
- ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
192
+ ) -> collections.abc.AsyncGenerator[ResponseType, None]: ...
191
193
 
192
194
  class Client:
193
195
  _client_from_env: typing.ClassVar[typing.Optional[Client]]
@@ -205,7 +207,7 @@ class Client:
205
207
  server_url: str,
206
208
  client_type: int,
207
209
  credentials: typing.Optional[tuple[str, str]],
208
- version: str = "1.5.3.dev2",
210
+ version: str = "1.5.3.dev4",
209
211
  ):
210
212
  """mdmd:hidden
211
213
  The Modal client object is not intended to be instantiated directly by users.
@@ -429,7 +431,7 @@ class Client:
429
431
  get_input_plane_metadata: __get_input_plane_metadata_spec
430
432
 
431
433
  class ___call_safely_spec(typing_extensions.Protocol):
432
- def __call__(self, /, coro, readable_method: str):
434
+ def __call__(self, /, coro: T, readable_method: str) -> T:
433
435
  """Runs coroutine wrapped in a task that's part of the client's task context
434
436
 
435
437
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -438,7 +440,7 @@ class Client:
438
440
  """
439
441
  ...
440
442
 
441
- async def aio(self, /, coro, readable_method: str):
443
+ async def aio(self, /, coro: typing.Coroutine[typing.Any, typing.Any, T], readable_method: str) -> T:
442
444
  """Runs coroutine wrapped in a task that's part of the client's task context
443
445
 
444
446
  * Raises ClientClosed in case the client is closed while the coroutine is executed
@@ -464,7 +466,7 @@ class Client:
464
466
  async def _call_unary(
465
467
  self,
466
468
  grpclib_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
467
- request: typing.Any,
469
+ request: RequestType,
468
470
  *,
469
471
  timeout: typing.Optional[float] = None,
470
472
  metadata: typing.Union[
@@ -472,18 +474,18 @@ class Client:
472
474
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
473
475
  None,
474
476
  ] = None,
475
- ) -> typing.Any: ...
477
+ ) -> ResponseType: ...
476
478
  def _call_stream(
477
479
  self,
478
480
  grpclib_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
479
- request: typing.Any,
481
+ request: RequestType,
480
482
  *,
481
483
  metadata: typing.Union[
482
484
  collections.abc.Mapping[str, typing.Union[str, bytes]],
483
485
  collections.abc.Collection[tuple[str, typing.Union[str, bytes]]],
484
486
  None,
485
487
  ],
486
- ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
488
+ ) -> collections.abc.AsyncGenerator[ResponseType, None]: ...
487
489
 
488
490
  HEARTBEAT_INTERVAL: float
489
491
 
@@ -86,6 +86,8 @@ def _bind_instance_method(cls: "_Cls", service_function: _Function, method_name:
86
86
 
87
87
  method_metadata = service_function._method_handle_metadata[method_name]
88
88
  new_function._hydrate(service_function.object_id, service_function.client, method_metadata)
89
+ if not new_function._app_id:
90
+ new_function._app_id = service_function._app_id
89
91
 
90
92
  async def _load(fun: "_Function", resolver: Resolver, load_context: LoadContext, existing_object_id: str | None):
91
93
  # there is currently no actual loading logic executed to create each method on
@@ -426,6 +428,7 @@ class _Obj:
426
428
  )
427
429
  await resolver.load(method_function, load_context) # get the appropriate method handle (lazy)
428
430
  fun._hydrate_from_other(method_function)
431
+ fun._app_id = method_function._app_id
429
432
 
430
433
  # The reason we don't *always* use this lazy loader is because it precludes attribute access
431
434
  # on local classes.
@@ -106,7 +106,8 @@ from modal_proto import api_pb2
106
106
  from ._utils.logger import configure_logger
107
107
  from .exception import InvalidError, NotFoundError
108
108
 
109
- DEFAULT_SERVER_URL = "https://api.modal.com" # TODO(erikbern): add modal2.com
109
+ # By default, try api.modal.com and fail over to api.modal2.com
110
+ DEFAULT_SERVER_URL = "https://api.modal.com,https://api.modal2.com"
110
111
 
111
112
 
112
113
  # Locate config file and read it
@@ -263,6 +264,33 @@ def _enforce_suffix_rules(x: str) -> str:
263
264
  return x
264
265
 
265
266
 
267
+ def _transform_headers(s: str) -> dict[str, str]:
268
+ error_msg = (
269
+ "Must be a comma-separated list of `<header>:<value>` pairs (whitespace is ignored). "
270
+ "<header> and <value> must contain at least one non-whitespace character, and may "
271
+ "not contain commas."
272
+ )
273
+
274
+ if s == "":
275
+ return {}
276
+
277
+ pairs = s.split(",")
278
+
279
+ headers: dict[str, str] = {}
280
+ for pair in pairs:
281
+ res = pair.split(":", maxsplit=1)
282
+ if len(res) != 2:
283
+ raise ValueError(error_msg)
284
+
285
+ header, value = res[0].strip(), res[1].strip()
286
+ if len(header) == 0 or len(value) == 0:
287
+ raise ValueError(error_msg)
288
+
289
+ headers[header] = value
290
+
291
+ return headers
292
+
293
+
266
294
  class _Setting(typing.NamedTuple):
267
295
  default: typing.Any = None
268
296
  transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
@@ -305,6 +333,7 @@ _SETTINGS = {
305
333
  "max_throttle_wait": _Setting(None, transform=lambda x: int(x) if x else None),
306
334
  "async_warnings": _Setting(True, transform=_to_boolean), # Activate synchronicity usage warnings
307
335
  "disable_api_proxy": _Setting(False, transform=_to_boolean),
336
+ "override_headers": _Setting(None, transform=_transform_headers),
308
337
  }
309
338
 
310
339
 
@@ -58,6 +58,7 @@ class Function(
58
58
  _metadata: typing.Optional[modal_proto.api_pb2.FunctionHandleMetadata]
59
59
  _options: modal._function_variants._FunctionOptions
60
60
  _base_function: typing.Optional[Function]
61
+ _app_id: typing.Optional[str]
61
62
 
62
63
  def __init__(self, *args, **kwargs):
63
64
  """mdmd:hidden"""
@@ -892,6 +893,8 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
892
893
 
893
894
  _is_generator: bool
894
895
  _num_inputs: typing.Optional[int]
896
+ _app_id: typing.Optional[str]
897
+ _function_id: typing.Optional[str]
895
898
 
896
899
  def __init__(self, *args, **kwargs):
897
900
  """mdmd:hidden"""
@@ -899,6 +902,17 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
899
902
 
900
903
  def _invocation(self): ...
901
904
 
905
+ class ___hydrate_from_id_metadata_spec(typing_extensions.Protocol):
906
+ def __call__(self, /) -> None:
907
+ """Hydrate metadata only when needed for FunctionCall fields."""
908
+ ...
909
+
910
+ async def aio(self, /) -> None:
911
+ """Hydrate metadata only when needed for FunctionCall fields."""
912
+ ...
913
+
914
+ _hydrate_from_id_metadata: ___hydrate_from_id_metadata_spec
915
+
902
916
  class __num_inputs_spec(typing_extensions.Protocol):
903
917
  def __call__(self, /) -> int:
904
918
  """Get the number of inputs in the function call.
@@ -1115,6 +1129,8 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
1115
1129
 
1116
1130
  from_id: typing.ClassVar[__from_id_spec]
1117
1131
 
1132
+ def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
1133
+
1118
1134
  class __gather_spec(typing_extensions.Protocol):
1119
1135
  def __call__(self, /, *function_calls: FunctionCall[modal._functions.T]) -> typing.Sequence[modal._functions.T]:
1120
1136
  """Wait until all Modal FunctionCall objects have results before returning.