modal 1.2.1.dev21__tar.gz → 1.2.5.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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

Files changed (204) hide show
  1. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/PKG-INFO +2 -2
  2. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_clustered_functions.py +1 -3
  3. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_container_entrypoint.py +33 -207
  4. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_functions.py +116 -101
  5. modal-1.2.5.dev4/modal/_grpc_client.py +171 -0
  6. modal-1.2.5.dev4/modal/_load_context.py +106 -0
  7. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_object.py +54 -19
  8. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_output.py +3 -4
  9. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_partial_function.py +1 -1
  10. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_resolver.py +21 -35
  11. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/container_io_manager.py +21 -22
  12. modal-1.2.5.dev4/modal/_runtime/user_code_event_loop.py +80 -0
  13. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/user_code_imports.py +209 -9
  14. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_tunnel.py +11 -2
  15. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_tunnel.pyi +25 -3
  16. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/async_utils.py +27 -8
  17. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/auth_token_manager.py +1 -4
  18. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/blob_utils.py +3 -4
  19. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/function_utils.py +1 -2
  20. modal-1.2.5.dev4/modal/_utils/grpc_utils.py +455 -0
  21. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/mount_utils.py +26 -1
  22. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/task_command_router_client.py +3 -4
  23. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/app.py +57 -25
  24. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/app.pyi +11 -4
  25. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/config.py +3 -1
  26. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/container.py +1 -2
  27. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/dict.py +5 -2
  28. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/entry_point.py +2 -2
  29. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/network_file_system.py +1 -4
  30. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/queues.py +5 -4
  31. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/run.py +3 -199
  32. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/secret.py +1 -2
  33. modal-1.2.5.dev4/modal/cli/shell.py +237 -0
  34. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/client.py +14 -124
  35. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/client.pyi +8 -98
  36. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cloud_bucket_mount.py +3 -1
  37. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cloud_bucket_mount.pyi +4 -0
  38. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cls.py +76 -36
  39. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cls.pyi +3 -0
  40. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/config.py +6 -1
  41. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/container_process.py +4 -8
  42. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/dict.py +26 -17
  43. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/dict.pyi +2 -0
  44. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/environments.py +16 -8
  45. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/environments.pyi +6 -2
  46. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/experimental/__init__.py +15 -3
  47. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/experimental/flash.py +11 -14
  48. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/experimental/flash.pyi +4 -0
  49. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/file_io.py +13 -27
  50. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/functions.pyi +18 -12
  51. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/image.py +48 -30
  52. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/image.pyi +13 -9
  53. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/io_streams.py +162 -123
  54. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/io_streams.pyi +63 -100
  55. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/mount.py +36 -26
  56. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/mount.pyi +33 -7
  57. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/network_file_system.py +19 -11
  58. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/network_file_system.pyi +12 -2
  59. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/object.pyi +35 -8
  60. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/parallel_map.py +29 -31
  61. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/parallel_map.pyi +3 -9
  62. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/proxy.py +14 -6
  63. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/proxy.pyi +10 -2
  64. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/queue.py +31 -23
  65. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/queue.pyi +12 -2
  66. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/runner.py +62 -58
  67. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/runner.pyi +4 -3
  68. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/sandbox.py +56 -82
  69. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/sandbox.pyi +0 -10
  70. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/secret.py +61 -44
  71. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/secret.pyi +21 -4
  72. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/serving.py +7 -11
  73. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/serving.pyi +7 -8
  74. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/snapshot.py +11 -8
  75. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/token_flow.py +1 -1
  76. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/volume.py +45 -29
  77. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/volume.pyi +2 -0
  78. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal.egg-info/PKG-INFO +2 -2
  79. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal.egg-info/SOURCES.txt +4 -0
  80. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/api.proto +55 -10
  81. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/api_grpc.py +1 -0
  82. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/api_pb2.py +1064 -1017
  83. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/api_pb2.pyi +197 -30
  84. modal-1.2.5.dev4/modal_proto/modal_api_grpc.py +194 -0
  85. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_version/__init__.py +1 -1
  86. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/pyproject.toml +1 -1
  87. modal-1.2.1.dev21/modal/_utils/grpc_utils.py +0 -293
  88. modal-1.2.1.dev21/modal_proto/modal_api_grpc.py +0 -194
  89. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/LICENSE +0 -0
  90. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/README.md +0 -0
  91. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/__init__.py +0 -0
  92. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/__main__.py +0 -0
  93. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_billing.py +0 -0
  94. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_clustered_functions.pyi +0 -0
  95. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_ipython.py +0 -0
  96. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_location.py +0 -0
  97. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_pty.py +0 -0
  98. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_resources.py +0 -0
  99. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/__init__.py +0 -0
  100. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/asgi.py +0 -0
  101. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/container_io_manager.pyi +0 -0
  102. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/execution_context.py +0 -0
  103. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/execution_context.pyi +0 -0
  104. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  105. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_runtime/telemetry.py +0 -0
  106. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_serialization.py +0 -0
  107. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_traceback.py +0 -0
  108. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_type_manager.py +0 -0
  109. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/__init__.py +0 -0
  110. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/app_utils.py +0 -0
  111. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
  112. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/deprecation.py +0 -0
  113. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/docker_utils.py +0 -0
  114. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/git_utils.py +0 -0
  115. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/grpc_testing.py +0 -0
  116. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/hash_utils.py +0 -0
  117. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/http_utils.py +0 -0
  118. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/jwt_utils.py +0 -0
  119. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/logger.py +0 -0
  120. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/name_utils.py +0 -0
  121. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/package_utils.py +0 -0
  122. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/pattern_utils.py +0 -0
  123. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/rand_pb_testing.py +0 -0
  124. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/shell_utils.py +0 -0
  125. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_utils/time_utils.py +0 -0
  126. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_vendor/__init__.py +0 -0
  127. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  128. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_vendor/cloudpickle.py +0 -0
  129. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_vendor/tblib.py +0 -0
  130. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/_watcher.py +0 -0
  131. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/billing.py +0 -0
  132. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/2023.12.312.txt +0 -0
  133. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/2023.12.txt +0 -0
  134. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/2024.04.txt +0 -0
  135. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/2024.10.txt +0 -0
  136. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/2025.06.txt +0 -0
  137. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/PREVIEW.txt +0 -0
  138. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/README.md +0 -0
  139. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/builder/base-images.json +0 -0
  140. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/call_graph.py +0 -0
  141. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/__init__.py +0 -0
  142. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/_download.py +0 -0
  143. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/_traceback.py +0 -0
  144. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/app.py +0 -0
  145. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/cluster.py +0 -0
  146. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/environment.py +0 -0
  147. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/import_refs.py +0 -0
  148. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/launch.py +0 -0
  149. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/profile.py +0 -0
  150. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/programs/__init__.py +0 -0
  151. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/programs/launch_instance_ssh.py +0 -0
  152. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/programs/run_jupyter.py +0 -0
  153. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/programs/run_marimo.py +0 -0
  154. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/programs/vscode.py +0 -0
  155. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/token.py +0 -0
  156. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/utils.py +0 -0
  157. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/cli/volume.py +0 -0
  158. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/container_process.pyi +0 -0
  159. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/exception.py +0 -0
  160. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/experimental/ipython.py +0 -0
  161. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/file_io.pyi +0 -0
  162. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/file_pattern_matcher.py +0 -0
  163. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/functions.py +0 -0
  164. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/gpu.py +0 -0
  165. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/object.py +0 -0
  166. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/output.py +0 -0
  167. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/partial_function.py +0 -0
  168. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/partial_function.pyi +0 -0
  169. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/py.typed +0 -0
  170. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/retries.py +0 -0
  171. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/running_app.py +0 -0
  172. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/schedule.py +0 -0
  173. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/scheduler_placement.py +0 -0
  174. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/snapshot.pyi +0 -0
  175. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/stream_type.py +0 -0
  176. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal/token_flow.pyi +0 -0
  177. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal.egg-info/dependency_links.txt +0 -0
  178. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal.egg-info/entry_points.txt +0 -0
  179. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal.egg-info/requires.txt +0 -0
  180. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal.egg-info/top_level.txt +0 -0
  181. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_docs/__init__.py +0 -0
  182. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_docs/gen_cli_docs.py +0 -0
  183. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_docs/gen_reference_docs.py +0 -0
  184. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_docs/mdmd/__init__.py +0 -0
  185. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_docs/mdmd/mdmd.py +0 -0
  186. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_docs/mdmd/signatures.py +0 -0
  187. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/__init__.py +0 -0
  188. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/api_pb2_grpc.py +0 -0
  189. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/api_pb2_grpc.pyi +0 -0
  190. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/py.typed +0 -0
  191. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/sandbox_router.proto +0 -0
  192. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/sandbox_router_grpc.py +0 -0
  193. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/sandbox_router_pb2.py +0 -0
  194. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/sandbox_router_pb2.pyi +0 -0
  195. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
  196. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
  197. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/task_command_router.proto +0 -0
  198. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/task_command_router_grpc.py +0 -0
  199. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/task_command_router_pb2.py +0 -0
  200. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/task_command_router_pb2.pyi +0 -0
  201. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  202. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  203. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/modal_version/__main__.py +0 -0
  204. {modal-1.2.1.dev21 → modal-1.2.5.dev4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.1.dev21
3
+ Version: 1.2.5.dev4
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.9
16
+ Requires-Python: <3.14,>=3.9
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: aiohttp
@@ -5,7 +5,6 @@ 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
9
8
  from modal.client import _Client
10
9
  from modal.exception import InvalidError
11
10
  from modal_proto import api_pb2
@@ -61,8 +60,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
61
60
  os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
62
61
 
63
62
  if world_size > 1:
64
- resp: api_pb2.TaskClusterHelloResponse = await retry_transient_errors(
65
- client.stub.TaskClusterHello,
63
+ resp = await client.stub.TaskClusterHello(
66
64
  api_pb2.TaskClusterHelloRequest(
67
65
  task_id=task_id,
68
66
  container_ip=container_ip,
@@ -15,27 +15,19 @@ if telemetry_socket:
15
15
  instrument_imports(telemetry_socket)
16
16
 
17
17
  import asyncio
18
- import inspect
19
18
  import queue
20
19
  import signal
21
- import sys
22
20
  import threading
23
21
  import time
24
- from collections.abc import Sequence
25
- from typing import TYPE_CHECKING, Any, Callable, Optional
22
+ import types
23
+ from typing import TYPE_CHECKING, Any, Optional, cast
26
24
 
27
25
  from google.protobuf.message import Message
28
26
 
29
27
  from modal._clustered_functions import initialize_clustered_function
30
- from modal._partial_function import (
31
- _find_callables_for_obj,
32
- _PartialFunctionFlags,
33
- )
28
+ from modal._runtime.user_code_event_loop import UserCodeEventLoop
34
29
  from modal._serialization import deserialize, deserialize_params
35
30
  from modal._utils.async_utils import TaskContext, aclosing, synchronizer
36
- from modal._utils.function_utils import (
37
- callable_has_non_self_params,
38
- )
39
31
  from modal.app import App, _App
40
32
  from modal.client import Client, _Client
41
33
  from modal.config import logger
@@ -53,6 +45,7 @@ from ._runtime.container_io_manager import (
53
45
  if TYPE_CHECKING:
54
46
  import modal._object
55
47
  import modal._runtime.container_io_manager
48
+ import modal._runtime.user_code_imports
56
49
 
57
50
 
58
51
  class DaemonizedThreadPool:
@@ -101,81 +94,6 @@ class DaemonizedThreadPool:
101
94
  self.inputs.put((func, args))
102
95
 
103
96
 
104
- class UserCodeEventLoop:
105
- """Run an async event loop as a context manager and handle signals.
106
-
107
- This will run all *user supplied* async code, i.e. async functions, as well as async enter/exit managers
108
-
109
- The following signals are handled while a coroutine is running on the event loop until
110
- completion (and then handlers are deregistered):
111
-
112
- - `SIGUSR1`: converted to an async task cancellation. Note that this only affects the event
113
- loop, and the signal handler defined here doesn't run for sync functions.
114
- - `SIGINT`: Unless the global signal handler has been set to SIGIGN, the loop's signal handler
115
- is set to cancel the current task and raise KeyboardInterrupt to the caller.
116
- """
117
-
118
- def __enter__(self):
119
- self.loop = asyncio.new_event_loop()
120
- self.tasks = set()
121
- return self
122
-
123
- def __exit__(self, exc_type, exc_value, traceback):
124
- self.loop.run_until_complete(self.loop.shutdown_asyncgens())
125
- if sys.version_info[:2] >= (3, 9):
126
- self.loop.run_until_complete(self.loop.shutdown_default_executor()) # Introduced in Python 3.9
127
-
128
- for task in self.tasks:
129
- task.cancel()
130
-
131
- self.loop.close()
132
-
133
- def create_task(self, coro):
134
- task = self.loop.create_task(coro)
135
- self.tasks.add(task)
136
- task.add_done_callback(self.tasks.discard)
137
- return task
138
-
139
- def run(self, coro):
140
- task = asyncio.ensure_future(coro, loop=self.loop)
141
- self._sigints = 0
142
-
143
- def _sigint_handler():
144
- # cancel the task in order to have run_until_complete return soon and
145
- # prevent a bunch of unwanted tracebacks when shutting down the
146
- # event loop.
147
-
148
- # this basically replicates the sigint handler installed by asyncio.run()
149
- self._sigints += 1
150
- if self._sigints == 1:
151
- # first sigint is graceful
152
- task.cancel()
153
- return
154
-
155
- # this should normally not happen, but the second sigint would "hard kill" the event loop!
156
- raise KeyboardInterrupt()
157
-
158
- ignore_sigint = signal.getsignal(signal.SIGINT) == signal.SIG_IGN
159
- if not ignore_sigint:
160
- self.loop.add_signal_handler(signal.SIGINT, _sigint_handler)
161
-
162
- # Before Python 3.9 there is no argument to Task.cancel
163
- if sys.version_info[:2] >= (3, 9):
164
- self.loop.add_signal_handler(signal.SIGUSR1, task.cancel, "Input was cancelled by user")
165
- else:
166
- self.loop.add_signal_handler(signal.SIGUSR1, task.cancel)
167
-
168
- try:
169
- return self.loop.run_until_complete(task)
170
- except asyncio.CancelledError:
171
- if self._sigints > 0:
172
- raise KeyboardInterrupt()
173
- finally:
174
- self.loop.remove_signal_handler(signal.SIGUSR1)
175
- if not ignore_sigint:
176
- self.loop.remove_signal_handler(signal.SIGINT)
177
-
178
-
179
97
  def call_function(
180
98
  user_code_event_loop: UserCodeEventLoop,
181
99
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
@@ -331,23 +249,25 @@ def call_function(
331
249
  signal.signal(signal.SIGUSR1, usr1_handler) # reset signal handler
332
250
 
333
251
 
334
- def call_lifecycle_functions(
335
- event_loop: UserCodeEventLoop,
336
- container_io_manager, #: ContainerIOManager, TODO: this type is generated at runtime
337
- funcs: Sequence[Callable[..., Any]],
338
- ) -> None:
339
- """Call function(s), can be sync or async, but any return values are ignored."""
340
- with container_io_manager.handle_user_exception():
341
- for func in funcs:
342
- # We are deprecating parametrized exit methods but want to gracefully handle old code.
343
- # We can remove this once the deprecation in the actual @exit decorator is enforced.
344
- args = (None, None, None) if callable_has_non_self_params(func) else ()
345
- # in case func is non-async, it's executed here and sigint will by default
346
- # interrupt it using a KeyboardInterrupt exception
347
- res = func(*args)
348
- if inspect.iscoroutine(res):
349
- # if however func is async, we have to jump through some hoops
350
- event_loop.run(res)
252
+ def get_serialized_user_class_and_function(
253
+ function_def: api_pb2.Function, client: _Client
254
+ ) -> tuple[Optional[type], Optional[types.FunctionType]]:
255
+ if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
256
+ assert function_def.function_serialized or function_def.class_serialized
257
+
258
+ if function_def.function_serialized:
259
+ ser_fun = deserialize(function_def.function_serialized, client)
260
+ else:
261
+ ser_fun = None
262
+
263
+ if function_def.class_serialized:
264
+ ser_usr_cls = deserialize(function_def.class_serialized, client)
265
+ else:
266
+ ser_usr_cls = None
267
+ else:
268
+ ser_usr_cls, ser_fun = None, None
269
+
270
+ return ser_usr_cls, ser_fun
351
271
 
352
272
 
353
273
  def main(container_args: api_pb2.ContainerArguments, client: Client):
@@ -357,34 +277,20 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
357
277
  active_app: _App
358
278
  service: Service
359
279
  function_def = container_args.function_def
360
- is_auto_snapshot: bool = function_def.is_auto_snapshot
361
280
  # The worker sets this flag to "1" for snapshot and restore tasks. Otherwise, this flag is unset,
362
281
  # in which case snapshots should be disabled.
363
282
  is_snapshotting_function = (
364
283
  function_def.is_checkpointing_function and os.environ.get("MODAL_ENABLE_SNAP_RESTORE") == "1"
365
284
  )
366
285
 
367
- _client: _Client = synchronizer._translate_in(client) # TODO(erikbern): ugly
286
+ _client: _Client = cast(_Client, synchronizer._translate_in(client)) # TODO(erikbern): ugly
368
287
 
369
288
  # Call ContainerHello - currently a noop but might be used later for things
370
289
  container_io_manager.hello()
371
290
 
372
291
  with container_io_manager.heartbeats(is_snapshotting_function), UserCodeEventLoop() as event_loop:
373
292
  # If this is a serialized function, fetch the definition from the server
374
- if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
375
- assert function_def.function_serialized or function_def.class_serialized
376
-
377
- if function_def.function_serialized:
378
- ser_fun = deserialize(function_def.function_serialized, _client)
379
- else:
380
- ser_fun = None
381
-
382
- if function_def.class_serialized:
383
- ser_usr_cls = deserialize(function_def.class_serialized, _client)
384
- else:
385
- ser_usr_cls = None
386
- else:
387
- ser_usr_cls, ser_fun = None, None
293
+ ser_usr_cls, ser_fun = get_serialized_user_class_and_function(function_def, _client)
388
294
 
389
295
  # Initialize the function, importing user code.
390
296
  with container_io_manager.handle_user_exception():
@@ -436,7 +342,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
436
342
 
437
343
  # Initialize objects on the app.
438
344
  # This is basically only functions and classes - anything else is deprecated and will be unsupported soon
439
- app: App = synchronizer._translate_out(active_app)
345
+ app: App = cast(App, synchronizer._translate_out(active_app))
440
346
  app._init_container(client, container_app)
441
347
 
442
348
  # Hydrate all function dependencies.
@@ -450,10 +356,13 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
450
356
  f"Function has {len(service.service_deps)} dependencies"
451
357
  f" but container got {len(dep_object_ids)} object ids.\n"
452
358
  f"Code deps: {service.service_deps}\n"
453
- f"Object ids: {dep_object_ids}"
359
+ f"Object ids: {dep_object_ids}\n"
360
+ "\n"
361
+ "This can happen if you are defining Modal objects under a conditional statement "
362
+ "that evaluates differently in the local and remote environments."
454
363
  )
455
364
  for object_id, obj in zip(dep_object_ids, service.service_deps):
456
- metadata: Message = container_app.object_handle_metadata[object_id]
365
+ metadata: Optional[Message] = container_app.object_handle_metadata[object_id]
457
366
  obj._hydrate(object_id, _client, metadata)
458
367
 
459
368
  # Initialize clustered functions.
@@ -464,91 +373,8 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
464
373
  function_def._experimental_group_size,
465
374
  )
466
375
 
467
- # Identify all "enter" methods that need to run before we snapshot.
468
- if service.user_cls_instance is not None and not is_auto_snapshot:
469
- pre_snapshot_methods = _find_callables_for_obj(
470
- service.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
471
- )
472
- call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
473
-
474
- # If this container is being used to create a checkpoint, checkpoint the container after
475
- # global imports and initialization. Checkpointed containers run from this point onwards.
476
- if is_snapshotting_function:
477
- container_io_manager.memory_snapshot()
478
-
479
- # Install hooks for interactive functions.
480
- def breakpoint_wrapper():
481
- # note: it would be nice to not have breakpoint_wrapper() included in the backtrace
482
- container_io_manager.interact(from_breakpoint=True)
483
- import pdb
484
-
485
- frame = inspect.currentframe().f_back
486
-
487
- pdb.Pdb().set_trace(frame)
488
-
489
- sys.breakpointhook = breakpoint_wrapper
490
-
491
- # Identify the "enter" methods to run after resuming from a snapshot.
492
- if service.user_cls_instance is not None and not is_auto_snapshot:
493
- post_snapshot_methods = _find_callables_for_obj(
494
- service.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
495
- )
496
- call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
497
-
498
- with container_io_manager.handle_user_exception():
499
- finalized_functions = service.get_finalized_functions(function_def, container_io_manager)
500
- # Execute the function.
501
- lifespan_background_tasks = []
502
- try:
503
- for finalized_function in finalized_functions.values():
504
- if finalized_function.lifespan_manager:
505
- lifespan_background_tasks.append(
506
- event_loop.create_task(finalized_function.lifespan_manager.background_task())
507
- )
508
- with container_io_manager.handle_user_exception():
509
- event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
510
- call_function(
511
- event_loop,
512
- container_io_manager,
513
- finalized_functions,
514
- batch_max_size,
515
- batch_wait_ms,
516
- )
517
- finally:
518
- # Run exit handlers. From this point onward, ignore all SIGINT signals that come from
519
- # graceful shutdowns originating on the worker, as well as stray SIGUSR1 signals that
520
- # may have been sent to cancel inputs.
521
- int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
522
- usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
523
-
524
- try:
525
- try:
526
- # run lifespan shutdown for asgi apps
527
- for finalized_function in finalized_functions.values():
528
- if finalized_function.lifespan_manager:
529
- with container_io_manager.handle_user_exception():
530
- event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
531
- finally:
532
- # no need to keep the lifespan asgi call around - we send it no more messages
533
- for lifespan_background_task in lifespan_background_tasks:
534
- lifespan_background_task.cancel() # prevent dangling tasks
535
-
536
- # Identify "exit" methods and run them.
537
- # want to make sure this is called even if the lifespan manager fails
538
- if service.user_cls_instance is not None and not is_auto_snapshot:
539
- exit_methods = _find_callables_for_obj(service.user_cls_instance, _PartialFunctionFlags.EXIT)
540
- call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
541
-
542
- # Finally, commit on exit to catch uncommitted volume changes and surface background
543
- # commit errors.
544
- container_io_manager.volume_commit(
545
- [v.volume_id for v in function_def.volume_mounts if v.allow_background_commits]
546
- )
547
- finally:
548
- # Restore the original signal handler, needed for container_test hygiene since the
549
- # test runs `main()` multiple times in the same process.
550
- signal.signal(signal.SIGINT, int_handler)
551
- signal.signal(signal.SIGUSR1, usr1_handler)
376
+ with service.execution_context(event_loop, container_io_manager) as finalized_functions:
377
+ call_function(event_loop, container_io_manager, finalized_functions, batch_max_size, batch_wait_ms)
552
378
 
553
379
 
554
380
  if __name__ == "__main__":