modal 1.1.4.dev15__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.
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/PKG-INFO +7 -6
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/README.md +4 -4
- modal-1.2.5.dev4/modal/_billing.py +80 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_clustered_functions.py +1 -3
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_container_entrypoint.py +53 -249
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_functions.py +218 -172
- modal-1.2.5.dev4/modal/_grpc_client.py +171 -0
- modal-1.2.5.dev4/modal/_load_context.py +106 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_object.py +54 -19
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_output.py +44 -20
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_partial_function.py +29 -4
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_pty.py +7 -3
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_resolver.py +21 -35
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/asgi.py +1 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/container_io_manager.py +282 -173
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/container_io_manager.pyi +32 -48
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/gpu_memory_snapshot.py +20 -17
- modal-1.2.5.dev4/modal/_runtime/user_code_event_loop.py +80 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/user_code_imports.py +237 -67
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_serialization.py +57 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_tunnel.py +11 -2
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_tunnel.pyi +25 -3
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/async_utils.py +27 -8
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/auth_token_manager.py +1 -4
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/blob_utils.py +11 -8
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/function_utils.py +32 -10
- modal-1.2.5.dev4/modal/_utils/grpc_utils.py +455 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/mount_utils.py +26 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/name_utils.py +17 -3
- modal-1.2.5.dev4/modal/_utils/task_command_router_client.py +536 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/app.py +253 -92
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/app.pyi +227 -46
- modal-1.2.5.dev4/modal/billing.py +5 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/_download.py +19 -3
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/cluster.py +11 -3
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/config.py +3 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/container.py +5 -4
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/dict.py +5 -2
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/entry_point.py +3 -2
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/environment.py +5 -4
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/launch.py +1 -2
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/network_file_system.py +1 -4
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/programs/run_jupyter.py +1 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/programs/run_marimo.py +1 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/programs/vscode.py +1 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/queues.py +5 -4
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/run.py +10 -187
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/secret.py +1 -2
- modal-1.2.5.dev4/modal/cli/shell.py +237 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/volume.py +7 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/client.py +14 -124
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/client.pyi +8 -98
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cloud_bucket_mount.py +5 -3
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cloud_bucket_mount.pyi +5 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cls.py +123 -103
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cls.pyi +37 -85
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/config.py +27 -3
- modal-1.2.5.dev4/modal/container_process.py +472 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/container_process.pyi +95 -32
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/dict.py +40 -59
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/dict.pyi +24 -83
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/environments.py +15 -27
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/environments.pyi +5 -15
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/exception.py +4 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/experimental/__init__.py +30 -42
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/experimental/flash.py +230 -78
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/experimental/flash.pyi +122 -9
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/file_io.py +13 -27
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/functions.pyi +37 -63
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/image.py +208 -70
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/image.pyi +239 -33
- modal-1.2.5.dev4/modal/io_streams.py +846 -0
- modal-1.2.5.dev4/modal/io_streams.pyi +530 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/mount.py +35 -143
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/mount.pyi +25 -169
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/network_file_system.py +22 -51
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/network_file_system.pyi +16 -76
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/object.pyi +35 -8
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/parallel_map.py +59 -52
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/parallel_map.pyi +4 -9
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/partial_function.pyi +4 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/proxy.py +14 -6
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/proxy.pyi +10 -2
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/queue.py +30 -61
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/queue.pyi +12 -76
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/runner.py +112 -92
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/runner.pyi +45 -27
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/sandbox.py +337 -117
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/sandbox.pyi +368 -41
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/secret.py +61 -73
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/secret.pyi +21 -42
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/serving.py +7 -11
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/serving.pyi +7 -8
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/snapshot.py +11 -8
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/token_flow.py +1 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/volume.py +68 -73
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/volume.pyi +32 -77
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal.egg-info/PKG-INFO +7 -6
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal.egg-info/SOURCES.txt +19 -7
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal.egg-info/requires.txt +1 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/api.proto +249 -53
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/api_grpc.py +130 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/api_pb2.py +1265 -1040
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/api_pb2.pyi +736 -51
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/api_pb2_grpc.py +266 -2
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/api_pb2_grpc.pyi +86 -6
- modal-1.2.5.dev4/modal_proto/modal_api_grpc.py +194 -0
- modal-1.2.5.dev4/modal_proto/sandbox_router.proto +145 -0
- modal-1.2.5.dev4/modal_proto/sandbox_router_grpc.py +105 -0
- modal-1.2.5.dev4/modal_proto/sandbox_router_pb2.py +149 -0
- modal-1.2.5.dev4/modal_proto/sandbox_router_pb2.pyi +333 -0
- modal-1.2.5.dev4/modal_proto/sandbox_router_pb2_grpc.py +203 -0
- modal-1.2.5.dev4/modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
- modal-1.2.5.dev4/modal_proto/task_command_router.proto +144 -0
- modal-1.2.5.dev4/modal_proto/task_command_router_grpc.py +105 -0
- modal-1.2.5.dev4/modal_proto/task_command_router_pb2.py +149 -0
- modal-1.2.5.dev4/modal_proto/task_command_router_pb2.pyi +333 -0
- modal-1.2.5.dev4/modal_proto/task_command_router_pb2_grpc.py +203 -0
- modal-1.2.5.dev4/modal_proto/task_command_router_pb2_grpc.pyi +75 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_version/__init__.py +1 -1
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/pyproject.toml +19 -3
- modal-1.1.4.dev15/modal/_utils/grpc_utils.py +0 -293
- modal-1.1.4.dev15/modal/container_process.py +0 -194
- modal-1.1.4.dev15/modal/io_streams.py +0 -467
- modal-1.1.4.dev15/modal/io_streams.pyi +0 -440
- modal-1.1.4.dev15/modal_proto/modal_api_grpc.py +0 -186
- modal-1.1.4.dev15/modal_proto/modal_options_grpc.py +0 -3
- modal-1.1.4.dev15/modal_proto/options.proto +0 -19
- modal-1.1.4.dev15/modal_proto/options_grpc.py +0 -3
- modal-1.1.4.dev15/modal_proto/options_pb2.py +0 -35
- modal-1.1.4.dev15/modal_proto/options_pb2.pyi +0 -20
- modal-1.1.4.dev15/modal_proto/options_pb2_grpc.py +0 -4
- modal-1.1.4.dev15/modal_proto/options_pb2_grpc.pyi +0 -7
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/LICENSE +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/__main__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_ipython.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_location.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_resources.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_traceback.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_type_manager.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/logger.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/_watcher.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/README.md +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/builder/base-images.json +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/call_graph.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/_traceback.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/app.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/import_refs.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/profile.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/token.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/cli/utils.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/experimental/ipython.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/file_io.pyi +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/functions.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/gpu.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/object.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/output.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/partial_function.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/py.typed +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/retries.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/running_app.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/schedule.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/scheduler_placement.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/snapshot.pyi +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/stream_type.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal/token_flow.pyi +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_docs/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/__init__.py +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_proto/py.typed +0 -0
- {modal-1.1.4.dev15 → modal-1.2.5.dev4}/modal_version/__main__.py +0 -0
- {modal-1.1.4.dev15 → 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.
|
|
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,10 +13,11 @@ 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:
|
|
16
|
+
Requires-Python: <3.14,>=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: aiohttp
|
|
20
|
+
Requires-Dist: cbor2
|
|
20
21
|
Requires-Dist: certifi
|
|
21
22
|
Requires-Dist: click~=8.1
|
|
22
23
|
Requires-Dist: grpclib<0.4.9,>=0.4.7
|
|
@@ -31,14 +32,14 @@ Requires-Dist: watchfiles
|
|
|
31
32
|
Requires-Dist: typing_extensions~=4.6
|
|
32
33
|
Dynamic: license-file
|
|
33
34
|
|
|
34
|
-
# Modal Python
|
|
35
|
+
# Modal Python SDK
|
|
35
36
|
|
|
36
37
|
[](https://pypi.org/project/modal/)
|
|
37
38
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
38
39
|
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
39
40
|
[](https://modal.com/slack)
|
|
40
41
|
|
|
41
|
-
The [Modal](https://modal.com/) Python
|
|
42
|
+
The [Modal](https://modal.com/) Python SDK provides convenient, on-demand
|
|
42
43
|
access to serverless cloud compute from Python scripts on your local computer.
|
|
43
44
|
|
|
44
45
|
## Documentation
|
|
@@ -52,10 +53,10 @@ a [user guide](https://modal.com/docs/guide), and the detailed
|
|
|
52
53
|
|
|
53
54
|
**This library requires Python 3.9 – 3.13.**
|
|
54
55
|
|
|
55
|
-
Install the package with `pip`:
|
|
56
|
+
Install the package with `uv` or `pip`:
|
|
56
57
|
|
|
57
58
|
```bash
|
|
58
|
-
pip install modal
|
|
59
|
+
uv pip install modal
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
You can create a Modal account (or link your existing one) directly on the
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Modal Python
|
|
1
|
+
# Modal Python SDK
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/modal/)
|
|
4
4
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
5
5
|
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
6
6
|
[](https://modal.com/slack)
|
|
7
7
|
|
|
8
|
-
The [Modal](https://modal.com/) Python
|
|
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
|
|
@@ -19,10 +19,10 @@ a [user guide](https://modal.com/docs/guide), and the detailed
|
|
|
19
19
|
|
|
20
20
|
**This library requires Python 3.9 – 3.13.**
|
|
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
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
for dt in (start, end):
|
|
56
|
+
if dt.tzinfo is None:
|
|
57
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
58
|
+
elif dt.tzinfo != timezone.utc:
|
|
59
|
+
raise InvalidError("Timezone-aware start/end limits must be in UTC.")
|
|
60
|
+
|
|
61
|
+
request = api_pb2.WorkspaceBillingReportRequest(
|
|
62
|
+
resolution=resolution,
|
|
63
|
+
tag_names=tag_names,
|
|
64
|
+
)
|
|
65
|
+
request.start_timestamp.FromDatetime(start)
|
|
66
|
+
request.end_timestamp.FromDatetime(end)
|
|
67
|
+
|
|
68
|
+
rows = []
|
|
69
|
+
async for pb_item in client.stub.WorkspaceBillingReport.unary_stream(request):
|
|
70
|
+
item = {
|
|
71
|
+
"object_id": pb_item.object_id,
|
|
72
|
+
"description": pb_item.description,
|
|
73
|
+
"environment_name": pb_item.environment_name,
|
|
74
|
+
"interval_start": pb_item.interval.ToDatetime().replace(tzinfo=timezone.utc),
|
|
75
|
+
"cost": Decimal(pb_item.cost),
|
|
76
|
+
"tags": dict(pb_item.tags),
|
|
77
|
+
}
|
|
78
|
+
rows.append(item)
|
|
79
|
+
|
|
80
|
+
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
|
|
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,31 +15,23 @@ 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
|
-
|
|
25
|
-
from typing import TYPE_CHECKING, Any,
|
|
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.
|
|
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
|
-
from modal._utils.async_utils import TaskContext, synchronizer
|
|
36
|
-
from modal._utils.function_utils import (
|
|
37
|
-
callable_has_non_self_params,
|
|
38
|
-
)
|
|
30
|
+
from modal._utils.async_utils import TaskContext, aclosing, synchronizer
|
|
39
31
|
from modal.app import App, _App
|
|
40
32
|
from modal.client import Client, _Client
|
|
41
33
|
from modal.config import logger
|
|
42
|
-
from modal.exception import ExecutionError, InputCancellation
|
|
34
|
+
from modal.exception import ExecutionError, InputCancellation
|
|
43
35
|
from modal.running_app import RunningApp, running_app_from_layout
|
|
44
36
|
from modal_proto import api_pb2
|
|
45
37
|
|
|
@@ -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",
|
|
@@ -184,17 +102,13 @@ def call_function(
|
|
|
184
102
|
batch_wait_ms: int,
|
|
185
103
|
):
|
|
186
104
|
async def run_input_async(io_context: IOContext) -> None:
|
|
187
|
-
started_at = time.time()
|
|
188
105
|
reset_context = execution_context._set_current_context_ids(
|
|
189
106
|
io_context.input_ids, io_context.function_call_ids, io_context.attempt_tokens
|
|
190
107
|
)
|
|
108
|
+
started_at = time.time()
|
|
191
109
|
async with container_io_manager.handle_input_exception.aio(io_context, started_at):
|
|
192
|
-
res = io_context.call_finalized_function()
|
|
193
110
|
# TODO(erikbern): any exception below shouldn't be considered a user exception
|
|
194
111
|
if io_context.finalized_function.is_generator:
|
|
195
|
-
if not inspect.isasyncgen(res):
|
|
196
|
-
raise InvalidError(f"Async generator function returned value of type {type(res)}")
|
|
197
|
-
|
|
198
112
|
# Send up to this many outputs at a time.
|
|
199
113
|
current_function_call_id = execution_context.current_function_call_id()
|
|
200
114
|
assert current_function_call_id is not None # Set above.
|
|
@@ -204,33 +118,24 @@ def call_function(
|
|
|
204
118
|
async with container_io_manager.generator_output_sender(
|
|
205
119
|
current_function_call_id,
|
|
206
120
|
current_attempt_token,
|
|
207
|
-
io_context.
|
|
121
|
+
io_context._generator_output_format(),
|
|
208
122
|
generator_queue,
|
|
209
123
|
):
|
|
210
124
|
item_count = 0
|
|
211
|
-
async
|
|
212
|
-
|
|
213
|
-
|
|
125
|
+
async with aclosing(io_context.call_generator_async()) as gen:
|
|
126
|
+
async for value in gen:
|
|
127
|
+
await container_io_manager._queue_put.aio(generator_queue, value)
|
|
128
|
+
item_count += 1
|
|
214
129
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
io_context,
|
|
218
|
-
started_at,
|
|
219
|
-
message,
|
|
220
|
-
api_pb2.DATA_FORMAT_GENERATOR_DONE,
|
|
130
|
+
await container_io_manager._send_outputs.aio(
|
|
131
|
+
started_at, io_context.output_items_generator_done(started_at, item_count)
|
|
221
132
|
)
|
|
222
133
|
else:
|
|
223
|
-
|
|
224
|
-
raise InvalidError(
|
|
225
|
-
f"Async (non-generator) function returned value of type {type(res)}"
|
|
226
|
-
" You might need to use @app.function(..., is_generator=True)."
|
|
227
|
-
)
|
|
228
|
-
value = await res
|
|
134
|
+
value = await io_context.call_function_async()
|
|
229
135
|
await container_io_manager.push_outputs.aio(
|
|
230
136
|
io_context,
|
|
231
137
|
started_at,
|
|
232
138
|
value,
|
|
233
|
-
io_context.finalized_function.data_format,
|
|
234
139
|
)
|
|
235
140
|
reset_context()
|
|
236
141
|
|
|
@@ -240,13 +145,9 @@ def call_function(
|
|
|
240
145
|
io_context.input_ids, io_context.function_call_ids, io_context.attempt_tokens
|
|
241
146
|
)
|
|
242
147
|
with container_io_manager.handle_input_exception(io_context, started_at):
|
|
243
|
-
res = io_context.call_finalized_function()
|
|
244
|
-
|
|
245
148
|
# TODO(erikbern): any exception below shouldn't be considered a user exception
|
|
246
149
|
if io_context.finalized_function.is_generator:
|
|
247
|
-
|
|
248
|
-
raise InvalidError(f"Generator function returned value of type {type(res)}")
|
|
249
|
-
|
|
150
|
+
gen = io_context.call_generator_sync()
|
|
250
151
|
# Send up to this many outputs at a time.
|
|
251
152
|
current_function_call_id = execution_context.current_function_call_id()
|
|
252
153
|
assert current_function_call_id is not None # Set above.
|
|
@@ -256,25 +157,20 @@ def call_function(
|
|
|
256
157
|
with container_io_manager.generator_output_sender(
|
|
257
158
|
current_function_call_id,
|
|
258
159
|
current_attempt_token,
|
|
259
|
-
io_context.
|
|
160
|
+
io_context._generator_output_format(),
|
|
260
161
|
generator_queue,
|
|
261
162
|
):
|
|
262
163
|
item_count = 0
|
|
263
|
-
for value in
|
|
164
|
+
for value in gen:
|
|
264
165
|
container_io_manager._queue_put(generator_queue, value)
|
|
265
166
|
item_count += 1
|
|
266
167
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
else:
|
|
270
|
-
if inspect.iscoroutine(res) or inspect.isgenerator(res) or inspect.isasyncgen(res):
|
|
271
|
-
raise InvalidError(
|
|
272
|
-
f"Sync (non-generator) function return value of type {type(res)}."
|
|
273
|
-
" You might need to use @app.function(..., is_generator=True)."
|
|
274
|
-
)
|
|
275
|
-
container_io_manager.push_outputs(
|
|
276
|
-
io_context, started_at, res, io_context.finalized_function.data_format
|
|
168
|
+
container_io_manager._send_outputs(
|
|
169
|
+
started_at, io_context.output_items_generator_done(started_at, item_count)
|
|
277
170
|
)
|
|
171
|
+
else:
|
|
172
|
+
values = io_context.call_function_sync()
|
|
173
|
+
container_io_manager.push_outputs(io_context, started_at, values)
|
|
278
174
|
reset_context()
|
|
279
175
|
|
|
280
176
|
if container_io_manager.input_concurrency_enabled:
|
|
@@ -353,23 +249,25 @@ def call_function(
|
|
|
353
249
|
signal.signal(signal.SIGUSR1, usr1_handler) # reset signal handler
|
|
354
250
|
|
|
355
251
|
|
|
356
|
-
def
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
373
271
|
|
|
374
272
|
|
|
375
273
|
def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
@@ -379,34 +277,20 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
379
277
|
active_app: _App
|
|
380
278
|
service: Service
|
|
381
279
|
function_def = container_args.function_def
|
|
382
|
-
is_auto_snapshot: bool = function_def.is_auto_snapshot
|
|
383
280
|
# The worker sets this flag to "1" for snapshot and restore tasks. Otherwise, this flag is unset,
|
|
384
281
|
# in which case snapshots should be disabled.
|
|
385
282
|
is_snapshotting_function = (
|
|
386
283
|
function_def.is_checkpointing_function and os.environ.get("MODAL_ENABLE_SNAP_RESTORE") == "1"
|
|
387
284
|
)
|
|
388
285
|
|
|
389
|
-
_client: _Client = synchronizer._translate_in(client) # TODO(erikbern): ugly
|
|
286
|
+
_client: _Client = cast(_Client, synchronizer._translate_in(client)) # TODO(erikbern): ugly
|
|
390
287
|
|
|
391
288
|
# Call ContainerHello - currently a noop but might be used later for things
|
|
392
289
|
container_io_manager.hello()
|
|
393
290
|
|
|
394
291
|
with container_io_manager.heartbeats(is_snapshotting_function), UserCodeEventLoop() as event_loop:
|
|
395
292
|
# If this is a serialized function, fetch the definition from the server
|
|
396
|
-
|
|
397
|
-
assert function_def.function_serialized or function_def.class_serialized
|
|
398
|
-
|
|
399
|
-
if function_def.function_serialized:
|
|
400
|
-
ser_fun = deserialize(function_def.function_serialized, _client)
|
|
401
|
-
else:
|
|
402
|
-
ser_fun = None
|
|
403
|
-
|
|
404
|
-
if function_def.class_serialized:
|
|
405
|
-
ser_usr_cls = deserialize(function_def.class_serialized, _client)
|
|
406
|
-
else:
|
|
407
|
-
ser_usr_cls = None
|
|
408
|
-
else:
|
|
409
|
-
ser_usr_cls, ser_fun = None, None
|
|
293
|
+
ser_usr_cls, ser_fun = get_serialized_user_class_and_function(function_def, _client)
|
|
410
294
|
|
|
411
295
|
# Initialize the function, importing user code.
|
|
412
296
|
with container_io_manager.handle_user_exception():
|
|
@@ -437,9 +321,9 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
437
321
|
param_kwargs,
|
|
438
322
|
)
|
|
439
323
|
else:
|
|
324
|
+
assert ser_usr_cls is None
|
|
440
325
|
service = import_single_function_service(
|
|
441
326
|
function_def,
|
|
442
|
-
ser_usr_cls,
|
|
443
327
|
ser_fun,
|
|
444
328
|
)
|
|
445
329
|
|
|
@@ -458,7 +342,7 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
458
342
|
|
|
459
343
|
# Initialize objects on the app.
|
|
460
344
|
# This is basically only functions and classes - anything else is deprecated and will be unsupported soon
|
|
461
|
-
app: App = synchronizer._translate_out(active_app)
|
|
345
|
+
app: App = cast(App, synchronizer._translate_out(active_app))
|
|
462
346
|
app._init_container(client, container_app)
|
|
463
347
|
|
|
464
348
|
# Hydrate all function dependencies.
|
|
@@ -472,10 +356,13 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
472
356
|
f"Function has {len(service.service_deps)} dependencies"
|
|
473
357
|
f" but container got {len(dep_object_ids)} object ids.\n"
|
|
474
358
|
f"Code deps: {service.service_deps}\n"
|
|
475
|
-
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."
|
|
476
363
|
)
|
|
477
364
|
for object_id, obj in zip(dep_object_ids, service.service_deps):
|
|
478
|
-
metadata: Message = container_app.object_handle_metadata[object_id]
|
|
365
|
+
metadata: Optional[Message] = container_app.object_handle_metadata[object_id]
|
|
479
366
|
obj._hydrate(object_id, _client, metadata)
|
|
480
367
|
|
|
481
368
|
# Initialize clustered functions.
|
|
@@ -486,91 +373,8 @@ def main(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
486
373
|
function_def._experimental_group_size,
|
|
487
374
|
)
|
|
488
375
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
pre_snapshot_methods = _find_callables_for_obj(
|
|
492
|
-
service.user_cls_instance, _PartialFunctionFlags.ENTER_PRE_SNAPSHOT
|
|
493
|
-
)
|
|
494
|
-
call_lifecycle_functions(event_loop, container_io_manager, list(pre_snapshot_methods.values()))
|
|
495
|
-
|
|
496
|
-
# If this container is being used to create a checkpoint, checkpoint the container after
|
|
497
|
-
# global imports and initialization. Checkpointed containers run from this point onwards.
|
|
498
|
-
if is_snapshotting_function:
|
|
499
|
-
container_io_manager.memory_snapshot()
|
|
500
|
-
|
|
501
|
-
# Install hooks for interactive functions.
|
|
502
|
-
def breakpoint_wrapper():
|
|
503
|
-
# note: it would be nice to not have breakpoint_wrapper() included in the backtrace
|
|
504
|
-
container_io_manager.interact(from_breakpoint=True)
|
|
505
|
-
import pdb
|
|
506
|
-
|
|
507
|
-
frame = inspect.currentframe().f_back
|
|
508
|
-
|
|
509
|
-
pdb.Pdb().set_trace(frame)
|
|
510
|
-
|
|
511
|
-
sys.breakpointhook = breakpoint_wrapper
|
|
512
|
-
|
|
513
|
-
# Identify the "enter" methods to run after resuming from a snapshot.
|
|
514
|
-
if service.user_cls_instance is not None and not is_auto_snapshot:
|
|
515
|
-
post_snapshot_methods = _find_callables_for_obj(
|
|
516
|
-
service.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
|
|
517
|
-
)
|
|
518
|
-
call_lifecycle_functions(event_loop, container_io_manager, list(post_snapshot_methods.values()))
|
|
519
|
-
|
|
520
|
-
with container_io_manager.handle_user_exception():
|
|
521
|
-
finalized_functions = service.get_finalized_functions(function_def, container_io_manager)
|
|
522
|
-
# Execute the function.
|
|
523
|
-
lifespan_background_tasks = []
|
|
524
|
-
try:
|
|
525
|
-
for finalized_function in finalized_functions.values():
|
|
526
|
-
if finalized_function.lifespan_manager:
|
|
527
|
-
lifespan_background_tasks.append(
|
|
528
|
-
event_loop.create_task(finalized_function.lifespan_manager.background_task())
|
|
529
|
-
)
|
|
530
|
-
with container_io_manager.handle_user_exception():
|
|
531
|
-
event_loop.run(finalized_function.lifespan_manager.lifespan_startup())
|
|
532
|
-
call_function(
|
|
533
|
-
event_loop,
|
|
534
|
-
container_io_manager,
|
|
535
|
-
finalized_functions,
|
|
536
|
-
batch_max_size,
|
|
537
|
-
batch_wait_ms,
|
|
538
|
-
)
|
|
539
|
-
finally:
|
|
540
|
-
# Run exit handlers. From this point onward, ignore all SIGINT signals that come from
|
|
541
|
-
# graceful shutdowns originating on the worker, as well as stray SIGUSR1 signals that
|
|
542
|
-
# may have been sent to cancel inputs.
|
|
543
|
-
int_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
544
|
-
usr1_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
|
|
545
|
-
|
|
546
|
-
try:
|
|
547
|
-
try:
|
|
548
|
-
# run lifespan shutdown for asgi apps
|
|
549
|
-
for finalized_function in finalized_functions.values():
|
|
550
|
-
if finalized_function.lifespan_manager:
|
|
551
|
-
with container_io_manager.handle_user_exception():
|
|
552
|
-
event_loop.run(finalized_function.lifespan_manager.lifespan_shutdown())
|
|
553
|
-
finally:
|
|
554
|
-
# no need to keep the lifespan asgi call around - we send it no more messages
|
|
555
|
-
for lifespan_background_task in lifespan_background_tasks:
|
|
556
|
-
lifespan_background_task.cancel() # prevent dangling tasks
|
|
557
|
-
|
|
558
|
-
# Identify "exit" methods and run them.
|
|
559
|
-
# want to make sure this is called even if the lifespan manager fails
|
|
560
|
-
if service.user_cls_instance is not None and not is_auto_snapshot:
|
|
561
|
-
exit_methods = _find_callables_for_obj(service.user_cls_instance, _PartialFunctionFlags.EXIT)
|
|
562
|
-
call_lifecycle_functions(event_loop, container_io_manager, list(exit_methods.values()))
|
|
563
|
-
|
|
564
|
-
# Finally, commit on exit to catch uncommitted volume changes and surface background
|
|
565
|
-
# commit errors.
|
|
566
|
-
container_io_manager.volume_commit(
|
|
567
|
-
[v.volume_id for v in function_def.volume_mounts if v.allow_background_commits]
|
|
568
|
-
)
|
|
569
|
-
finally:
|
|
570
|
-
# Restore the original signal handler, needed for container_test hygiene since the
|
|
571
|
-
# test runs `main()` multiple times in the same process.
|
|
572
|
-
signal.signal(signal.SIGINT, int_handler)
|
|
573
|
-
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)
|
|
574
378
|
|
|
575
379
|
|
|
576
380
|
if __name__ == "__main__":
|