modAL 1.2.5__tar.gz → 1.2.5.dev1__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 (203) hide show
  1. {modal-1.2.5 → modal-1.2.5.dev1}/PKG-INFO +1 -1
  2. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_container_entrypoint.py +206 -29
  3. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_functions.py +0 -5
  4. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_partial_function.py +0 -2
  5. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/user_code_imports.py +10 -236
  6. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/blob_utils.py +5 -18
  7. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/function_utils.py +12 -9
  8. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/grpc_utils.py +1 -3
  9. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/task_command_router_client.py +73 -46
  10. {modal-1.2.5 → modal-1.2.5.dev1}/modal/app.py +3 -28
  11. modal-1.2.5.dev1/modal/cli/shell.py +237 -0
  12. {modal-1.2.5 → modal-1.2.5.dev1}/modal/client.pyi +10 -2
  13. {modal-1.2.5 → modal-1.2.5.dev1}/modal/dict.py +6 -31
  14. {modal-1.2.5 → modal-1.2.5.dev1}/modal/dict.pyi +0 -2
  15. {modal-1.2.5 → modal-1.2.5.dev1}/modal/experimental/__init__.py +2 -8
  16. {modal-1.2.5 → modal-1.2.5.dev1}/modal/experimental/flash.py +16 -132
  17. {modal-1.2.5 → modal-1.2.5.dev1}/modal/experimental/flash.pyi +0 -60
  18. {modal-1.2.5 → modal-1.2.5.dev1}/modal/functions.pyi +6 -7
  19. {modal-1.2.5 → modal-1.2.5.dev1}/modal/io_streams.py +99 -78
  20. {modal-1.2.5 → modal-1.2.5.dev1}/modal/io_streams.pyi +26 -28
  21. {modal-1.2.5 → modal-1.2.5.dev1}/modal/mount.py +8 -14
  22. {modal-1.2.5 → modal-1.2.5.dev1}/modal/runner.py +1 -1
  23. {modal-1.2.5 → modal-1.2.5.dev1}/modal/volume.py +1 -5
  24. {modal-1.2.5 → modal-1.2.5.dev1}/modal.egg-info/PKG-INFO +1 -1
  25. {modal-1.2.5 → modal-1.2.5.dev1}/modal.egg-info/SOURCES.txt +6 -1
  26. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/api.proto +4 -20
  27. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/api_pb2.py +874 -880
  28. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/api_pb2.pyi +9 -34
  29. modal-1.2.5.dev1/modal_proto/sandbox_router.proto +145 -0
  30. modal-1.2.5.dev1/modal_proto/sandbox_router_grpc.py +105 -0
  31. modal-1.2.5.dev1/modal_proto/sandbox_router_pb2.py +149 -0
  32. modal-1.2.5.dev1/modal_proto/sandbox_router_pb2.pyi +333 -0
  33. modal-1.2.5.dev1/modal_proto/sandbox_router_pb2_grpc.py +203 -0
  34. modal-1.2.5.dev1/modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  35. {modal-1.2.5 → modal-1.2.5.dev1}/modal_version/__init__.py +1 -1
  36. {modal-1.2.5 → modal-1.2.5.dev1}/pyproject.toml +4 -1
  37. modal-1.2.5/modal/_runtime/user_code_event_loop.py +0 -80
  38. modal-1.2.5/modal/cli/shell.py +0 -375
  39. {modal-1.2.5 → modal-1.2.5.dev1}/LICENSE +0 -0
  40. {modal-1.2.5 → modal-1.2.5.dev1}/README.md +0 -0
  41. {modal-1.2.5 → modal-1.2.5.dev1}/modal/__init__.py +0 -0
  42. {modal-1.2.5 → modal-1.2.5.dev1}/modal/__main__.py +0 -0
  43. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_billing.py +0 -0
  44. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_clustered_functions.py +0 -0
  45. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_clustered_functions.pyi +0 -0
  46. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_grpc_client.py +0 -0
  47. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_ipython.py +0 -0
  48. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_load_context.py +0 -0
  49. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_location.py +0 -0
  50. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_object.py +0 -0
  51. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_output.py +0 -0
  52. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_pty.py +0 -0
  53. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_resolver.py +0 -0
  54. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_resources.py +0 -0
  55. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/__init__.py +0 -0
  56. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/asgi.py +0 -0
  57. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/container_io_manager.py +0 -0
  58. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/container_io_manager.pyi +0 -0
  59. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/execution_context.py +0 -0
  60. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/execution_context.pyi +0 -0
  61. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  62. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_runtime/telemetry.py +0 -0
  63. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_serialization.py +0 -0
  64. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_traceback.py +0 -0
  65. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_tunnel.py +0 -0
  66. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_tunnel.pyi +0 -0
  67. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_type_manager.py +0 -0
  68. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/__init__.py +0 -0
  69. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/app_utils.py +0 -0
  70. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/async_utils.py +0 -0
  71. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/auth_token_manager.py +0 -0
  72. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
  73. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/deprecation.py +0 -0
  74. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/docker_utils.py +0 -0
  75. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/git_utils.py +0 -0
  76. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/grpc_testing.py +0 -0
  77. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/hash_utils.py +0 -0
  78. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/http_utils.py +0 -0
  79. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/jwt_utils.py +0 -0
  80. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/logger.py +0 -0
  81. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/mount_utils.py +0 -0
  82. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/name_utils.py +0 -0
  83. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/package_utils.py +0 -0
  84. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/pattern_utils.py +0 -0
  85. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/rand_pb_testing.py +0 -0
  86. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/shell_utils.py +0 -0
  87. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_utils/time_utils.py +0 -0
  88. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_vendor/__init__.py +0 -0
  89. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  90. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_vendor/cloudpickle.py +0 -0
  91. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_vendor/tblib.py +0 -0
  92. {modal-1.2.5 → modal-1.2.5.dev1}/modal/_watcher.py +0 -0
  93. {modal-1.2.5 → modal-1.2.5.dev1}/modal/app.pyi +0 -0
  94. {modal-1.2.5 → modal-1.2.5.dev1}/modal/billing.py +0 -0
  95. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/2023.12.312.txt +0 -0
  96. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/2023.12.txt +0 -0
  97. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/2024.04.txt +0 -0
  98. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/2024.10.txt +0 -0
  99. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/2025.06.txt +0 -0
  100. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/PREVIEW.txt +0 -0
  101. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/README.md +0 -0
  102. {modal-1.2.5 → modal-1.2.5.dev1}/modal/builder/base-images.json +0 -0
  103. {modal-1.2.5 → modal-1.2.5.dev1}/modal/call_graph.py +0 -0
  104. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/__init__.py +0 -0
  105. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/_download.py +0 -0
  106. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/_traceback.py +0 -0
  107. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/app.py +0 -0
  108. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/cluster.py +0 -0
  109. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/config.py +0 -0
  110. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/container.py +0 -0
  111. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/dict.py +0 -0
  112. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/entry_point.py +0 -0
  113. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/environment.py +0 -0
  114. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/import_refs.py +0 -0
  115. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/launch.py +0 -0
  116. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/network_file_system.py +0 -0
  117. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/profile.py +0 -0
  118. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/programs/__init__.py +0 -0
  119. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/programs/launch_instance_ssh.py +0 -0
  120. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/programs/run_jupyter.py +0 -0
  121. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/programs/run_marimo.py +0 -0
  122. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/programs/vscode.py +0 -0
  123. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/queues.py +0 -0
  124. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/run.py +0 -0
  125. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/secret.py +0 -0
  126. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/token.py +0 -0
  127. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/utils.py +0 -0
  128. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cli/volume.py +0 -0
  129. {modal-1.2.5 → modal-1.2.5.dev1}/modal/client.py +0 -0
  130. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cloud_bucket_mount.py +0 -0
  131. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cloud_bucket_mount.pyi +0 -0
  132. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cls.py +0 -0
  133. {modal-1.2.5 → modal-1.2.5.dev1}/modal/cls.pyi +0 -0
  134. {modal-1.2.5 → modal-1.2.5.dev1}/modal/config.py +0 -0
  135. {modal-1.2.5 → modal-1.2.5.dev1}/modal/container_process.py +0 -0
  136. {modal-1.2.5 → modal-1.2.5.dev1}/modal/container_process.pyi +0 -0
  137. {modal-1.2.5 → modal-1.2.5.dev1}/modal/environments.py +0 -0
  138. {modal-1.2.5 → modal-1.2.5.dev1}/modal/environments.pyi +0 -0
  139. {modal-1.2.5 → modal-1.2.5.dev1}/modal/exception.py +0 -0
  140. {modal-1.2.5 → modal-1.2.5.dev1}/modal/experimental/ipython.py +0 -0
  141. {modal-1.2.5 → modal-1.2.5.dev1}/modal/file_io.py +0 -0
  142. {modal-1.2.5 → modal-1.2.5.dev1}/modal/file_io.pyi +0 -0
  143. {modal-1.2.5 → modal-1.2.5.dev1}/modal/file_pattern_matcher.py +0 -0
  144. {modal-1.2.5 → modal-1.2.5.dev1}/modal/functions.py +0 -0
  145. {modal-1.2.5 → modal-1.2.5.dev1}/modal/gpu.py +0 -0
  146. {modal-1.2.5 → modal-1.2.5.dev1}/modal/image.py +0 -0
  147. {modal-1.2.5 → modal-1.2.5.dev1}/modal/image.pyi +0 -0
  148. {modal-1.2.5 → modal-1.2.5.dev1}/modal/mount.pyi +0 -0
  149. {modal-1.2.5 → modal-1.2.5.dev1}/modal/network_file_system.py +0 -0
  150. {modal-1.2.5 → modal-1.2.5.dev1}/modal/network_file_system.pyi +0 -0
  151. {modal-1.2.5 → modal-1.2.5.dev1}/modal/object.py +0 -0
  152. {modal-1.2.5 → modal-1.2.5.dev1}/modal/object.pyi +0 -0
  153. {modal-1.2.5 → modal-1.2.5.dev1}/modal/output.py +0 -0
  154. {modal-1.2.5 → modal-1.2.5.dev1}/modal/parallel_map.py +0 -0
  155. {modal-1.2.5 → modal-1.2.5.dev1}/modal/parallel_map.pyi +0 -0
  156. {modal-1.2.5 → modal-1.2.5.dev1}/modal/partial_function.py +0 -0
  157. {modal-1.2.5 → modal-1.2.5.dev1}/modal/partial_function.pyi +0 -0
  158. {modal-1.2.5 → modal-1.2.5.dev1}/modal/proxy.py +0 -0
  159. {modal-1.2.5 → modal-1.2.5.dev1}/modal/proxy.pyi +0 -0
  160. {modal-1.2.5 → modal-1.2.5.dev1}/modal/py.typed +0 -0
  161. {modal-1.2.5 → modal-1.2.5.dev1}/modal/queue.py +0 -0
  162. {modal-1.2.5 → modal-1.2.5.dev1}/modal/queue.pyi +0 -0
  163. {modal-1.2.5 → modal-1.2.5.dev1}/modal/retries.py +0 -0
  164. {modal-1.2.5 → modal-1.2.5.dev1}/modal/runner.pyi +0 -0
  165. {modal-1.2.5 → modal-1.2.5.dev1}/modal/running_app.py +0 -0
  166. {modal-1.2.5 → modal-1.2.5.dev1}/modal/sandbox.py +0 -0
  167. {modal-1.2.5 → modal-1.2.5.dev1}/modal/sandbox.pyi +0 -0
  168. {modal-1.2.5 → modal-1.2.5.dev1}/modal/schedule.py +0 -0
  169. {modal-1.2.5 → modal-1.2.5.dev1}/modal/scheduler_placement.py +0 -0
  170. {modal-1.2.5 → modal-1.2.5.dev1}/modal/secret.py +0 -0
  171. {modal-1.2.5 → modal-1.2.5.dev1}/modal/secret.pyi +0 -0
  172. {modal-1.2.5 → modal-1.2.5.dev1}/modal/serving.py +0 -0
  173. {modal-1.2.5 → modal-1.2.5.dev1}/modal/serving.pyi +0 -0
  174. {modal-1.2.5 → modal-1.2.5.dev1}/modal/snapshot.py +0 -0
  175. {modal-1.2.5 → modal-1.2.5.dev1}/modal/snapshot.pyi +0 -0
  176. {modal-1.2.5 → modal-1.2.5.dev1}/modal/stream_type.py +0 -0
  177. {modal-1.2.5 → modal-1.2.5.dev1}/modal/token_flow.py +0 -0
  178. {modal-1.2.5 → modal-1.2.5.dev1}/modal/token_flow.pyi +0 -0
  179. {modal-1.2.5 → modal-1.2.5.dev1}/modal/volume.pyi +0 -0
  180. {modal-1.2.5 → modal-1.2.5.dev1}/modal.egg-info/dependency_links.txt +0 -0
  181. {modal-1.2.5 → modal-1.2.5.dev1}/modal.egg-info/entry_points.txt +0 -0
  182. {modal-1.2.5 → modal-1.2.5.dev1}/modal.egg-info/requires.txt +0 -0
  183. {modal-1.2.5 → modal-1.2.5.dev1}/modal.egg-info/top_level.txt +0 -0
  184. {modal-1.2.5 → modal-1.2.5.dev1}/modal_docs/__init__.py +0 -0
  185. {modal-1.2.5 → modal-1.2.5.dev1}/modal_docs/gen_cli_docs.py +0 -0
  186. {modal-1.2.5 → modal-1.2.5.dev1}/modal_docs/gen_reference_docs.py +0 -0
  187. {modal-1.2.5 → modal-1.2.5.dev1}/modal_docs/mdmd/__init__.py +0 -0
  188. {modal-1.2.5 → modal-1.2.5.dev1}/modal_docs/mdmd/mdmd.py +0 -0
  189. {modal-1.2.5 → modal-1.2.5.dev1}/modal_docs/mdmd/signatures.py +0 -0
  190. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/__init__.py +0 -0
  191. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/api_grpc.py +0 -0
  192. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/api_pb2_grpc.py +0 -0
  193. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/api_pb2_grpc.pyi +0 -0
  194. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/modal_api_grpc.py +0 -0
  195. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/py.typed +0 -0
  196. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/task_command_router.proto +0 -0
  197. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/task_command_router_grpc.py +0 -0
  198. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/task_command_router_pb2.py +0 -0
  199. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/task_command_router_pb2.pyi +0 -0
  200. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/task_command_router_pb2_grpc.py +0 -0
  201. {modal-1.2.5 → modal-1.2.5.dev1}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
  202. {modal-1.2.5 → modal-1.2.5.dev1}/modal_version/__main__.py +0 -0
  203. {modal-1.2.5 → modal-1.2.5.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.2.5
3
+ Version: 1.2.5.dev1
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -15,19 +15,27 @@ if telemetry_socket:
15
15
  instrument_imports(telemetry_socket)
16
16
 
17
17
  import asyncio
18
+ import inspect
18
19
  import queue
19
20
  import signal
21
+ import sys
20
22
  import threading
21
23
  import time
22
- import types
23
- from typing import TYPE_CHECKING, Any, Optional, cast
24
+ from collections.abc import Sequence
25
+ from typing import TYPE_CHECKING, Any, Callable, Optional
24
26
 
25
27
  from google.protobuf.message import Message
26
28
 
27
29
  from modal._clustered_functions import initialize_clustered_function
28
- from modal._runtime.user_code_event_loop import UserCodeEventLoop
30
+ from modal._partial_function import (
31
+ _find_callables_for_obj,
32
+ _PartialFunctionFlags,
33
+ )
29
34
  from modal._serialization import deserialize, deserialize_params
30
35
  from modal._utils.async_utils import TaskContext, aclosing, synchronizer
36
+ from modal._utils.function_utils import (
37
+ callable_has_non_self_params,
38
+ )
31
39
  from modal.app import App, _App
32
40
  from modal.client import Client, _Client
33
41
  from modal.config import logger
@@ -45,7 +53,6 @@ from ._runtime.container_io_manager import (
45
53
  if TYPE_CHECKING:
46
54
  import modal._object
47
55
  import modal._runtime.container_io_manager
48
- import modal._runtime.user_code_imports
49
56
 
50
57
 
51
58
  class DaemonizedThreadPool:
@@ -94,6 +101,81 @@ class DaemonizedThreadPool:
94
101
  self.inputs.put((func, args))
95
102
 
96
103
 
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
+
97
179
  def call_function(
98
180
  user_code_event_loop: UserCodeEventLoop,
99
181
  container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
@@ -249,25 +331,23 @@ def call_function(
249
331
  signal.signal(signal.SIGUSR1, usr1_handler) # reset signal handler
250
332
 
251
333
 
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
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)
271
351
 
272
352
 
273
353
  def main(container_args: api_pb2.ContainerArguments, client: Client):
@@ -277,20 +357,34 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
277
357
  active_app: _App
278
358
  service: Service
279
359
  function_def = container_args.function_def
360
+ is_auto_snapshot: bool = function_def.is_auto_snapshot
280
361
  # The worker sets this flag to "1" for snapshot and restore tasks. Otherwise, this flag is unset,
281
362
  # in which case snapshots should be disabled.
282
363
  is_snapshotting_function = (
283
364
  function_def.is_checkpointing_function and os.environ.get("MODAL_ENABLE_SNAP_RESTORE") == "1"
284
365
  )
285
366
 
286
- _client: _Client = cast(_Client, synchronizer._translate_in(client)) # TODO(erikbern): ugly
367
+ _client: _Client = synchronizer._translate_in(client) # TODO(erikbern): ugly
287
368
 
288
369
  # Call ContainerHello - currently a noop but might be used later for things
289
370
  container_io_manager.hello()
290
371
 
291
372
  with container_io_manager.heartbeats(is_snapshotting_function), UserCodeEventLoop() as event_loop:
292
373
  # If this is a serialized function, fetch the definition from the server
293
- ser_usr_cls, ser_fun = get_serialized_user_class_and_function(function_def, _client)
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
294
388
 
295
389
  # Initialize the function, importing user code.
296
390
  with container_io_manager.handle_user_exception():
@@ -342,7 +436,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
342
436
 
343
437
  # Initialize objects on the app.
344
438
  # This is basically only functions and classes - anything else is deprecated and will be unsupported soon
345
- app: App = cast(App, synchronizer._translate_out(active_app))
439
+ app: App = synchronizer._translate_out(active_app)
346
440
  app._init_container(client, container_app)
347
441
 
348
442
  # Hydrate all function dependencies.
@@ -362,7 +456,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
362
456
  "that evaluates differently in the local and remote environments."
363
457
  )
364
458
  for object_id, obj in zip(dep_object_ids, service.service_deps):
365
- metadata: Optional[Message] = container_app.object_handle_metadata[object_id]
459
+ metadata: Message = container_app.object_handle_metadata[object_id]
366
460
  obj._hydrate(object_id, _client, metadata)
367
461
 
368
462
  # Initialize clustered functions.
@@ -373,8 +467,91 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
373
467
  function_def._experimental_group_size,
374
468
  )
375
469
 
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)
470
+ # Identify all "enter" methods that need to run before we snapshot.
471
+ if service.user_cls_instance is not None and not is_auto_snapshot:
472
+ pre_snapshot_methods = _find_callables_for_obj(
473
+ service.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
474
+ )
475
+ call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
476
+
477
+ # If this container is being used to create a checkpoint, checkpoint the container after
478
+ # global imports and initialization. Checkpointed containers run from this point onwards.
479
+ if is_snapshotting_function:
480
+ container_io_manager.memory_snapshot()
481
+
482
+ # Install hooks for interactive functions.
483
+ def breakpoint_wrapper():
484
+ # note: it would be nice to not have breakpoint_wrapper() included in the backtrace
485
+ container_io_manager.interact(from_breakpoint=True)
486
+ import pdb
487
+
488
+ frame = inspect.currentframe().f_back
489
+
490
+ pdb.Pdb().set_trace(frame)
491
+
492
+ sys.breakpointhook = breakpoint_wrapper
493
+
494
+ # Identify the "enter" methods to run after resuming from a snapshot.
495
+ if service.user_cls_instance is not None and not is_auto_snapshot:
496
+ post_snapshot_methods = _find_callables_for_obj(
497
+ service.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
498
+ )
499
+ call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
500
+
501
+ with container_io_manager.handle_user_exception():
502
+ finalized_functions = service.get_finalized_functions(function_def, container_io_manager)
503
+ # Execute the function.
504
+ lifespan_background_tasks = []
505
+ try:
506
+ for finalized_function in finalized_functions.values():
507
+ if finalized_function.lifespan_manager:
508
+ lifespan_background_tasks.append(
509
+ event_loop.create_task(finalized_function.lifespan_manager.background_task())
510
+ )
511
+ with container_io_manager.handle_user_exception():
512
+ event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
513
+ call_function(
514
+ event_loop,
515
+ container_io_manager,
516
+ finalized_functions,
517
+ batch_max_size,
518
+ batch_wait_ms,
519
+ )
520
+ finally:
521
+ # Run exit handlers. From this point onward, ignore all SIGINT signals that come from
522
+ # graceful shutdowns originating on the worker, as well as stray SIGUSR1 signals that
523
+ # may have been sent to cancel inputs.
524
+ int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
525
+ usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
526
+
527
+ try:
528
+ try:
529
+ # run lifespan shutdown for asgi apps
530
+ for finalized_function in finalized_functions.values():
531
+ if finalized_function.lifespan_manager:
532
+ with container_io_manager.handle_user_exception():
533
+ event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
534
+ finally:
535
+ # no need to keep the lifespan asgi call around - we send it no more messages
536
+ for lifespan_background_task in lifespan_background_tasks:
537
+ lifespan_background_task.cancel() # prevent dangling tasks
538
+
539
+ # Identify "exit" methods and run them.
540
+ # want to make sure this is called even if the lifespan manager fails
541
+ if service.user_cls_instance is not None and not is_auto_snapshot:
542
+ exit_methods = _find_callables_for_obj(service.user_cls_instance, _PartialFunctionFlags.EXIT)
543
+ call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
544
+
545
+ # Finally, commit on exit to catch uncommitted volume changes and surface background
546
+ # commit errors.
547
+ container_io_manager.volume_commit(
548
+ [v.volume_id for v in function_def.volume_mounts if v.allow_background_commits]
549
+ )
550
+ finally:
551
+ # Restore the original signal handler, needed for container_test hygiene since the
552
+ # test runs `main()` multiple times in the same process.
553
+ signal.signal(signal.SIGINT, int_handler)
554
+ signal.signal(signal.SIGUSR1, usr1_handler)
378
555
 
379
556
 
380
557
  if __name__ == "__main__":
@@ -701,7 +701,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
701
701
  _experimental_proxy_ip: Optional[str] = None,
702
702
  _experimental_custom_scaling_factor: Optional[float] = None,
703
703
  restrict_output: bool = False,
704
- http_config: Optional[api_pb2.HTTPConfig] = None,
705
704
  ) -> "_Function":
706
705
  """mdmd:hidden
707
706
 
@@ -988,7 +987,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
988
987
  function_definition = api_pb2.Function(
989
988
  module_name=info.module_name or "",
990
989
  function_name=info.function_name,
991
- implementation_name=info.implementation_name,
992
990
  mount_ids=loaded_mount_ids,
993
991
  secret_ids=[secret.object_id for secret in secrets],
994
992
  image_id=(image.object_id if image else ""),
@@ -1048,14 +1046,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1048
1046
  function_schema=function_schema,
1049
1047
  supported_input_formats=supported_input_formats,
1050
1048
  supported_output_formats=supported_output_formats,
1051
- http_config=http_config,
1052
1049
  )
1053
1050
 
1054
1051
  if isinstance(gpu, list):
1055
1052
  function_data = api_pb2.FunctionData(
1056
1053
  module_name=function_definition.module_name,
1057
1054
  function_name=function_definition.function_name,
1058
- implementation_name=function_definition.implementation_name,
1059
1055
  function_type=function_definition.function_type,
1060
1056
  warm_pool_size=function_definition.warm_pool_size,
1061
1057
  concurrency_limit=function_definition.concurrency_limit,
@@ -1087,7 +1083,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1087
1083
  untrusted=function_definition.untrusted,
1088
1084
  supported_input_formats=supported_input_formats,
1089
1085
  supported_output_formats=supported_output_formats,
1090
- http_config=http_config,
1091
1086
  )
1092
1087
 
1093
1088
  ranked_functions = []
@@ -46,7 +46,6 @@ class _PartialFunctionFlags(enum.IntFlag):
46
46
  BATCHED = 64
47
47
  CONCURRENT = 128
48
48
  CLUSTERED = 256 # Experimental: Clustered functions
49
- HTTP_WEB_INTERFACE = 512 # Experimental: HTTP server
50
49
 
51
50
  @staticmethod
52
51
  def all() -> int:
@@ -77,7 +76,6 @@ class _PartialFunctionParams:
77
76
  target_concurrent_inputs: Optional[int] = None
78
77
  build_timeout: Optional[int] = None
79
78
  rdma: Optional[bool] = None
80
- http_config: Optional[api_pb2.HTTPConfig] = None
81
79
 
82
80
  def update(self, other: "_PartialFunctionParams") -> None:
83
81
  """Update self with params set in other."""