modal 1.2.7.dev12__tar.gz → 1.2.7.dev13__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 (196) hide show
  1. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/PKG-INFO +1 -1
  2. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_functions.py +12 -6
  3. modal-1.2.7.dev13/modal/_ipython.py +21 -0
  4. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_object.py +16 -0
  5. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/async_utils.py +310 -2
  6. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/grpc_testing.py +6 -3
  7. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/app.py +5 -4
  8. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/client.py +3 -2
  9. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/client.pyi +4 -2
  10. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/config.py +1 -0
  11. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/exception.py +4 -0
  12. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/functions.pyi +8 -28
  13. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/image.py +10 -5
  14. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/image.pyi +16 -17
  15. modal-1.2.7.dev13/modal/snapshot.py +56 -0
  16. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/snapshot.pyi +18 -10
  17. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/volume.py +13 -4
  18. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/volume.pyi +8 -6
  19. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/PKG-INFO +1 -1
  20. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_version/__init__.py +1 -1
  21. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/pyproject.toml +1 -0
  22. modal-1.2.7.dev12/modal/_ipython.py +0 -11
  23. modal-1.2.7.dev12/modal/snapshot.py +0 -45
  24. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/LICENSE +0 -0
  25. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/README.md +0 -0
  26. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/__init__.py +0 -0
  27. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/__main__.py +0 -0
  28. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_billing.py +0 -0
  29. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_clustered_functions.py +0 -0
  30. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_clustered_functions.pyi +0 -0
  31. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_container_entrypoint.py +0 -0
  32. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_grpc_client.py +0 -0
  33. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_load_context.py +0 -0
  34. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_location.py +0 -0
  35. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_output.py +0 -0
  36. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_partial_function.py +0 -0
  37. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_pty.py +0 -0
  38. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_resolver.py +0 -0
  39. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_resources.py +0 -0
  40. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/__init__.py +0 -0
  41. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/asgi.py +0 -0
  42. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/container_io_manager.py +0 -0
  43. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/container_io_manager.pyi +0 -0
  44. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/execution_context.py +0 -0
  45. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/execution_context.pyi +0 -0
  46. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  47. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/telemetry.py +0 -0
  48. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/user_code_event_loop.py +0 -0
  49. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_runtime/user_code_imports.py +0 -0
  50. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_serialization.py +0 -0
  51. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_traceback.py +0 -0
  52. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_tunnel.py +0 -0
  53. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_tunnel.pyi +0 -0
  54. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_type_manager.py +0 -0
  55. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/__init__.py +0 -0
  56. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/app_utils.py +0 -0
  57. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/auth_token_manager.py +0 -0
  58. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/blob_utils.py +0 -0
  59. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/bytes_io_segment_payload.py +0 -0
  60. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/deprecation.py +0 -0
  61. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/docker_utils.py +0 -0
  62. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/function_utils.py +0 -0
  63. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/git_utils.py +0 -0
  64. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/grpc_utils.py +0 -0
  65. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/hash_utils.py +0 -0
  66. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/http_utils.py +0 -0
  67. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/jwt_utils.py +0 -0
  68. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/logger.py +0 -0
  69. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/mount_utils.py +0 -0
  70. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/name_utils.py +0 -0
  71. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/package_utils.py +0 -0
  72. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/pattern_utils.py +0 -0
  73. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/rand_pb_testing.py +0 -0
  74. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/shell_utils.py +0 -0
  75. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/task_command_router_client.py +0 -0
  76. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_utils/time_utils.py +0 -0
  77. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/__init__.py +0 -0
  78. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  79. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/cloudpickle.py +0 -0
  80. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_vendor/tblib.py +0 -0
  81. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/_watcher.py +0 -0
  82. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/app.pyi +0 -0
  83. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/billing.py +0 -0
  84. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2023.12.312.txt +0 -0
  85. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2023.12.txt +0 -0
  86. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2024.04.txt +0 -0
  87. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2024.10.txt +0 -0
  88. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/2025.06.txt +0 -0
  89. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/PREVIEW.txt +0 -0
  90. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/README.md +0 -0
  91. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/builder/base-images.json +0 -0
  92. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/call_graph.py +0 -0
  93. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/__init__.py +0 -0
  94. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/_download.py +0 -0
  95. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/_traceback.py +0 -0
  96. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/app.py +0 -0
  97. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/cluster.py +0 -0
  98. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/config.py +0 -0
  99. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/container.py +0 -0
  100. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/dict.py +0 -0
  101. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/entry_point.py +0 -0
  102. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/environment.py +0 -0
  103. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/import_refs.py +0 -0
  104. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/launch.py +0 -0
  105. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/network_file_system.py +0 -0
  106. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/profile.py +0 -0
  107. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/programs/__init__.py +0 -0
  108. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/programs/run_jupyter.py +0 -0
  109. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/programs/vscode.py +0 -0
  110. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/queues.py +0 -0
  111. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/run.py +0 -0
  112. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/secret.py +0 -0
  113. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/shell.py +0 -0
  114. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/token.py +0 -0
  115. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/utils.py +0 -0
  116. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cli/volume.py +0 -0
  117. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cloud_bucket_mount.py +0 -0
  118. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cloud_bucket_mount.pyi +0 -0
  119. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cls.py +0 -0
  120. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/cls.pyi +0 -0
  121. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/container_process.py +0 -0
  122. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/container_process.pyi +0 -0
  123. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/dict.py +0 -0
  124. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/dict.pyi +0 -0
  125. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/environments.py +0 -0
  126. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/environments.pyi +0 -0
  127. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/__init__.py +0 -0
  128. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/flash.py +0 -0
  129. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/flash.pyi +0 -0
  130. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/experimental/ipython.py +0 -0
  131. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/file_io.py +0 -0
  132. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/file_io.pyi +0 -0
  133. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/file_pattern_matcher.py +0 -0
  134. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/functions.py +0 -0
  135. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/gpu.py +0 -0
  136. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/io_streams.py +0 -0
  137. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/io_streams.pyi +0 -0
  138. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/mount.py +0 -0
  139. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/mount.pyi +0 -0
  140. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/network_file_system.py +0 -0
  141. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/network_file_system.pyi +0 -0
  142. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/object.py +0 -0
  143. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/object.pyi +0 -0
  144. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/output.py +0 -0
  145. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/parallel_map.py +0 -0
  146. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/parallel_map.pyi +0 -0
  147. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/partial_function.py +0 -0
  148. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/partial_function.pyi +0 -0
  149. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/proxy.py +0 -0
  150. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/proxy.pyi +0 -0
  151. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/py.typed +0 -0
  152. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/queue.py +0 -0
  153. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/queue.pyi +0 -0
  154. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/retries.py +0 -0
  155. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/runner.py +0 -0
  156. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/runner.pyi +0 -0
  157. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/running_app.py +0 -0
  158. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/sandbox.py +0 -0
  159. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/sandbox.pyi +0 -0
  160. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/schedule.py +0 -0
  161. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/scheduler_placement.py +0 -0
  162. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/secret.py +0 -0
  163. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/secret.pyi +0 -0
  164. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/serving.py +0 -0
  165. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/serving.pyi +0 -0
  166. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/stream_type.py +0 -0
  167. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/token_flow.py +0 -0
  168. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal/token_flow.pyi +0 -0
  169. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/SOURCES.txt +0 -0
  170. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/dependency_links.txt +0 -0
  171. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/entry_points.txt +0 -0
  172. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/requires.txt +0 -0
  173. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal.egg-info/top_level.txt +0 -0
  174. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/__init__.py +0 -0
  175. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/gen_cli_docs.py +0 -0
  176. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/gen_reference_docs.py +0 -0
  177. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/mdmd/__init__.py +0 -0
  178. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/mdmd/mdmd.py +0 -0
  179. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_docs/mdmd/signatures.py +0 -0
  180. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/__init__.py +0 -0
  181. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api.proto +0 -0
  182. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_grpc.py +0 -0
  183. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2.py +0 -0
  184. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2.pyi +0 -0
  185. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2_grpc.py +0 -0
  186. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/api_pb2_grpc.pyi +0 -0
  187. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/modal_api_grpc.py +0 -0
  188. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/py.typed +0 -0
  189. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router.proto +0 -0
  190. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_grpc.py +0 -0
  191. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2.py +0 -0
  192. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2.pyi +0 -0
  193. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  194. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  195. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/modal_version/__main__.py +0 -0
  196. {modal-1.2.7.dev12 → modal-1.2.7.dev13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.7.dev12
3
+ Version: 1.2.7.dev13
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -38,6 +38,7 @@ from ._utils.async_utils import (
38
38
  aclosing,
39
39
  async_merge,
40
40
  callable_to_agen,
41
+ deprecate_aio_usage,
41
42
  synchronizer,
42
43
  warn_if_generator_is_not_consumed,
43
44
  )
@@ -96,6 +97,7 @@ from .volume import _Volume
96
97
  if TYPE_CHECKING:
97
98
  import modal.app
98
99
  import modal.cls
100
+ import modal.functions
99
101
 
100
102
  MAX_INTERNAL_FAILURE_COUNT = 8
101
103
  TERMINAL_STATUSES = (
@@ -2025,8 +2027,11 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2025
2027
  assert self._client and self._client.stub
2026
2028
  await self._client.stub.FunctionCallCancel(request)
2027
2029
 
2028
- @staticmethod
2029
- async def from_id(function_call_id: str, client: Optional[_Client] = None) -> "_FunctionCall[Any]":
2030
+ @deprecate_aio_usage((2025, 11, 14), "FunctionCall.from_id")
2031
+ @classmethod
2032
+ def from_id(
2033
+ cls, function_call_id: str, client: Optional["modal.client.Client"] = None
2034
+ ) -> "modal.functions.FunctionCall[Any]":
2030
2035
  """Instantiate a FunctionCall object from an existing ID.
2031
2036
 
2032
2037
  Examples:
@@ -2037,7 +2042,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2037
2042
  fc_id = fc.object_id
2038
2043
 
2039
2044
  # Later, use the ID to re-instantiate the FunctionCall object
2040
- fc = _FunctionCall.from_id(fc_id)
2045
+ fc = FunctionCall.from_id(fc_id)
2041
2046
  result = fc.get()
2042
2047
  ```
2043
2048
 
@@ -2045,6 +2050,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2045
2050
  if you no longer have access to the original object returned from `Function.spawn`.
2046
2051
 
2047
2052
  """
2053
+ _client = typing.cast(_Client, synchronizer._translate_in(client))
2048
2054
 
2049
2055
  async def _load(
2050
2056
  self: _FunctionCall, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
@@ -2053,10 +2059,10 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
2053
2059
  self._hydrate(function_call_id, load_context.client, None)
2054
2060
 
2055
2061
  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)
2062
+ impl_instance = _FunctionCall._from_loader(
2063
+ _load, rep, hydrate_lazily=True, load_context_overrides=LoadContext(client=_client)
2059
2064
  )
2065
+ return typing.cast("modal.functions.FunctionCall[Any]", synchronizer._translate_out(impl_instance))
2060
2066
 
2061
2067
  @staticmethod
2062
2068
  async def gather(*function_calls: "_FunctionCall[T]") -> typing.Sequence[T]:
@@ -0,0 +1,21 @@
1
+ # Copyright Modal Labs 2022
2
+ import sys
3
+
4
+
5
+ def is_interactive_ipython():
6
+ """
7
+ Detect if we're running in an interactive IPython session.
8
+
9
+ Returns True for IPython shells (including Jupyter notebooks), False otherwise.
10
+ """
11
+ try:
12
+ # Check if IPython is available and get the current instance
13
+ ipython = sys.modules.get("IPython")
14
+ if ipython is None:
15
+ return False
16
+
17
+ # Try to get the active IPython instance
18
+ shell = ipython.get_ipython()
19
+ return shell is not None
20
+ except Exception:
21
+ return False
@@ -1,4 +1,5 @@
1
1
  # Copyright Modal Labs 2022
2
+ import contextlib
2
3
  import typing
3
4
  import uuid
4
5
  from collections.abc import Awaitable, Hashable, Sequence
@@ -350,3 +351,18 @@ def live_method_gen(method):
350
351
  yield item
351
352
 
352
353
  return wrapped
354
+
355
+
356
+ def live_method_contextmanager(method):
357
+ # make sure a wrapped function returning an async context manager
358
+ # will not require both an `await func.aio()` and `async with`
359
+ # which would have been the case if it was wrapped in live_method
360
+
361
+ @wraps(method)
362
+ @contextlib.asynccontextmanager
363
+ async def wrapped(self, *args, **kwargs):
364
+ await self.hydrate()
365
+ async with method(self, *args, **kwargs) as ctx:
366
+ yield ctx
367
+
368
+ return wrapped
@@ -5,8 +5,10 @@ import contextlib
5
5
  import functools
6
6
  import inspect
7
7
  import itertools
8
+ import os
8
9
  import sys
9
10
  import time
11
+ import types
10
12
  import typing
11
13
  import warnings
12
14
  from collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Iterable, Iterator
@@ -24,10 +26,14 @@ from typing import (
24
26
 
25
27
  import synchronicity
26
28
  from synchronicity.async_utils import Runner
29
+ from synchronicity.combined_types import MethodWithAio
27
30
  from synchronicity.exceptions import NestedEventLoops
28
31
  from typing_extensions import ParamSpec, assert_type
29
32
 
30
- from ..exception import InvalidError
33
+ from modal._ipython import is_interactive_ipython
34
+ from modal._utils.deprecation import deprecation_warning
35
+
36
+ from ..exception import AsyncUsageWarning, InvalidError
31
37
  from .logger import logger
32
38
 
33
39
  T = TypeVar("T")
@@ -38,7 +44,285 @@ if sys.platform == "win32":
38
44
  # quick workaround for deadlocks on shutdown - need to investigate further
39
45
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
40
46
 
41
- synchronizer = synchronicity.Synchronizer()
47
+
48
+ def rewrite_sync_to_async(code_line: str, original_func: Callable) -> tuple[bool, str]:
49
+ """
50
+ Rewrite a blocking call to use async/await syntax.
51
+
52
+ Handles four patterns:
53
+ 1. __aiter__: for x in obj -> async for x in obj
54
+ 2. __aenter__: with obj as x -> async with obj as x
55
+ 3. Async generators in for loops: for x in obj.method(...) -> async for x in obj.method(...)
56
+ 4. Regular methods: obj.method() -> await obj.method.aio()
57
+
58
+ Args:
59
+ code_line: The line of code containing the blocking call
60
+ original_func: The original function object being called
61
+
62
+ Returns:
63
+ A tuple of (success, rewritten_code):
64
+ - success: True if the pattern was found and rewritten, False if falling back to generic
65
+ - rewritten_code: The rewritten code or a generic suggestion
66
+ """
67
+ import re
68
+
69
+ func_name = original_func.__name__ # type: ignore
70
+
71
+ # Check if this is an async generator function
72
+ is_async_gen = inspect.isasyncgenfunction(original_func)
73
+
74
+ # Handle __aiter__ pattern: for x in obj -> async for x in obj
75
+ if func_name == "__aiter__" and code_line.startswith("for "):
76
+ suggestion = code_line.replace("for ", "async for ", 1)
77
+ return (True, suggestion)
78
+
79
+ # Handle __aenter__ pattern: with obj as x -> async with obj as x
80
+ if func_name == "__aenter__" and code_line.startswith("with "):
81
+ suggestion = code_line.replace("with ", "async with ", 1)
82
+ return (True, suggestion)
83
+
84
+ # Handle __setitem__ pattern: dct['key'] = value -> suggest alternative
85
+ if func_name == "__setitem__":
86
+ # Try to extract the object and key from the bracket syntax
87
+ setitem_match = re.match(r"(\w+)\[([^\]]+)\]\s*=\s*(.+)", code_line.strip())
88
+ if setitem_match:
89
+ obj, key, value = setitem_match.groups()
90
+ suggestion = (
91
+ f"You can't use `{obj}[{key}] = {value}` syntax asynchronously - "
92
+ f"there may be an alternative api, e.g. {obj}.put.aio({key}, {value})"
93
+ )
94
+ return (False, suggestion)
95
+ return (False, f"await ...{func_name}.aio(...)")
96
+
97
+ # Handle __getitem__ pattern: dct['key'] -> suggest alternative
98
+ if func_name == "__getitem__":
99
+ # Try to extract the object and key from the bracket syntax
100
+ getitem_match = re.match(r"(\w+)\[([^\]]+)\]$", code_line.strip())
101
+ if getitem_match:
102
+ obj, key = getitem_match.groups()
103
+ suggestion = (
104
+ f"You can't use `{obj}[{key}]` syntax asynchronously - "
105
+ f"there may be an alternative api, e.g. {obj}.get.aio({key})"
106
+ )
107
+ return (False, suggestion)
108
+ return (False, f"await ...{func_name}.aio(...)")
109
+
110
+ # Handle async generator methods in for loops: for x in obj.method(...) -> async for x in obj.method(...)
111
+ if is_async_gen and code_line.strip().startswith("for "):
112
+ # Pattern: for <var> in <expr>.<method>(<args>):
113
+ for_pattern = rf"(for\s+\w+\s+in\s+.*\.){re.escape(func_name)}(\s*\()"
114
+ for_match = re.search(for_pattern, code_line)
115
+
116
+ if for_match:
117
+ # Just replace "for" with "async for" - no .aio() needed for async generators
118
+ suggestion = code_line.replace("for ", "async for ", 1)
119
+ return (True, suggestion)
120
+
121
+ # Handle regular method calls and property access
122
+ # First check if it's a property access (no parentheses after the name)
123
+ property_pattern = rf"\.{re.escape(func_name)}(?!\s*\()"
124
+ property_match = re.search(property_pattern, code_line)
125
+
126
+ if property_match:
127
+ # This is a property access, rewrite to use await without .aio()
128
+ # Find the start of the expression (skip statement keywords and assignments)
129
+ statement_start = 0
130
+ prefix_match = re.match(r"^(\s*(?:\w+\s*=|return|yield|raise)\s+)", code_line)
131
+ if prefix_match:
132
+ statement_start = len(prefix_match.group(1))
133
+
134
+ before_expr = code_line[:statement_start]
135
+ after_prefix = code_line[statement_start:]
136
+
137
+ # Just add await before the expression for properties
138
+ suggestion = before_expr + "await " + after_prefix.lstrip()
139
+ return (True, suggestion)
140
+
141
+ # Try to find a method call (with parentheses)
142
+ method_pattern = rf"\.{re.escape(func_name)}\s*\("
143
+ method_match = re.search(method_pattern, code_line)
144
+
145
+ if not method_match:
146
+ # Can't find the function call or property
147
+ return (False, f"await ...{func_name}.aio(...)")
148
+
149
+ # Safety check: don't attempt rewrite for complex expressions
150
+ unsafe_keywords = ["if", "elif", "while", "and", "or", "not", "in", "is", "for"]
151
+
152
+ # Check if line contains control flow keywords (might be too complex)
153
+ for keyword in unsafe_keywords:
154
+ if re.search(rf"\b{keyword}\b", code_line):
155
+ # Fall back to generic suggestion for complex expressions
156
+ return (False, f"await ...{func_name}.aio(...)")
157
+
158
+ # Find the start of the object expression that leads to the method call
159
+ # We need to find where the object/chain starts, e.g., in "2 * foo.bar.method()" we want "foo"
160
+ # Work backwards from the method match to find the start of the identifier chain
161
+ method_start = method_match.start()
162
+
163
+ # Find the start of the identifier chain (the object being called)
164
+ # Walk backwards to find identifiers and dots that form the chain
165
+ expr_start = method_start
166
+ i = method_start - 1
167
+ while i >= 0:
168
+ c = code_line[i]
169
+ if c.isalnum() or c == "_" or c == ".":
170
+ expr_start = i
171
+ i -= 1
172
+ elif c.isspace():
173
+ # Skip whitespace within the chain (though unusual)
174
+ i -= 1
175
+ else:
176
+ # Found a non-identifier character, stop
177
+ break
178
+
179
+ # Now expr_start points to the start of the object chain (e.g., "foo" in "foo.method()")
180
+ # But we need to check if the identifier we found is actually a keyword like return/yield/raise
181
+ # In that case, skip over it and find the actual object
182
+ before_obj = code_line[:expr_start]
183
+ obj_and_rest = code_line[expr_start:]
184
+
185
+ # Check if what we found starts with a statement keyword
186
+ keyword_match = re.match(r"^(return|yield|raise)\s+", obj_and_rest)
187
+ if keyword_match:
188
+ # The "object" we found is actually a keyword, adjust to skip it
189
+ keyword_len = len(keyword_match.group(0))
190
+ before_obj = code_line[: expr_start + keyword_len]
191
+ obj_and_rest = code_line[expr_start + keyword_len :]
192
+
193
+ # Add .aio() after the method name and await before the object
194
+ rewritten_expr = re.sub(rf"(\.{re.escape(func_name)})\s*\(", r"\1.aio(", obj_and_rest, count=1)
195
+ suggestion = before_obj + "await " + rewritten_expr
196
+
197
+ return (True, suggestion)
198
+
199
+
200
+ @dataclass
201
+ class _CallFrame:
202
+ """Simple dataclass to hold call frame information."""
203
+
204
+ filename: str
205
+ lineno: int
206
+ line: Optional[str]
207
+
208
+
209
+ def _extract_user_call_frame():
210
+ """
211
+ Extract the call frame from user code by filtering out frames from synchronicity and asyncio.
212
+
213
+ Returns a _CallFrame with the filename, line number, and source line, or None if not found.
214
+ """
215
+ import linecache
216
+ import os
217
+
218
+ # Get the current call stack
219
+ stack = inspect.stack()
220
+
221
+ # Get the absolute path of this module to filter it out
222
+ this_file = os.path.abspath(__file__)
223
+
224
+ # Filter out frames from synchronicity, asyncio, and this module
225
+ for frame_info in stack:
226
+ filename = frame_info.filename
227
+ # Skip frames from synchronicity, asyncio packages, and this module
228
+ # Use path separators to ensure we're matching packages, not just filenames containing these words
229
+ if (
230
+ os.path.sep + "synchronicity" + os.path.sep in filename
231
+ or os.path.sep + "asyncio" + os.path.sep in filename
232
+ or os.path.abspath(filename) == this_file
233
+ ):
234
+ continue
235
+
236
+ # Found a user frame
237
+ line = linecache.getline(filename, frame_info.lineno)
238
+ return _CallFrame(filename=filename, lineno=frame_info.lineno, line=line if line else None)
239
+
240
+ # Fallback if we can't find a suitable frame
241
+ return None
242
+
243
+
244
+ def _blocking_in_async_warning(original_func: types.FunctionType):
245
+ if is_interactive_ipython():
246
+ # in notebooks or interactive sessions where sync usage is expected
247
+ # even if it's actually running in an event loop
248
+ return
249
+
250
+ import warnings
251
+
252
+ # Skip warnings for __aexit__ and __anext__ - the __aenter__ and __aiter__ warnings are sufficient
253
+ if original_func:
254
+ func_name = getattr(original_func, "__name__", str(original_func))
255
+ if func_name in ("__aexit__", "__anext__"):
256
+ # These dunders would typically already have caused a warning on the __aenter__ or __aiter__ respectively
257
+ return
258
+
259
+ # Extract the call frame from the stack
260
+ call_frame = _extract_user_call_frame()
261
+
262
+ # Build detailed warning message with location and function first
263
+ message_parts = [
264
+ "A blocking Modal interface is being used in an async context.",
265
+ "\n\nThis may cause performance issues or bugs.",
266
+ " Consider rewriting to use Modal's async interfaces:",
267
+ "\nhttps://modal.com/docs/guide/async",
268
+ ]
269
+
270
+ # Generate intelligent suggestion based on the context
271
+ suggestion = None
272
+ code_line = None
273
+
274
+ if original_func and call_frame and call_frame.line:
275
+ code_line = call_frame.line.strip()
276
+ # Use the unified rewrite function for all patterns
277
+ _, suggestion = rewrite_sync_to_async(code_line, original_func)
278
+
279
+ # Add suggestion in "change X to Y" format
280
+ if suggestion and code_line:
281
+ # this is a bit ugly, but the warnings formatter will show the offending source line
282
+ # on the last line regardless what we do, so we add this to not make it look out of place
283
+ message_parts.append(f"\n\nSuggested rewrite:\n {suggestion}\n\nOriginal line:")
284
+
285
+ # Use warn_explicit to provide precise location information from the call frame
286
+ if call_frame:
287
+ # Extract module name from filename, or use a default
288
+ module_name = os.path.splitext(os.path.basename(call_frame.filename))[0]
289
+
290
+ warnings.warn_explicit(
291
+ "".join(message_parts),
292
+ AsyncUsageWarning,
293
+ filename=call_frame.filename,
294
+ lineno=call_frame.lineno,
295
+ module=module_name,
296
+ )
297
+ else:
298
+ # Fallback to regular warn if no frame information available
299
+ warnings.warn("".join(message_parts), AsyncUsageWarning)
300
+
301
+
302
+ def _safe_blocking_in_async_warning(original_func: types.FunctionType):
303
+ """
304
+ Safety wrapper around _blocking_in_async_warning to ensure it never raises exceptions.
305
+
306
+ This is non-critical functionality (just a warning), so we don't want it to break user code.
307
+ However, if the warning has been configured to be treated as an error (via filterwarnings),
308
+ we should let that propagate.
309
+ """
310
+ from ..config import config
311
+
312
+ if not config.get("async_warnings"):
313
+ return
314
+ try:
315
+ _blocking_in_async_warning(original_func)
316
+ except AsyncUsageWarning:
317
+ # Re-raise the warning if it's been configured as an error
318
+ raise
319
+ except Exception:
320
+ # Silently ignore any other errors in the warning system
321
+ # We don't want the warning mechanism itself to cause problems
322
+ pass
323
+
324
+
325
+ synchronizer = synchronicity.Synchronizer(blocking_in_async_callback=_safe_blocking_in_async_warning)
42
326
 
43
327
 
44
328
  def synchronize_api(obj, target_module=None):
@@ -389,6 +673,7 @@ class _WarnIfGeneratorIsNotConsumed:
389
673
  self.function_name = function_name
390
674
  self.iterated = False
391
675
  self.warned = False
676
+ self.__wrapped__ = gen
392
677
 
393
678
  def __aiter__(self):
394
679
  self.iterated = True
@@ -897,3 +1182,26 @@ async def async_chain(*generators: AsyncGenerator[T, None]) -> AsyncGenerator[T,
897
1182
  logger.exception(f"Error closing async generator: {e}")
898
1183
  if first_exception is not None:
899
1184
  raise first_exception
1185
+
1186
+
1187
+ def deprecate_aio_usage(deprecation_date: tuple[int, int, int], readable_sync_call: str):
1188
+ # Note: Currently only works on methods, not top level functions
1189
+ def deco(sync_implementation):
1190
+ if isinstance(sync_implementation, classmethod):
1191
+ sync_implementation = sync_implementation.__func__
1192
+ is_classmethod = True
1193
+ else:
1194
+ is_classmethod = False
1195
+
1196
+ async def _async_proxy(*args, **kwargs):
1197
+ deprecation_warning(
1198
+ deprecation_date,
1199
+ f"""The async constructor {readable_sync_call}.aio(...) will be deprecated in a future version of Modal.
1200
+ Please use {readable_sync_call}(...) instead (it doesn't perform any IO, and is safe in async contexts)
1201
+ """,
1202
+ )
1203
+ return sync_implementation(*args, **kwargs)
1204
+
1205
+ return MethodWithAio(sync_implementation, _async_proxy, synchronizer, is_classmethod=is_classmethod)
1206
+
1207
+ return deco
@@ -45,8 +45,11 @@ def patch_mock_servicer(cls):
45
45
  Also patches all unimplemented abstract methods in a mock servicer with default error implementations.
46
46
  """
47
47
 
48
- async def fallback(self, stream) -> None:
49
- raise GRPCError(Status.UNIMPLEMENTED, "Not implemented in mock servicer " + repr(cls))
48
+ def fallback(name: str):
49
+ async def _fallback(self, stream) -> None:
50
+ raise GRPCError(Status.UNIMPLEMENTED, f"{name} not implemented in mock servicer " + repr(cls))
51
+
52
+ return _fallback
50
53
 
51
54
  @contextlib.contextmanager
52
55
  def intercept(servicer):
@@ -85,7 +88,7 @@ def patch_mock_servicer(cls):
85
88
  for name in dir(cls):
86
89
  method = getattr(cls, name)
87
90
  if getattr(method, "__isabstractmethod__", False):
88
- setattr(cls, name, patch_grpc_method(name, fallback))
91
+ setattr(cls, name, patch_grpc_method(name, fallback(name)))
89
92
  elif name[0].isupper() and inspect.isfunction(method):
90
93
  setattr(cls, name, patch_grpc_method(name, method))
91
94
 
@@ -21,7 +21,7 @@ from synchronicity.async_wrap import asynccontextmanager
21
21
  from modal_proto import api_pb2
22
22
 
23
23
  from ._functions import _Function
24
- from ._ipython import is_notebook
24
+ from ._ipython import is_interactive_ipython
25
25
  from ._load_context import LoadContext
26
26
  from ._object import _get_environment_name, _Object
27
27
  from ._partial_function import (
@@ -509,9 +509,10 @@ class _App:
509
509
  if old_function is function:
510
510
  return # already added the same exact instance, ignore
511
511
 
512
- # In notebooks, re-registering the same function with the same app will cause a named collision.
513
- # This is common notebook coding behavior, so we hide the warning.
514
- if not is_notebook():
512
+ # In a notebook or interactive REPL it would be relatively normal to rerun a cell that
513
+ # registers a function multiple times (i.e. as you iterate on the Function definition),
514
+ # and we don't want to warn about a collision in that case.
515
+ if not is_interactive_ipython():
515
516
  logger.warning(
516
517
  f"Warning: function name '{function.tag}' collision!"
517
518
  " Overriding existing function "
@@ -71,14 +71,15 @@ class _Client:
71
71
  _client_from_env_lock: ClassVar[Optional[asyncio.Lock]] = None
72
72
  _cancellation_context: TaskContext
73
73
  _cancellation_context_event_loop: Optional[asyncio.AbstractEventLoop] = None
74
- _stub: Optional[modal_api_grpc.ModalClientModal]
74
+ _stub: Optional[modal_api_grpc.ModalClientModal] = None
75
75
  _auth_token_manager: Optional[_AuthTokenManager] = None
76
76
  _snapshotted: bool = False
77
+ client_type: "api_pb2.ClientType.ValueType"
77
78
 
78
79
  def __init__(
79
80
  self,
80
81
  server_url: str,
81
- client_type: int,
82
+ client_type: "api_pb2.ClientType.ValueType",
82
83
  credentials: Optional[tuple[str, str]],
83
84
  version: str = __version__,
84
85
  ):
@@ -26,13 +26,14 @@ class _Client:
26
26
  _stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
27
27
  _auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
28
28
  _snapshotted: bool
29
+ client_type: int
29
30
 
30
31
  def __init__(
31
32
  self,
32
33
  server_url: str,
33
34
  client_type: int,
34
35
  credentials: typing.Optional[tuple[str, str]],
35
- version: str = "1.2.7.dev12",
36
+ version: str = "1.2.7.dev13",
36
37
  ):
37
38
  """mdmd:hidden
38
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -155,13 +156,14 @@ class Client:
155
156
  _stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
156
157
  _auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
157
158
  _snapshotted: bool
159
+ client_type: int
158
160
 
159
161
  def __init__(
160
162
  self,
161
163
  server_url: str,
162
164
  client_type: int,
163
165
  credentials: typing.Optional[tuple[str, str]],
164
- version: str = "1.2.7.dev12",
166
+ version: str = "1.2.7.dev13",
165
167
  ):
166
168
  """mdmd:hidden
167
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -263,6 +263,7 @@ _SETTINGS = {
263
263
  ),
264
264
  "dev_suffix": _Setting("", transform=_enforce_suffix_rules),
265
265
  "max_throttle_wait": _Setting(None, transform=lambda x: int(x) if x else None),
266
+ "async_warnings": _Setting(False, transform=_to_boolean), # Feature flag for async API warnings
266
267
  }
267
268
 
268
269
 
@@ -252,6 +252,10 @@ class ServerWarning(UserWarning):
252
252
  """Warning originating from the Modal server and re-issued in client code."""
253
253
 
254
254
 
255
+ class AsyncUsageWarning(UserWarning):
256
+ """Warning emitted when a blocking Modal interface is used in an async context."""
257
+
258
+
255
259
  class InternalFailure(Error):
256
260
  """Retriable internal error."""
257
261
 
@@ -407,7 +407,7 @@ class Function(
407
407
 
408
408
  _call_generator: ___call_generator_spec
409
409
 
410
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
410
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
411
411
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
412
412
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
413
413
  ...
@@ -416,7 +416,7 @@ class Function(
416
416
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
417
417
  ...
418
418
 
419
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
419
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
420
420
 
421
421
  class __remote_gen_spec(typing_extensions.Protocol):
422
422
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -443,7 +443,7 @@ class Function(
443
443
  """
444
444
  ...
445
445
 
446
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
446
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
447
447
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
448
448
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
449
449
 
@@ -466,7 +466,7 @@ class Function(
466
466
  """
467
467
  ...
468
468
 
469
- _experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
469
+ _experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
470
470
 
471
471
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
472
472
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
@@ -474,7 +474,7 @@ class Function(
474
474
 
475
475
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
476
476
 
477
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
477
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
478
478
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
479
479
  """Calls the function with the given arguments, without waiting for the results.
480
480
 
@@ -495,7 +495,7 @@ class Function(
495
495
  """
496
496
  ...
497
497
 
498
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
498
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
499
499
 
500
500
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
501
501
  """Return the inner Python object wrapped by this Modal Function."""
@@ -864,7 +864,7 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
864
864
  fc_id = fc.object_id
865
865
 
866
866
  # Later, use the ID to re-instantiate the FunctionCall object
867
- fc = _FunctionCall.from_id(fc_id)
867
+ fc = FunctionCall.from_id(fc_id)
868
868
  result = fc.get()
869
869
  ```
870
870
 
@@ -873,27 +873,7 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
873
873
  """
874
874
  ...
875
875
 
876
- async def aio(
877
- self, /, function_call_id: str, client: typing.Optional[modal.client.Client] = None
878
- ) -> FunctionCall[typing.Any]:
879
- """Instantiate a FunctionCall object from an existing ID.
880
-
881
- Examples:
882
-
883
- ```python notest
884
- # Spawn a FunctionCall and keep track of its object ID
885
- fc = my_func.spawn()
886
- fc_id = fc.object_id
887
-
888
- # Later, use the ID to re-instantiate the FunctionCall object
889
- fc = _FunctionCall.from_id(fc_id)
890
- result = fc.get()
891
- ```
892
-
893
- Note that it's only necessary to re-instantiate the `FunctionCall` with this method
894
- if you no longer have access to the original object returned from `Function.spawn`.
895
- """
896
- ...
876
+ async def aio(self, /, function_call_id: str, client: typing.Optional[modal.client.Client] = None): ...
897
877
 
898
878
  from_id: typing.ClassVar[__from_id_spec]
899
879