modal 1.1.5.dev52__tar.gz → 1.3.1.dev20__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 (216) hide show
  1. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/PKG-INFO +10 -9
  2. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/README.md +5 -5
  3. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/__init__.py +4 -4
  4. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/__main__.py +4 -29
  5. modal-1.3.1.dev20/modal/_billing.py +84 -0
  6. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_clustered_functions.py +1 -3
  7. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_container_entrypoint.py +33 -208
  8. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_functions.py +173 -173
  9. modal-1.3.1.dev20/modal/_grpc_client.py +191 -0
  10. modal-1.3.1.dev20/modal/_ipython.py +21 -0
  11. modal-1.3.1.dev20/modal/_load_context.py +106 -0
  12. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_object.py +72 -21
  13. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_output.py +28 -20
  14. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_partial_function.py +31 -25
  15. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_resolver.py +44 -57
  16. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/container_io_manager.py +34 -29
  17. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/container_io_manager.pyi +42 -44
  18. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/gpu_memory_snapshot.py +9 -7
  19. modal-1.3.1.dev20/modal/_runtime/user_code_event_loop.py +80 -0
  20. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/user_code_imports.py +236 -10
  21. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_serialization.py +2 -1
  22. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_traceback.py +4 -13
  23. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_tunnel.py +16 -11
  24. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_tunnel.pyi +25 -3
  25. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/async_utils.py +337 -10
  26. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/auth_token_manager.py +1 -4
  27. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/blob_utils.py +29 -22
  28. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/function_utils.py +26 -22
  29. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/grpc_testing.py +6 -3
  30. modal-1.3.1.dev20/modal/_utils/grpc_utils.py +450 -0
  31. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/mount_utils.py +26 -1
  32. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/name_utils.py +2 -3
  33. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/package_utils.py +0 -1
  34. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/rand_pb_testing.py +8 -1
  35. modal-1.3.1.dev20/modal/_utils/task_command_router_client.py +524 -0
  36. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_vendor/cloudpickle.py +144 -48
  37. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/app.py +283 -109
  38. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/app.pyi +219 -56
  39. modal-1.3.1.dev20/modal/billing.py +5 -0
  40. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/2025.06.txt +6 -3
  41. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/PREVIEW.txt +2 -1
  42. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/base-images.json +4 -2
  43. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/_download.py +19 -3
  44. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/cluster.py +4 -2
  45. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/config.py +3 -1
  46. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/container.py +10 -6
  47. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/dict.py +5 -2
  48. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/entry_point.py +26 -2
  49. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/environment.py +6 -19
  50. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/launch.py +1 -76
  51. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/network_file_system.py +5 -20
  52. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/programs/run_jupyter.py +1 -1
  53. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/programs/vscode.py +1 -1
  54. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/queues.py +5 -4
  55. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/run.py +35 -195
  56. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/secret.py +1 -2
  57. modal-1.3.1.dev20/modal/cli/shell.py +375 -0
  58. modal-1.3.1.dev20/modal/cli/token.py +108 -0
  59. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/utils.py +1 -13
  60. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/volume.py +11 -17
  61. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/client.py +16 -125
  62. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/client.pyi +94 -144
  63. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cloud_bucket_mount.py +3 -1
  64. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cloud_bucket_mount.pyi +4 -0
  65. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cls.py +98 -105
  66. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cls.pyi +8 -85
  67. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/config.py +21 -1
  68. modal-1.3.1.dev20/modal/container_process.py +472 -0
  69. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/container_process.pyi +99 -38
  70. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/dict.py +71 -73
  71. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/dict.pyi +87 -133
  72. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/environments.py +15 -27
  73. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/environments.pyi +5 -15
  74. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/exception.py +154 -16
  75. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/experimental/__init__.py +34 -38
  76. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/experimental/flash.py +167 -74
  77. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/experimental/flash.pyi +102 -47
  78. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/file_io.py +50 -92
  79. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/file_io.pyi +117 -89
  80. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/functions.pyi +70 -136
  81. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/image.py +116 -86
  82. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/image.pyi +55 -35
  83. modal-1.3.1.dev20/modal/io_streams.py +822 -0
  84. modal-1.3.1.dev20/modal/io_streams.pyi +530 -0
  85. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/mount.py +66 -164
  86. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/mount.pyi +33 -179
  87. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/network_file_system.py +20 -51
  88. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/network_file_system.pyi +53 -94
  89. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/object.pyi +114 -22
  90. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/output.py +2 -2
  91. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/parallel_map.py +42 -44
  92. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/parallel_map.pyi +9 -17
  93. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/partial_function.pyi +4 -3
  94. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/proxy.py +14 -6
  95. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/proxy.pyi +10 -2
  96. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/queue.py +44 -76
  97. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/queue.pyi +81 -119
  98. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/runner.py +99 -100
  99. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/runner.pyi +44 -27
  100. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/sandbox.py +225 -107
  101. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/sandbox.pyi +226 -60
  102. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/secret.py +57 -84
  103. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/secret.pyi +28 -51
  104. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/serving.py +7 -11
  105. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/serving.pyi +7 -8
  106. modal-1.3.1.dev20/modal/snapshot.py +56 -0
  107. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/snapshot.pyi +18 -10
  108. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/token_flow.py +1 -1
  109. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/token_flow.pyi +4 -6
  110. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/volume.py +102 -96
  111. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/volume.pyi +102 -120
  112. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal.egg-info/PKG-INFO +10 -9
  113. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal.egg-info/SOURCES.txt +13 -15
  114. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal.egg-info/requires.txt +7 -2
  115. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/api.proto +206 -70
  116. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/api_grpc.py +75 -26
  117. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/api_pb2.py +1215 -1100
  118. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/api_pb2.pyi +556 -84
  119. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/api_pb2_grpc.py +147 -49
  120. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/api_pb2_grpc.pyi +49 -21
  121. modal-1.3.1.dev20/modal_proto/modal_api_grpc.py +195 -0
  122. modal-1.3.1.dev20/modal_proto/task_command_router.proto +164 -0
  123. modal-1.3.1.dev20/modal_proto/task_command_router_grpc.py +138 -0
  124. modal-1.3.1.dev20/modal_proto/task_command_router_pb2.py +180 -0
  125. modal-1.1.5.dev52/modal_proto/sandbox_router_pb2.pyi → modal-1.3.1.dev20/modal_proto/task_command_router_pb2.pyi +148 -57
  126. modal-1.3.1.dev20/modal_proto/task_command_router_pb2_grpc.py +272 -0
  127. modal-1.3.1.dev20/modal_proto/task_command_router_pb2_grpc.pyi +100 -0
  128. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_version/__init__.py +1 -1
  129. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_version/__main__.py +1 -1
  130. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/pyproject.toml +34 -10
  131. modal-1.1.5.dev52/modal/_ipython.py +0 -11
  132. modal-1.1.5.dev52/modal/_utils/grpc_utils.py +0 -293
  133. modal-1.1.5.dev52/modal/cli/programs/launch_instance_ssh.py +0 -94
  134. modal-1.1.5.dev52/modal/cli/programs/run_marimo.py +0 -95
  135. modal-1.1.5.dev52/modal/cli/token.py +0 -60
  136. modal-1.1.5.dev52/modal/container_process.py +0 -196
  137. modal-1.1.5.dev52/modal/io_streams.py +0 -471
  138. modal-1.1.5.dev52/modal/io_streams.pyi +0 -440
  139. modal-1.1.5.dev52/modal/snapshot.py +0 -42
  140. modal-1.1.5.dev52/modal_proto/modal_api_grpc.py +0 -192
  141. modal-1.1.5.dev52/modal_proto/modal_options_grpc.py +0 -3
  142. modal-1.1.5.dev52/modal_proto/options.proto +0 -19
  143. modal-1.1.5.dev52/modal_proto/options_grpc.py +0 -3
  144. modal-1.1.5.dev52/modal_proto/options_pb2.py +0 -35
  145. modal-1.1.5.dev52/modal_proto/options_pb2.pyi +0 -20
  146. modal-1.1.5.dev52/modal_proto/options_pb2_grpc.py +0 -4
  147. modal-1.1.5.dev52/modal_proto/options_pb2_grpc.pyi +0 -7
  148. modal-1.1.5.dev52/modal_proto/sandbox_router.proto +0 -125
  149. modal-1.1.5.dev52/modal_proto/sandbox_router_grpc.py +0 -89
  150. modal-1.1.5.dev52/modal_proto/sandbox_router_pb2.py +0 -128
  151. modal-1.1.5.dev52/modal_proto/sandbox_router_pb2_grpc.py +0 -169
  152. modal-1.1.5.dev52/modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
  153. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/LICENSE +0 -0
  154. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_clustered_functions.pyi +0 -0
  155. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_location.py +0 -0
  156. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_pty.py +0 -0
  157. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_resources.py +0 -0
  158. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/__init__.py +0 -0
  159. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/asgi.py +0 -0
  160. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/execution_context.py +0 -0
  161. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/execution_context.pyi +0 -0
  162. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_runtime/telemetry.py +0 -0
  163. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_type_manager.py +0 -0
  164. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/__init__.py +0 -0
  165. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/app_utils.py +0 -0
  166. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/bytes_io_segment_payload.py +0 -0
  167. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/deprecation.py +0 -0
  168. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/docker_utils.py +0 -0
  169. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/git_utils.py +0 -0
  170. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/hash_utils.py +0 -0
  171. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/http_utils.py +0 -0
  172. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/jwt_utils.py +0 -0
  173. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/logger.py +0 -0
  174. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/pattern_utils.py +0 -0
  175. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/shell_utils.py +0 -0
  176. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_utils/time_utils.py +0 -0
  177. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_vendor/__init__.py +0 -0
  178. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  179. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_vendor/tblib.py +0 -0
  180. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/_watcher.py +0 -0
  181. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/2023.12.312.txt +0 -0
  182. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/2023.12.txt +0 -0
  183. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/2024.04.txt +0 -0
  184. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/2024.10.txt +0 -0
  185. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/builder/README.md +0 -0
  186. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/call_graph.py +0 -0
  187. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/__init__.py +0 -0
  188. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/_traceback.py +0 -0
  189. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/app.py +0 -0
  190. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/import_refs.py +0 -0
  191. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/profile.py +0 -0
  192. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/cli/programs/__init__.py +0 -0
  193. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/experimental/ipython.py +0 -0
  194. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/file_pattern_matcher.py +0 -0
  195. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/functions.py +0 -0
  196. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/gpu.py +0 -0
  197. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/object.py +0 -0
  198. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/partial_function.py +0 -0
  199. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/py.typed +0 -0
  200. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/retries.py +0 -0
  201. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/running_app.py +0 -0
  202. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/schedule.py +0 -0
  203. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/scheduler_placement.py +0 -0
  204. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal/stream_type.py +0 -0
  205. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal.egg-info/dependency_links.txt +0 -0
  206. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal.egg-info/entry_points.txt +0 -0
  207. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal.egg-info/top_level.txt +0 -0
  208. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_docs/__init__.py +0 -0
  209. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_docs/gen_cli_docs.py +0 -0
  210. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_docs/gen_reference_docs.py +0 -0
  211. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_docs/mdmd/__init__.py +0 -0
  212. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_docs/mdmd/mdmd.py +0 -0
  213. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_docs/mdmd/signatures.py +0 -0
  214. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/__init__.py +0 -0
  215. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/modal_proto/py.typed +0 -0
  216. {modal-1.1.5.dev52 → modal-1.3.1.dev20}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.5.dev52
3
+ Version: 1.3.1.dev20
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -13,17 +13,18 @@ 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.15,>=3.10
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: aiohttp
20
20
  Requires-Dist: cbor2
21
21
  Requires-Dist: certifi
22
22
  Requires-Dist: click~=8.1
23
- Requires-Dist: grpclib<0.4.9,>=0.4.7
23
+ Requires-Dist: grpclib<0.4.10,>=0.4.7; python_version < "3.14"
24
+ Requires-Dist: grpclib<0.4.10,>=0.4.9; python_version >= "3.14"
24
25
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
25
26
  Requires-Dist: rich>=12.0.0
26
- Requires-Dist: synchronicity~=0.10.2
27
+ Requires-Dist: synchronicity~=0.11.1
27
28
  Requires-Dist: toml
28
29
  Requires-Dist: typer>=0.9
29
30
  Requires-Dist: types-certifi
@@ -32,14 +33,14 @@ Requires-Dist: watchfiles
32
33
  Requires-Dist: typing_extensions~=4.6
33
34
  Dynamic: license-file
34
35
 
35
- # Modal Python Library
36
+ # Modal Python SDK
36
37
 
37
38
  [![PyPI Version](https://img.shields.io/pypi/v/modal.svg)](https://pypi.org/project/modal/)
38
39
  [![License](https://img.shields.io/badge/license-apache_2.0-darkviolet.svg)](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
39
40
  [![Tests](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
40
41
  [![Slack](https://img.shields.io/badge/slack-join-blue.svg?logo=slack)](https://modal.com/slack)
41
42
 
42
- The [Modal](https://modal.com/) Python library provides convenient, on-demand
43
+ The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
43
44
  access to serverless cloud compute from Python scripts on your local computer.
44
45
 
45
46
  ## Documentation
@@ -51,12 +52,12 @@ a [user guide](https://modal.com/docs/guide), and the detailed
51
52
 
52
53
  ## Installation
53
54
 
54
- **This library requires Python 3.9 – 3.13.**
55
+ **This library requires Python 3.10 – 3.14.**
55
56
 
56
- Install the package with `pip`:
57
+ Install the package with `uv` or `pip`:
57
58
 
58
59
  ```bash
59
- pip install modal
60
+ uv pip install modal
60
61
  ```
61
62
 
62
63
  You can create a Modal account (or link your existing one) directly on the
@@ -1,11 +1,11 @@
1
- # Modal Python Library
1
+ # Modal Python SDK
2
2
 
3
3
  [![PyPI Version](https://img.shields.io/pypi/v/modal.svg)](https://pypi.org/project/modal/)
4
4
  [![License](https://img.shields.io/badge/license-apache_2.0-darkviolet.svg)](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
5
5
  [![Tests](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
6
6
  [![Slack](https://img.shields.io/badge/slack-join-blue.svg?logo=slack)](https://modal.com/slack)
7
7
 
8
- The [Modal](https://modal.com/) Python library provides convenient, on-demand
8
+ The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
9
9
  access to serverless cloud compute from Python scripts on your local computer.
10
10
 
11
11
  ## Documentation
@@ -17,12 +17,12 @@ a [user guide](https://modal.com/docs/guide), and the detailed
17
17
 
18
18
  ## Installation
19
19
 
20
- **This library requires Python 3.9 – 3.13.**
20
+ **This library requires Python 3.10 – 3.14.**
21
21
 
22
- Install the package with `pip`:
22
+ Install the package with `uv` or `pip`:
23
23
 
24
24
  ```bash
25
- pip install modal
25
+ uv pip install modal
26
26
  ```
27
27
 
28
28
  You can create a Modal account (or link your existing one) directly on the
@@ -1,10 +1,10 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import sys
3
3
 
4
- if sys.version_info[:2] < (3, 9):
5
- raise RuntimeError("This version of Modal requires at least Python 3.9")
6
- if sys.version_info[:2] >= (3, 14):
7
- raise RuntimeError("This version of Modal does not support Python 3.14+")
4
+ if sys.version_info[:2] < (3, 10):
5
+ raise RuntimeError("This version of Modal requires at least Python 3.10")
6
+ if sys.version_info[:2] >= (3, 15):
7
+ raise RuntimeError("This version of Modal does not support Python 3.15+")
8
8
 
9
9
  from modal_version import __version__
10
10
 
@@ -35,37 +35,12 @@ def main():
35
35
  ):
36
36
  raise
37
37
 
38
- from grpclib import GRPCError, Status
39
38
  from rich.panel import Panel
40
39
 
41
- if isinstance(exc, GRPCError):
42
- status_map = {
43
- Status.ABORTED: "Aborted",
44
- Status.ALREADY_EXISTS: "Already exists",
45
- Status.CANCELLED: "Cancelled",
46
- Status.DATA_LOSS: "Data loss",
47
- Status.DEADLINE_EXCEEDED: "Deadline exceeded",
48
- Status.FAILED_PRECONDITION: "Failed precondition",
49
- Status.INTERNAL: "Internal",
50
- Status.INVALID_ARGUMENT: "Invalid",
51
- Status.NOT_FOUND: "Not found",
52
- Status.OUT_OF_RANGE: "Out of range",
53
- Status.PERMISSION_DENIED: "Permission denied",
54
- Status.RESOURCE_EXHAUSTED: "Resource exhausted",
55
- Status.UNAUTHENTICATED: "Unauthenticaed",
56
- Status.UNAVAILABLE: "Unavailable",
57
- Status.UNIMPLEMENTED: "Unimplemented",
58
- Status.UNKNOWN: "Unknown",
59
- }
60
- title = f"Error: {status_map.get(exc.status, 'Unknown')}"
61
- content = str(exc.message)
62
- if exc.details:
63
- content += f"\n\nDetails: {exc.details}"
64
- else:
65
- title = "Error"
66
- content = str(exc)
67
- if notes := getattr(exc, "__notes__", []):
68
- content = f"{content}\n\nNote: {' '.join(notes)}"
40
+ title = "Error"
41
+ content = str(exc)
42
+ if notes := getattr(exc, "__notes__", []):
43
+ content = f"{content}\n\nNote: {' '.join(notes)}"
69
44
 
70
45
  console = make_console(stderr=True)
71
46
  panel = Panel(content, title=title, title_align="left", border_style="red")
@@ -0,0 +1,84 @@
1
+ # Copyright Modal Labs 2025
2
+ from datetime import datetime, timezone
3
+ from decimal import Decimal
4
+ from typing import Any, Optional, TypedDict
5
+
6
+ from modal_proto import api_pb2
7
+
8
+ from .client import _Client
9
+ from .exception import InvalidError
10
+
11
+
12
+ class WorkspaceBillingReportItem(TypedDict):
13
+ object_id: str
14
+ description: str
15
+ environment_name: str
16
+ interval_start: datetime
17
+ cost: Decimal
18
+ tags: dict[str, str]
19
+
20
+
21
+ async def _workspace_billing_report(
22
+ *,
23
+ start: datetime, # Start of the report, inclusive
24
+ end: Optional[datetime] = None, # End of the report, exclusive
25
+ resolution: str = "d", # Resolution, e.g. "d" for daily or "h" for hourly
26
+ tag_names: Optional[list[str]] = None, # Optional additional metadata to include
27
+ client: Optional[_Client] = None,
28
+ ) -> list[dict[str, Any]]:
29
+ """Generate a tabular report of workspace usage by object and time.
30
+
31
+ The result will be a list of dictionaries for each interval (determined by `resolution`)
32
+ between the `start` and `end` limits. The dictionary represents a single Modal object
33
+ that billing can be attributed to (e.g., an App) along with metadata (including user-defined
34
+ tags) for identifying that object.
35
+
36
+ The `start` and `end` parameters are required to either have a UTC timezone or to be
37
+ timezone-naive (which will be interpreted as UTC times). The timestamps in the result will
38
+ be in UTC. Cost will be reported for full intervals, even if the provided `start` or `end`
39
+ parameters are partial: `start` will be rounded to the beginning of its interval, while
40
+ partial `end` intervals will be excluded.
41
+
42
+ Additional user-provided metadata can be included in the report if the objects have tags
43
+ and `tag_names` (i.e., keys) are specified in the request. Note that tags will be attributed
44
+ to the entire interval even if they were added or removed at some point within it.
45
+
46
+ """
47
+ if client is None:
48
+ client = await _Client.from_env()
49
+
50
+ tag_names = tag_names or []
51
+
52
+ if end is None:
53
+ end = datetime.now(timezone.utc)
54
+
55
+ if start.tzinfo is None:
56
+ start = start.replace(tzinfo=timezone.utc)
57
+ elif start.tzinfo != timezone.utc:
58
+ raise InvalidError("Timezone-aware 'start' parameter must be in UTC.")
59
+
60
+ if end.tzinfo is None:
61
+ end = end.replace(tzinfo=timezone.utc)
62
+ elif end.tzinfo != timezone.utc:
63
+ raise InvalidError("Timezone-aware 'end' parameter must be in UTC.")
64
+
65
+ request = api_pb2.WorkspaceBillingReportRequest(
66
+ resolution=resolution,
67
+ tag_names=tag_names,
68
+ )
69
+ request.start_timestamp.FromDatetime(start)
70
+ request.end_timestamp.FromDatetime(end)
71
+
72
+ rows = []
73
+ async for pb_item in client.stub.WorkspaceBillingReport.unary_stream(request):
74
+ item = {
75
+ "object_id": pb_item.object_id,
76
+ "description": pb_item.description,
77
+ "environment_name": pb_item.environment_name,
78
+ "interval_start": pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
79
+ "cost": Decimal(pb_item.cost),
80
+ "tags": dict(pb_item.tags),
81
+ }
82
+ rows.append(item)
83
+
84
+ return rows
@@ -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
@@ -51,8 +43,8 @@ from ._runtime.container_io_manager import (
51
43
  )
52
44
 
53
45
  if TYPE_CHECKING:
54
- import modal._object
55
46
  import modal._runtime.container_io_manager
47
+ import modal._runtime.user_code_imports
56
48
 
57
49
 
58
50
  class DaemonizedThreadPool:
@@ -101,81 +93,6 @@ class DaemonizedThreadPool:
101
93
  self.inputs.put((func, args))
102
94
 
103
95
 
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
96
  def call_function(
180
97
  user_code_event_loop: UserCodeEventLoop,
181
98
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
@@ -331,23 +248,25 @@ def call_function(
331
248
  signal.signal(signal.SIGUSR1, usr1_handler) # reset signal handler
332
249
 
333
250
 
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)
251
+ def get_serialized_user_class_and_function(
252
+ function_def: api_pb2.Function, client: _Client
253
+ ) -> tuple[Optional[type], Optional[types.FunctionType]]:
254
+ if function_def.definition_type == api_pb2.Function.DEFINITION_TYPE_SERIALIZED:
255
+ assert function_def.function_serialized or function_def.class_serialized
256
+
257
+ if function_def.function_serialized:
258
+ ser_fun = deserialize(function_def.function_serialized, client)
259
+ else:
260
+ ser_fun = None
261
+
262
+ if function_def.class_serialized:
263
+ ser_usr_cls = deserialize(function_def.class_serialized, client)
264
+ else:
265
+ ser_usr_cls = None
266
+ else:
267
+ ser_usr_cls, ser_fun = None, None
268
+
269
+ return ser_usr_cls, ser_fun
351
270
 
352
271
 
353
272
  def main(container_args: api_pb2.ContainerArguments, client: Client):
@@ -357,34 +276,20 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
357
276
  active_app: _App
358
277
  service: Service
359
278
  function_def = container_args.function_def
360
- is_auto_snapshot: bool = function_def.is_auto_snapshot
361
279
  # The worker sets this flag to "1" for snapshot and restore tasks. Otherwise, this flag is unset,
362
280
  # in which case snapshots should be disabled.
363
281
  is_snapshotting_function = (
364
282
  function_def.is_checkpointing_function and os.environ.get("MODAL_ENABLE_SNAP_RESTORE") == "1"
365
283
  )
366
284
 
367
- _client: _Client = synchronizer._translate_in(client) # TODO(erikbern): ugly
285
+ _client: _Client = cast(_Client, synchronizer._translate_in(client)) # TODO(erikbern): ugly
368
286
 
369
287
  # Call ContainerHello - currently a noop but might be used later for things
370
288
  container_io_manager.hello()
371
289
 
372
290
  with container_io_manager.heartbeats(is_snapshotting_function), UserCodeEventLoop() as event_loop:
373
291
  # 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
292
+ ser_usr_cls, ser_fun = get_serialized_user_class_and_function(function_def, _client)
388
293
 
389
294
  # Initialize the function, importing user code.
390
295
  with container_io_manager.handle_user_exception():
@@ -436,7 +341,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
436
341
 
437
342
  # Initialize objects on the app.
438
343
  # This is basically only functions and classes - anything else is deprecated and will be unsupported soon
439
- app: App = synchronizer._translate_out(active_app)
344
+ app: App = cast(App, synchronizer._translate_out(active_app))
440
345
  app._init_container(client, container_app)
441
346
 
442
347
  # Hydrate all function dependencies.
@@ -450,10 +355,13 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
450
355
  f"Function has {len(service.service_deps)} dependencies"
451
356
  f" but container got {len(dep_object_ids)} object ids.\n"
452
357
  f"Code deps: {service.service_deps}\n"
453
- f"Object ids: {dep_object_ids}"
358
+ f"Object ids: {dep_object_ids}\n"
359
+ "\n"
360
+ "This can happen if you are defining Modal objects under a conditional statement "
361
+ "that evaluates differently in the local and remote environments."
454
362
  )
455
363
  for object_id, obj in zip(dep_object_ids, service.service_deps):
456
- metadata: Message = container_app.object_handle_metadata[object_id]
364
+ metadata: Optional[Message] = container_app.object_handle_metadata[object_id]
457
365
  obj._hydrate(object_id, _client, metadata)
458
366
 
459
367
  # Initialize clustered functions.
@@ -464,91 +372,8 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
464
372
  function_def._experimental_group_size,
465
373
  )
466
374
 
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)
375
+ with service.execution_context(event_loop, container_io_manager) as finalized_functions:
376
+ call_function(event_loop, container_io_manager, finalized_functions, batch_max_size, batch_wait_ms)
552
377
 
553
378
 
554
379
  if __name__ == "__main__":