modAL 1.2.2__tar.gz → 1.2.2.dev2__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 (201) hide show
  1. {modal-1.2.2 → modal-1.2.2.dev2}/PKG-INFO +2 -2
  2. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_clustered_functions.py +3 -1
  3. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_functions.py +97 -106
  4. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_object.py +18 -47
  5. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_output.py +4 -3
  6. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_resolver.py +35 -21
  7. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/container_io_manager.py +22 -21
  8. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/async_utils.py +3 -12
  9. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/auth_token_manager.py +4 -1
  10. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/blob_utils.py +4 -3
  11. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/function_utils.py +2 -1
  12. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/grpc_utils.py +51 -80
  13. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/task_command_router_client.py +4 -3
  14. {modal-1.2.2 → modal-1.2.2.dev2}/modal/app.py +4 -10
  15. {modal-1.2.2 → modal-1.2.2.dev2}/modal/app.pyi +0 -3
  16. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/config.py +1 -3
  17. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/container.py +2 -1
  18. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/dict.py +2 -5
  19. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/network_file_system.py +4 -1
  20. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/queues.py +4 -5
  21. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/secret.py +2 -1
  22. {modal-1.2.2 → modal-1.2.2.dev2}/modal/client.py +115 -5
  23. {modal-1.2.2 → modal-1.2.2.dev2}/modal/client.pyi +99 -2
  24. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cloud_bucket_mount.py +0 -2
  25. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cloud_bucket_mount.pyi +0 -4
  26. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cls.py +33 -71
  27. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cls.pyi +0 -3
  28. {modal-1.2.2 → modal-1.2.2.dev2}/modal/config.py +1 -1
  29. {modal-1.2.2 → modal-1.2.2.dev2}/modal/container_process.py +5 -2
  30. {modal-1.2.2 → modal-1.2.2.dev2}/modal/dict.py +17 -26
  31. {modal-1.2.2 → modal-1.2.2.dev2}/modal/dict.pyi +0 -2
  32. {modal-1.2.2 → modal-1.2.2.dev2}/modal/environments.py +8 -16
  33. {modal-1.2.2 → modal-1.2.2.dev2}/modal/environments.pyi +2 -6
  34. {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/__init__.py +3 -2
  35. {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/flash.py +12 -7
  36. {modal-1.2.2 → modal-1.2.2.dev2}/modal/file_io.py +27 -13
  37. {modal-1.2.2 → modal-1.2.2.dev2}/modal/functions.pyi +10 -16
  38. {modal-1.2.2 → modal-1.2.2.dev2}/modal/image.py +26 -26
  39. {modal-1.2.2 → modal-1.2.2.dev2}/modal/image.pyi +9 -9
  40. {modal-1.2.2 → modal-1.2.2.dev2}/modal/io_streams.py +44 -10
  41. {modal-1.2.2 → modal-1.2.2.dev2}/modal/io_streams.pyi +91 -15
  42. {modal-1.2.2 → modal-1.2.2.dev2}/modal/mount.py +26 -36
  43. {modal-1.2.2 → modal-1.2.2.dev2}/modal/mount.pyi +7 -33
  44. {modal-1.2.2 → modal-1.2.2.dev2}/modal/network_file_system.py +11 -19
  45. {modal-1.2.2 → modal-1.2.2.dev2}/modal/network_file_system.pyi +2 -12
  46. {modal-1.2.2 → modal-1.2.2.dev2}/modal/object.pyi +8 -35
  47. {modal-1.2.2 → modal-1.2.2.dev2}/modal/parallel_map.py +31 -29
  48. {modal-1.2.2 → modal-1.2.2.dev2}/modal/parallel_map.pyi +9 -3
  49. {modal-1.2.2 → modal-1.2.2.dev2}/modal/proxy.py +6 -14
  50. {modal-1.2.2 → modal-1.2.2.dev2}/modal/proxy.pyi +2 -10
  51. {modal-1.2.2 → modal-1.2.2.dev2}/modal/queue.py +23 -31
  52. {modal-1.2.2 → modal-1.2.2.dev2}/modal/queue.pyi +2 -12
  53. {modal-1.2.2 → modal-1.2.2.dev2}/modal/runner.py +58 -62
  54. {modal-1.2.2 → modal-1.2.2.dev2}/modal/runner.pyi +3 -4
  55. {modal-1.2.2 → modal-1.2.2.dev2}/modal/sandbox.py +58 -44
  56. {modal-1.2.2 → modal-1.2.2.dev2}/modal/sandbox.pyi +1 -0
  57. {modal-1.2.2 → modal-1.2.2.dev2}/modal/secret.py +44 -61
  58. {modal-1.2.2 → modal-1.2.2.dev2}/modal/secret.pyi +4 -21
  59. {modal-1.2.2 → modal-1.2.2.dev2}/modal/serving.py +11 -7
  60. {modal-1.2.2 → modal-1.2.2.dev2}/modal/serving.pyi +8 -7
  61. {modal-1.2.2 → modal-1.2.2.dev2}/modal/snapshot.py +8 -11
  62. {modal-1.2.2 → modal-1.2.2.dev2}/modal/token_flow.py +1 -1
  63. {modal-1.2.2 → modal-1.2.2.dev2}/modal/volume.py +28 -44
  64. {modal-1.2.2 → modal-1.2.2.dev2}/modal/volume.pyi +0 -2
  65. {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/PKG-INFO +2 -2
  66. {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/SOURCES.txt +0 -2
  67. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api.proto +9 -23
  68. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2.py +1015 -1031
  69. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2.pyi +19 -90
  70. modal-1.2.2.dev2/modal_proto/modal_api_grpc.py +194 -0
  71. {modal-1.2.2 → modal-1.2.2.dev2}/modal_version/__init__.py +1 -1
  72. {modal-1.2.2 → modal-1.2.2.dev2}/pyproject.toml +1 -1
  73. modal-1.2.2/modal/_grpc_client.py +0 -171
  74. modal-1.2.2/modal/_load_context.py +0 -105
  75. modal-1.2.2/modal_proto/modal_api_grpc.py +0 -194
  76. {modal-1.2.2 → modal-1.2.2.dev2}/LICENSE +0 -0
  77. {modal-1.2.2 → modal-1.2.2.dev2}/README.md +0 -0
  78. {modal-1.2.2 → modal-1.2.2.dev2}/modal/__init__.py +0 -0
  79. {modal-1.2.2 → modal-1.2.2.dev2}/modal/__main__.py +0 -0
  80. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_billing.py +0 -0
  81. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_clustered_functions.pyi +0 -0
  82. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_container_entrypoint.py +0 -0
  83. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_ipython.py +0 -0
  84. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_location.py +0 -0
  85. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_partial_function.py +0 -0
  86. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_pty.py +0 -0
  87. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_resources.py +0 -0
  88. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/__init__.py +0 -0
  89. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/asgi.py +0 -0
  90. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
  91. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/execution_context.py +0 -0
  92. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/execution_context.pyi +0 -0
  93. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  94. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/telemetry.py +0 -0
  95. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/user_code_imports.py +0 -0
  96. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_serialization.py +0 -0
  97. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_traceback.py +0 -0
  98. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_tunnel.py +0 -0
  99. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_tunnel.pyi +0 -0
  100. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_type_manager.py +0 -0
  101. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/__init__.py +0 -0
  102. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/app_utils.py +0 -0
  103. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
  104. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/deprecation.py +0 -0
  105. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/docker_utils.py +0 -0
  106. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/git_utils.py +0 -0
  107. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/grpc_testing.py +0 -0
  108. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/hash_utils.py +0 -0
  109. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/http_utils.py +0 -0
  110. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/jwt_utils.py +0 -0
  111. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/logger.py +0 -0
  112. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/mount_utils.py +0 -0
  113. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/name_utils.py +0 -0
  114. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/package_utils.py +0 -0
  115. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/pattern_utils.py +0 -0
  116. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/rand_pb_testing.py +0 -0
  117. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/shell_utils.py +0 -0
  118. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/time_utils.py +0 -0
  119. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/__init__.py +0 -0
  120. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  121. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/cloudpickle.py +0 -0
  122. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/tblib.py +0 -0
  123. {modal-1.2.2 → modal-1.2.2.dev2}/modal/_watcher.py +0 -0
  124. {modal-1.2.2 → modal-1.2.2.dev2}/modal/billing.py +0 -0
  125. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2023.12.312.txt +0 -0
  126. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2023.12.txt +0 -0
  127. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2024.04.txt +0 -0
  128. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2024.10.txt +0 -0
  129. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2025.06.txt +0 -0
  130. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/PREVIEW.txt +0 -0
  131. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/README.md +0 -0
  132. {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/base-images.json +0 -0
  133. {modal-1.2.2 → modal-1.2.2.dev2}/modal/call_graph.py +0 -0
  134. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/__init__.py +0 -0
  135. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/_download.py +0 -0
  136. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/_traceback.py +0 -0
  137. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/app.py +0 -0
  138. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/cluster.py +0 -0
  139. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/entry_point.py +0 -0
  140. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/environment.py +0 -0
  141. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/import_refs.py +0 -0
  142. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/launch.py +0 -0
  143. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/profile.py +0 -0
  144. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/__init__.py +0 -0
  145. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/launch_instance_ssh.py +0 -0
  146. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/run_jupyter.py +0 -0
  147. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/run_marimo.py +0 -0
  148. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/vscode.py +0 -0
  149. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/run.py +0 -0
  150. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/token.py +0 -0
  151. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/utils.py +0 -0
  152. {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/volume.py +0 -0
  153. {modal-1.2.2 → modal-1.2.2.dev2}/modal/container_process.pyi +0 -0
  154. {modal-1.2.2 → modal-1.2.2.dev2}/modal/exception.py +0 -0
  155. {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/flash.pyi +0 -0
  156. {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/ipython.py +0 -0
  157. {modal-1.2.2 → modal-1.2.2.dev2}/modal/file_io.pyi +0 -0
  158. {modal-1.2.2 → modal-1.2.2.dev2}/modal/file_pattern_matcher.py +0 -0
  159. {modal-1.2.2 → modal-1.2.2.dev2}/modal/functions.py +0 -0
  160. {modal-1.2.2 → modal-1.2.2.dev2}/modal/gpu.py +0 -0
  161. {modal-1.2.2 → modal-1.2.2.dev2}/modal/object.py +0 -0
  162. {modal-1.2.2 → modal-1.2.2.dev2}/modal/output.py +0 -0
  163. {modal-1.2.2 → modal-1.2.2.dev2}/modal/partial_function.py +0 -0
  164. {modal-1.2.2 → modal-1.2.2.dev2}/modal/partial_function.pyi +0 -0
  165. {modal-1.2.2 → modal-1.2.2.dev2}/modal/py.typed +0 -0
  166. {modal-1.2.2 → modal-1.2.2.dev2}/modal/retries.py +0 -0
  167. {modal-1.2.2 → modal-1.2.2.dev2}/modal/running_app.py +0 -0
  168. {modal-1.2.2 → modal-1.2.2.dev2}/modal/schedule.py +0 -0
  169. {modal-1.2.2 → modal-1.2.2.dev2}/modal/scheduler_placement.py +0 -0
  170. {modal-1.2.2 → modal-1.2.2.dev2}/modal/snapshot.pyi +0 -0
  171. {modal-1.2.2 → modal-1.2.2.dev2}/modal/stream_type.py +0 -0
  172. {modal-1.2.2 → modal-1.2.2.dev2}/modal/token_flow.pyi +0 -0
  173. {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/dependency_links.txt +0 -0
  174. {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/entry_points.txt +0 -0
  175. {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/requires.txt +0 -0
  176. {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/top_level.txt +0 -0
  177. {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/__init__.py +0 -0
  178. {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/gen_cli_docs.py +0 -0
  179. {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/gen_reference_docs.py +0 -0
  180. {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/mdmd/__init__.py +0 -0
  181. {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/mdmd/mdmd.py +0 -0
  182. {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/mdmd/signatures.py +0 -0
  183. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/__init__.py +0 -0
  184. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_grpc.py +0 -0
  185. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2_grpc.py +0 -0
  186. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
  187. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/py.typed +0 -0
  188. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router.proto +0 -0
  189. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_grpc.py +0 -0
  190. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2.py +0 -0
  191. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2.pyi +0 -0
  192. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  193. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  194. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router.proto +0 -0
  195. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_grpc.py +0 -0
  196. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2.py +0 -0
  197. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
  198. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  199. {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  200. {modal-1.2.2 → modal-1.2.2.dev2}/modal_version/__main__.py +0 -0
  201. {modal-1.2.2 → modal-1.2.2.dev2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.2
3
+ Version: 1.2.2.dev2
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -13,7 +13,7 @@ Classifier: Topic :: System :: Distributed Computing
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: <3.14,>=3.9
16
+ Requires-Python: >=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: aiohttp
@@ -5,6 +5,7 @@ from dataclasses import dataclass
5
5
  from typing import Optional
6
6
 
7
7
  from modal._utils.async_utils import synchronize_api
8
+ from modal._utils.grpc_utils import retry_transient_errors
8
9
  from modal.client import _Client
9
10
  from modal.exception import InvalidError
10
11
  from modal_proto import api_pb2
@@ -60,7 +61,8 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
60
61
  os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
61
62
 
62
63
  if world_size > 1:
63
- resp = await client.stub.TaskClusterHello(
64
+ resp: api_pb2.TaskClusterHelloResponse = await retry_transient_errors(
65
+ client.stub.TaskClusterHello,
64
66
  api_pb2.TaskClusterHelloRequest(
65
67
  task_id=task_id,
66
68
  container_ip=container_ip,
@@ -19,8 +19,7 @@ from synchronicity.combined_types import MethodWithAio
19
19
  from modal_proto import api_pb2
20
20
  from modal_proto.modal_api_grpc import ModalClientModal
21
21
 
22
- from ._load_context import LoadContext
23
- from ._object import _Object, live_method, live_method_gen
22
+ from ._object import _get_environment_name, _Object, live_method, live_method_gen
24
23
  from ._pty import get_pty_info
25
24
  from ._resolver import Resolver
26
25
  from ._resources import convert_fn_config_to_resources_config
@@ -54,7 +53,7 @@ from ._utils.function_utils import (
54
53
  get_function_type,
55
54
  is_async,
56
55
  )
57
- from ._utils.grpc_utils import Retry, RetryWarningMessage
56
+ from ._utils.grpc_utils import RetryWarningMessage, retry_transient_errors
58
57
  from ._utils.mount_utils import validate_network_file_systems, validate_volumes
59
58
  from .call_graph import InputInfo, _reconstruct_call_graph
60
59
  from .client import _Client
@@ -165,22 +164,21 @@ class _Invocation:
165
164
 
166
165
  if from_spawn_map:
167
166
  request.from_spawn_map = True
168
- response = await client.stub.FunctionMap(
167
+ response = await retry_transient_errors(
168
+ client.stub.FunctionMap,
169
169
  request,
170
- retry=Retry(
171
- max_retries=None,
172
- max_delay=30.0,
173
- warning_message=RetryWarningMessage(
174
- message="Warning: `.spawn_map(...)` for function `{self._function_name}` is waiting to create"
175
- "more function calls. This may be due to hitting rate limits or function backlog limits.",
176
- warning_interval=10,
177
- errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
178
- ),
179
- additional_status_codes=[Status.RESOURCE_EXHAUSTED],
170
+ max_retries=None,
171
+ max_delay=30.0,
172
+ retry_warning_message=RetryWarningMessage(
173
+ message="Warning: `.spawn_map(...)` for function `{self._function_name}` is waiting to create"
174
+ "more function calls. This may be due to hitting rate limits or function backlog limits.",
175
+ warning_interval=10,
176
+ errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
180
177
  ),
178
+ additional_status_codes=[Status.RESOURCE_EXHAUSTED],
181
179
  )
182
180
  else:
183
- response = await client.stub.FunctionMap(request)
181
+ response = await retry_transient_errors(client.stub.FunctionMap, request)
184
182
 
185
183
  function_call_id = response.function_call_id
186
184
  if response.pipelined_inputs:
@@ -200,7 +198,10 @@ class _Invocation:
200
198
  request_put = api_pb2.FunctionPutInputsRequest(
201
199
  function_id=function_id, inputs=[item], function_call_id=function_call_id
202
200
  )
203
- inputs_response: api_pb2.FunctionPutInputsResponse = await client.stub.FunctionPutInputs(request_put)
201
+ inputs_response: api_pb2.FunctionPutInputsResponse = await retry_transient_errors(
202
+ client.stub.FunctionPutInputs,
203
+ request_put,
204
+ )
204
205
  processed_inputs = inputs_response.inputs
205
206
  if not processed_inputs:
206
207
  raise Exception("Could not create function call - the input queue seems to be full")
@@ -242,9 +243,10 @@ class _Invocation:
242
243
  start_idx=index,
243
244
  end_idx=index,
244
245
  )
245
- response: api_pb2.FunctionGetOutputsResponse = await self.stub.FunctionGetOutputs(
246
+ response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
247
+ self.stub.FunctionGetOutputs,
246
248
  request,
247
- retry=Retry(attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD),
249
+ attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD,
248
250
  )
249
251
 
250
252
  if len(response.outputs) > 0:
@@ -264,7 +266,10 @@ class _Invocation:
264
266
 
265
267
  item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
266
268
  request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
267
- await self.stub.FunctionRetryInputs(request)
269
+ await retry_transient_errors(
270
+ self.stub.FunctionRetryInputs,
271
+ request,
272
+ )
268
273
 
269
274
  async def _get_single_output(self, expected_jwt: Optional[str] = None) -> api_pb2.FunctionGetOutputsItem:
270
275
  # waits indefinitely for a single result for the function, and clear the outputs buffer after
@@ -368,8 +373,10 @@ class _Invocation:
368
373
  start_idx=current_index,
369
374
  end_idx=batch_end_index,
370
375
  )
371
- response: api_pb2.FunctionGetOutputsResponse = await self.stub.FunctionGetOutputs(
372
- request, retry=Retry(attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD)
376
+ response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
377
+ self.stub.FunctionGetOutputs,
378
+ request,
379
+ attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD,
373
380
  )
374
381
 
375
382
  outputs = list(response.outputs)
@@ -441,7 +448,7 @@ class _InputPlaneInvocation:
441
448
  )
442
449
 
443
450
  metadata = await client.get_input_plane_metadata(input_plane_region)
444
- response = await stub.AttemptStart(request, metadata=metadata)
451
+ response = await retry_transient_errors(stub.AttemptStart, request, metadata=metadata)
445
452
  attempt_token = response.attempt_token
446
453
 
447
454
  return _InputPlaneInvocation(
@@ -461,9 +468,10 @@ class _InputPlaneInvocation:
461
468
  requested_at=time.time(),
462
469
  )
463
470
  metadata = await self.client.get_input_plane_metadata(self.input_plane_region)
464
- await_response: api_pb2.AttemptAwaitResponse = await self.stub.AttemptAwait(
471
+ await_response: api_pb2.AttemptAwaitResponse = await retry_transient_errors(
472
+ self.stub.AttemptAwait,
465
473
  await_request,
466
- retry=Retry(attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD),
474
+ attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
467
475
  metadata=metadata,
468
476
  )
469
477
 
@@ -503,7 +511,11 @@ class _InputPlaneInvocation:
503
511
  input=self.input_item,
504
512
  attempt_token=self.attempt_token,
505
513
  )
506
- retry_response = await self.stub.AttemptRetry(retry_request, metadata=metadata)
514
+ retry_response = await retry_transient_errors(
515
+ self.stub.AttemptRetry,
516
+ retry_request,
517
+ metadata=metadata,
518
+ )
507
519
  return retry_response.attempt_token
508
520
 
509
521
  async def run_generator(self):
@@ -657,7 +669,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
657
669
  @staticmethod
658
670
  def from_local(
659
671
  info: FunctionInfo,
660
- app: Optional["modal.app._App"], # App here should only be None in case of Image.run_function
672
+ app,
661
673
  image: _Image,
662
674
  env: Optional[dict[str, Optional[str]]] = None,
663
675
  secrets: Optional[Collection[_Secret]] = None,
@@ -883,12 +895,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
883
895
  is_web_endpoint, is_generator, restrict_output
884
896
  )
885
897
 
886
- async def _preload(
887
- self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
888
- ):
889
- assert load_context.app_id
898
+ async def _preload(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
899
+ assert resolver.client and resolver.client.stub
900
+
901
+ assert resolver.app_id
890
902
  req = api_pb2.FunctionPrecreateRequest(
891
- app_id=load_context.app_id,
903
+ app_id=resolver.app_id,
892
904
  function_name=info.function_name,
893
905
  function_type=function_type,
894
906
  existing_function_id=existing_object_id or "",
@@ -904,12 +916,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
904
916
  elif webhook_config:
905
917
  req.webhook_config.CopyFrom(webhook_config)
906
918
 
907
- response = await load_context.client.stub.FunctionPrecreate(req)
908
- self._hydrate(response.function_id, load_context.client, response.handle_metadata)
919
+ response = await retry_transient_errors(resolver.client.stub.FunctionPrecreate, req)
920
+ self._hydrate(response.function_id, resolver.client, response.handle_metadata)
909
921
 
910
- async def _load(
911
- self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
912
- ):
922
+ async def _load(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
923
+ assert resolver.client and resolver.client.stub
913
924
  with FunctionCreationStatus(resolver, tag) as function_creation_status:
914
925
  timeout_secs = timeout
915
926
 
@@ -1105,16 +1116,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1105
1116
  ),
1106
1117
  )
1107
1118
 
1108
- assert load_context.app_id
1119
+ assert resolver.app_id
1109
1120
  assert (function_definition is None) != (function_data is None) # xor
1110
1121
  request = api_pb2.FunctionCreateRequest(
1111
- app_id=load_context.app_id,
1122
+ app_id=resolver.app_id,
1112
1123
  function=function_definition,
1113
1124
  function_data=function_data,
1114
1125
  existing_function_id=existing_object_id or "",
1115
1126
  )
1116
1127
  try:
1117
- response: api_pb2.FunctionCreateResponse = await load_context.client.stub.FunctionCreate(request)
1128
+ response: api_pb2.FunctionCreateResponse = await retry_transient_errors(
1129
+ resolver.client.stub.FunctionCreate, request
1130
+ )
1118
1131
  except GRPCError as exc:
1119
1132
  if exc.status == Status.INVALID_ARGUMENT:
1120
1133
  raise InvalidError(exc.message)
@@ -1129,14 +1142,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1129
1142
  serve_mounts = {m for m in all_mounts if m.is_local()}
1130
1143
  serve_mounts |= image._serve_mounts
1131
1144
  obj._serve_mounts = frozenset(serve_mounts)
1132
- self._hydrate(response.function_id, load_context.client, response.handle_metadata)
1145
+ self._hydrate(response.function_id, resolver.client, response.handle_metadata)
1133
1146
 
1134
1147
  rep = f"Function({tag})"
1135
- # Pass a *reference* to the App's LoadContext - this is important since the App is
1136
- # the only way to infer a LoadContext for an `@app.function`, and the App doesn't
1137
- # get its client until *after* the Function is created.
1138
- load_context = app._root_load_context if app else LoadContext.empty()
1139
- obj = _Function._from_loader(_load, rep, preload=_preload, deps=_deps, load_context_overrides=load_context)
1148
+ obj = _Function._from_loader(_load, rep, preload=_preload, deps=_deps)
1140
1149
 
1141
1150
  obj._raw_f = info.raw_f
1142
1151
  obj._info = info
@@ -1178,12 +1187,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1178
1187
 
1179
1188
  parent = self
1180
1189
 
1181
- async def _load(
1182
- param_bound_func: _Function,
1183
- resolver: Resolver,
1184
- load_context: LoadContext,
1185
- existing_object_id: Optional[str],
1186
- ):
1190
+ async def _load(param_bound_func: _Function, resolver: Resolver, existing_object_id: Optional[str]):
1187
1191
  if not parent.is_hydrated:
1188
1192
  # While the base Object.hydrate() method appears to be idempotent, it's not always safe
1189
1193
  await parent.hydrate()
@@ -1216,6 +1220,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1216
1220
  param_bound_func._hydrate_from_other(parent)
1217
1221
  return
1218
1222
 
1223
+ environment_name = _get_environment_name(None, resolver)
1219
1224
  assert parent is not None and parent.is_hydrated
1220
1225
 
1221
1226
  if options:
@@ -1255,11 +1260,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1255
1260
  function_id=parent.object_id,
1256
1261
  serialized_params=serialized_params,
1257
1262
  function_options=options_pb,
1258
- environment_name=load_context.environment_name
1263
+ environment_name=environment_name
1259
1264
  or "", # TODO: investigate shouldn't environment name always be specified here?
1260
1265
  )
1261
1266
 
1262
- response = await parent._client.stub.FunctionBindParams(req)
1267
+ response = await retry_transient_errors(parent._client.stub.FunctionBindParams, req)
1263
1268
  param_bound_func._hydrate(response.bound_function_id, parent._client, response.handle_metadata)
1264
1269
 
1265
1270
  def _deps():
@@ -1272,13 +1277,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1272
1277
  return [dep for dep in all_deps if not dep.is_hydrated]
1273
1278
  return []
1274
1279
 
1275
- fun: _Function = _Function._from_loader(
1276
- _load,
1277
- "Function(parametrized)",
1278
- hydrate_lazily=True,
1279
- deps=_deps,
1280
- load_context_overrides=self._load_context_overrides,
1281
- )
1280
+ fun: _Function = _Function._from_loader(_load, "Function(parametrized)", hydrate_lazily=True, deps=_deps)
1282
1281
 
1283
1282
  fun._info = self._info
1284
1283
  fun._obj = obj
@@ -1329,7 +1328,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1329
1328
  scaledown_window=scaledown_window,
1330
1329
  )
1331
1330
  request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
1332
- await self.client.stub.FunctionUpdateSchedulingParams(request)
1331
+ await retry_transient_errors(self.client.stub.FunctionUpdateSchedulingParams, request)
1333
1332
 
1334
1333
  # One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
1335
1334
  # and then we could return them here (would need some ad hoc dataclass, which I don't love)
@@ -1376,43 +1375,34 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1376
1375
  cls,
1377
1376
  app_name: str,
1378
1377
  name: str,
1379
- *,
1380
- load_context_overrides: LoadContext,
1378
+ namespace=None, # mdmd:line-hidden
1379
+ environment_name: Optional[str] = None,
1381
1380
  ):
1382
1381
  # internal function lookup implementation that allows lookup of class "service functions"
1383
1382
  # in addition to non-class functions
1384
- async def _load_remote(
1385
- self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
1386
- ):
1383
+ async def _load_remote(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
1384
+ assert resolver.client and resolver.client.stub
1387
1385
  request = api_pb2.FunctionGetRequest(
1388
1386
  app_name=app_name,
1389
1387
  object_tag=name,
1390
- environment_name=load_context.environment_name,
1388
+ environment_name=_get_environment_name(environment_name, resolver) or "",
1391
1389
  )
1392
1390
  try:
1393
- response = await load_context.client.stub.FunctionGet(request)
1391
+ response = await retry_transient_errors(resolver.client.stub.FunctionGet, request)
1394
1392
  except NotFoundError as exc:
1395
1393
  # refine the error message
1396
- env_context = (
1397
- f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
1398
- )
1394
+ env_context = f" (in the '{environment_name}' environment)" if environment_name else ""
1399
1395
  raise NotFoundError(
1400
1396
  f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
1401
1397
  ) from None
1402
1398
 
1403
1399
  print_server_warnings(response.server_warnings)
1404
1400
 
1405
- self._hydrate(response.function_id, load_context.client, response.handle_metadata)
1401
+ self._hydrate(response.function_id, resolver.client, response.handle_metadata)
1406
1402
 
1407
- environment_rep = (
1408
- f", environment_name={load_context_overrides.environment_name!r}"
1409
- if load_context_overrides._environment_name # slightly ugly - checking if _environment_name is overridden
1410
- else ""
1411
- )
1403
+ environment_rep = f", environment_name={environment_name!r}" if environment_name else ""
1412
1404
  rep = f"modal.Function.from_name('{app_name}', '{name}'{environment_rep})"
1413
- return cls._from_loader(
1414
- _load_remote, rep, is_another_app=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
1415
- )
1405
+ return cls._from_loader(_load_remote, rep, is_another_app=True, hydrate_lazily=True)
1416
1406
 
1417
1407
  @classmethod
1418
1408
  def from_name(
@@ -1422,7 +1412,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1422
1412
  *,
1423
1413
  namespace=None, # mdmd:line-hidden
1424
1414
  environment_name: Optional[str] = None,
1425
- client: Optional[_Client] = None,
1426
1415
  ) -> "_Function":
1427
1416
  """Reference a Function from a deployed App by its name.
1428
1417
 
@@ -1446,9 +1435,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1446
1435
  )
1447
1436
 
1448
1437
  warn_if_passing_namespace(namespace, "modal.Function.from_name")
1449
- return cls._from_name(
1450
- app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
1451
- )
1438
+ return cls._from_name(app_name, name, environment_name=environment_name)
1452
1439
 
1453
1440
  @property
1454
1441
  def tag(self) -> str:
@@ -1671,8 +1658,8 @@ Use the `Function.get_web_url()` method instead.
1671
1658
  input_queue,
1672
1659
  self.client,
1673
1660
  )
1674
- fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, None)
1675
- fc._num_inputs = num_inputs # set the cached value of num_inputs
1661
+ metadata = api_pb2.FunctionCallFromIdResponse(function_call_id=function_call_id, num_inputs=num_inputs)
1662
+ fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, metadata)
1676
1663
  return fc
1677
1664
 
1678
1665
  async def _call_function(self, args, kwargs) -> ReturnType:
@@ -1901,9 +1888,10 @@ Use the `Function.get_web_url()` method instead.
1901
1888
  @live_method
1902
1889
  async def get_current_stats(self) -> FunctionStats:
1903
1890
  """Return a `FunctionStats` object describing the current function's queue and runner counts."""
1904
- resp = await self.client.stub.FunctionGetCurrentStats(
1891
+ resp = await retry_transient_errors(
1892
+ self.client.stub.FunctionGetCurrentStats,
1905
1893
  api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
1906
- retry=Retry(total_timeout=10.0),
1894
+ total_timeout=10.0,
1907
1895
  )
1908
1896
  return FunctionStats(backlog=resp.backlog, num_total_runners=resp.num_total_tasks)
1909
1897
 
@@ -1941,16 +1929,19 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1941
1929
  def _invocation(self):
1942
1930
  return _Invocation(self.client.stub, self.object_id, self.client)
1943
1931
 
1932
+ def _hydrate_metadata(self, metadata: Optional[Message]):
1933
+ if not metadata:
1934
+ return
1935
+ assert isinstance(metadata, api_pb2.FunctionCallFromIdResponse)
1936
+ self._num_inputs = metadata.num_inputs
1937
+
1944
1938
  @live_method
1945
1939
  async def num_inputs(self) -> int:
1946
1940
  """Get the number of inputs in the function call."""
1947
- if self._num_inputs is None:
1948
- request = api_pb2.FunctionCallFromIdRequest(function_call_id=self.object_id)
1949
- resp = await self.client.stub.FunctionCallFromId(request)
1950
- self._num_inputs = resp.num_inputs # cached
1941
+ # Should have been hydrated.
1942
+ assert self._num_inputs is not None
1951
1943
  return self._num_inputs
1952
1944
 
1953
- @live_method
1954
1945
  async def get(self, timeout: Optional[float] = None, *, index: int = 0) -> ReturnType:
1955
1946
  """Get the result of the index-th input of the function call.
1956
1947
  `.spawn()` calls have a single output, so only specifying `index=0` is valid.
@@ -1994,7 +1985,6 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1994
1985
  async for _, item in self._invocation().enumerate(start_index=start, end_index=end):
1995
1986
  yield item
1996
1987
 
1997
- @live_method
1998
1988
  async def get_call_graph(self) -> list[InputInfo]:
1999
1989
  """Returns a structure representing the call graph from a given root
2000
1990
  call ID, along with the status of execution for each node.
@@ -2004,10 +1994,9 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2004
1994
  """
2005
1995
  assert self._client and self._client.stub
2006
1996
  request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
2007
- response = await self._client.stub.FunctionGetCallGraph(request)
1997
+ response = await retry_transient_errors(self._client.stub.FunctionGetCallGraph, request)
2008
1998
  return _reconstruct_call_graph(response)
2009
1999
 
2010
- @live_method
2011
2000
  async def cancel(
2012
2001
  self,
2013
2002
  # if true, containers running the inputs are forcibly terminated
@@ -2023,7 +2012,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2023
2012
  function_call_id=self.object_id, terminate_containers=terminate_containers
2024
2013
  )
2025
2014
  assert self._client and self._client.stub
2026
- await self._client.stub.FunctionCallCancel(request)
2015
+ await retry_transient_errors(self._client.stub.FunctionCallCancel, request)
2027
2016
 
2028
2017
  @staticmethod
2029
2018
  async def from_id(function_call_id: str, client: Optional[_Client] = None) -> "_FunctionCall[Any]":
@@ -2045,18 +2034,20 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2045
2034
  if you no longer have access to the original object returned from `Function.spawn`.
2046
2035
 
2047
2036
  """
2037
+ if client is None:
2038
+ client = await _Client.from_env()
2048
2039
 
2049
- async def _load(
2050
- self: _FunctionCall, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
2051
- ):
2052
- # this loader doesn't do anything in practice, but it will get the client from the load_context
2053
- self._hydrate(function_call_id, load_context.client, None)
2040
+ async def _load(self: _FunctionCall, resolver: Resolver, existing_object_id: Optional[str]):
2041
+ request = api_pb2.FunctionCallFromIdRequest(function_call_id=function_call_id)
2042
+ resp = await retry_transient_errors(resolver.client.stub.FunctionCallFromId, request)
2043
+ self._hydrate(function_call_id, resolver.client, resp)
2054
2044
 
2055
2045
  rep = f"FunctionCall.from_id({function_call_id!r})"
2056
-
2057
- return _FunctionCall._from_loader(
2058
- _load, rep, hydrate_lazily=True, load_context_overrides=LoadContext(client=client)
2059
- )
2046
+ fc: _FunctionCall[Any] = _FunctionCall._from_loader(_load, rep, hydrate_lazily=True)
2047
+ # We already know the object ID, so we can set it directly
2048
+ fc._object_id = function_call_id
2049
+ fc._client = client
2050
+ return fc
2060
2051
 
2061
2052
  @staticmethod
2062
2053
  async def gather(*function_calls: "_FunctionCall[T]") -> typing.Sequence[T]:
@@ -10,7 +10,6 @@ from typing_extensions import Self
10
10
 
11
11
  from modal._traceback import suppress_tb_frames
12
12
 
13
- from ._load_context import LoadContext
14
13
  from ._resolver import Resolver
15
14
  from ._utils.async_utils import aclosing
16
15
  from ._utils.deprecation import deprecation_warning
@@ -21,19 +20,11 @@ from .exception import ExecutionError, InvalidError
21
20
  EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
22
21
 
23
22
 
24
- def _get_environment_name(
25
- environment_name: Optional[str] = None,
26
- ) -> Optional[str]:
27
- """Get environment name from various sources.
28
-
29
- Args:
30
- environment_name: Explicitly provided environment name (highest priority)
31
-
32
- Returns:
33
- Environment name from first available source, or config default
34
- """
23
+ def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
35
24
  if environment_name:
36
25
  return environment_name
26
+ elif resolver and resolver.environment_name:
27
+ return resolver.environment_name
37
28
  else:
38
29
  return config.get("environment")
39
30
 
@@ -43,14 +34,13 @@ class _Object:
43
34
  _prefix_to_type: ClassVar[dict[str, type]] = {}
44
35
 
45
36
  # For constructors
46
- _load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]]
47
- _preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]]
37
+ _load: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]]
38
+ _preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]]
48
39
  _rep: str
49
40
  _is_another_app: bool
50
41
  _hydrate_lazily: bool
51
42
  _deps: Optional[Callable[..., Sequence["_Object"]]]
52
43
  _deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
53
- _load_context_overrides: LoadContext
54
44
 
55
45
  # For hydrated objects
56
46
  _object_id: Optional[str]
@@ -76,15 +66,13 @@ class _Object:
76
66
  def _init(
77
67
  self,
78
68
  rep: str,
79
- load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
69
+ load: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
80
70
  is_another_app: bool = False,
81
- preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
71
+ preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
82
72
  hydrate_lazily: bool = False,
83
73
  deps: Optional[Callable[..., Sequence["_Object"]]] = None,
84
74
  deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
85
75
  name: Optional[str] = None,
86
- *,
87
- load_context_overrides: Optional[LoadContext] = None,
88
76
  ):
89
77
  self._local_uuid = str(uuid.uuid4())
90
78
  self._load = load
@@ -94,9 +82,6 @@ class _Object:
94
82
  self._hydrate_lazily = hydrate_lazily
95
83
  self._deps = deps
96
84
  self._deduplication_key = deduplication_key
97
- self._load_context_overrides = (
98
- load_context_overrides if load_context_overrides is not None else LoadContext.empty()
99
- )
100
85
 
101
86
  self._object_id = None
102
87
  self._client = None
@@ -178,30 +163,18 @@ class _Object:
178
163
  @classmethod
179
164
  def _from_loader(
180
165
  cls,
181
- load: Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]],
166
+ load: Callable[[Self, Resolver, Optional[str]], Awaitable[None]],
182
167
  rep: str,
183
168
  is_another_app: bool = False,
184
- preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
169
+ preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
185
170
  hydrate_lazily: bool = False,
186
171
  deps: Optional[Callable[..., Sequence["_Object"]]] = None,
187
172
  deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
188
173
  name: Optional[str] = None,
189
- *,
190
- load_context_overrides: LoadContext,
191
174
  ):
192
175
  # TODO(erikbern): flip the order of the two first arguments
193
176
  obj = _Object.__new__(cls)
194
- obj._init(
195
- rep,
196
- load,
197
- is_another_app,
198
- preload,
199
- hydrate_lazily,
200
- deps,
201
- deduplication_key,
202
- name,
203
- load_context_overrides=load_context_overrides,
204
- )
177
+ obj._init(rep, load, is_another_app, preload, hydrate_lazily, deps, deduplication_key, name)
205
178
  return obj
206
179
 
207
180
  @staticmethod
@@ -302,27 +275,25 @@ class _Object:
302
275
 
303
276
  *Added in v0.72.39*: This method replaces the deprecated `.resolve()` method.
304
277
  """
305
- # TODO: add deprecation for the client argument here - should be added in constructors instead
306
278
  if self._is_hydrated:
307
279
  if self.client._snapshotted and not self._is_rehydrated:
308
280
  # memory snapshots capture references which must be rehydrated
309
281
  # on restore to handle staleness.
310
282
  logger.debug(f"rehydrating {self} after snapshot")
311
283
  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)
284
+ c = client if client is not None else await _Client.from_env()
285
+ resolver = Resolver(c)
286
+ await resolver.load(typing.cast(_Object, self))
316
287
  self._is_rehydrated = True
317
- logger.debug(f"rehydrated {self} with client {id(self.client)}")
288
+ logger.debug(f"rehydrated {self} with client {id(c)}")
318
289
  elif not self._hydrate_lazily:
290
+ # TODO(michael) can remove _hydrate lazily? I think all objects support it now?
319
291
  self._validate_is_hydrated()
320
292
  else:
321
- # Set the client on LoadContext before loading
322
- root_load_context = LoadContext(client=client)
323
- resolver = Resolver()
293
+ c = client if client is not None else await _Client.from_env()
294
+ resolver = Resolver(c)
324
295
  with suppress_tb_frames(1): # skip this frame by default
325
- await resolver.load(self, root_load_context)
296
+ await resolver.load(self)
326
297
  return self
327
298
 
328
299
 
@@ -34,7 +34,7 @@ from rich.text import Text
34
34
  from modal._utils.time_utils import timestamp_to_localized_str
35
35
  from modal_proto import api_pb2
36
36
 
37
- from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, Retry
37
+ from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
38
38
  from ._utils.shell_utils import stream_from_stdin, write_to_fd
39
39
  from .client import _Client
40
40
  from .config import logger
@@ -489,11 +489,12 @@ async def stream_pty_shell_input(client: _Client, exec_id: str, finish_event: as
489
489
  """
490
490
 
491
491
  async def _handle_input(data: bytes, message_index: int):
492
- await client.stub.ContainerExecPutInput(
492
+ await retry_transient_errors(
493
+ client.stub.ContainerExecPutInput,
493
494
  api_pb2.ContainerExecPutInputRequest(
494
495
  exec_id=exec_id, input=api_pb2.RuntimeInputMessage(message=data, message_index=message_index)
495
496
  ),
496
- retry=Retry(total_timeout=10),
497
+ total_timeout=10,
497
498
  )
498
499
 
499
500
  async with stream_from_stdin(_handle_input, use_raw_terminal=True):