modal 1.1.5.dev52__tar.gz → 1.3.1.dev24__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.
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/PKG-INFO +10 -9
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/README.md +5 -5
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/__init__.py +4 -4
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/__main__.py +4 -29
- modal-1.3.1.dev24/modal/_billing.py +84 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_clustered_functions.py +1 -3
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_container_entrypoint.py +33 -208
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_functions.py +173 -173
- modal-1.3.1.dev24/modal/_grpc_client.py +191 -0
- modal-1.3.1.dev24/modal/_ipython.py +21 -0
- modal-1.3.1.dev24/modal/_load_context.py +106 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_object.py +72 -21
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_output.py +28 -20
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_partial_function.py +31 -25
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_resolver.py +44 -57
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/container_io_manager.py +34 -29
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/container_io_manager.pyi +42 -44
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/gpu_memory_snapshot.py +9 -7
- modal-1.3.1.dev24/modal/_runtime/user_code_event_loop.py +80 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/user_code_imports.py +236 -10
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_serialization.py +2 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_traceback.py +4 -13
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_tunnel.py +16 -11
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_tunnel.pyi +25 -3
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/async_utils.py +337 -10
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/auth_token_manager.py +1 -4
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/blob_utils.py +29 -22
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/function_utils.py +26 -22
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/grpc_testing.py +6 -3
- modal-1.3.1.dev24/modal/_utils/grpc_utils.py +450 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/mount_utils.py +26 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/name_utils.py +2 -3
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/package_utils.py +0 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/rand_pb_testing.py +8 -1
- modal-1.3.1.dev24/modal/_utils/task_command_router_client.py +524 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_vendor/cloudpickle.py +144 -48
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/app.py +283 -109
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/app.pyi +219 -56
- modal-1.3.1.dev24/modal/billing.py +5 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/2025.06.txt +6 -3
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/PREVIEW.txt +2 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/base-images.json +4 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/_download.py +19 -3
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/cluster.py +4 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/config.py +3 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/container.py +10 -6
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/dict.py +5 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/entry_point.py +26 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/environment.py +6 -19
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/launch.py +1 -76
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/network_file_system.py +5 -20
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/programs/run_jupyter.py +1 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/programs/vscode.py +1 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/queues.py +5 -4
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/run.py +35 -195
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/secret.py +1 -2
- modal-1.3.1.dev24/modal/cli/shell.py +375 -0
- modal-1.3.1.dev24/modal/cli/token.py +108 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/utils.py +1 -13
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/volume.py +11 -17
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/client.py +16 -125
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/client.pyi +94 -144
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cloud_bucket_mount.py +3 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cloud_bucket_mount.pyi +4 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cls.py +98 -105
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cls.pyi +8 -85
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/config.py +21 -1
- modal-1.3.1.dev24/modal/container_process.py +472 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/container_process.pyi +99 -38
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/dict.py +71 -73
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/dict.pyi +87 -133
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/environments.py +15 -27
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/environments.pyi +5 -15
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/exception.py +154 -16
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/experimental/__init__.py +34 -38
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/experimental/flash.py +167 -74
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/experimental/flash.pyi +102 -47
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/file_io.py +50 -92
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/file_io.pyi +117 -89
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/functions.pyi +70 -136
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/image.py +116 -86
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/image.pyi +55 -35
- modal-1.3.1.dev24/modal/io_streams.py +822 -0
- modal-1.3.1.dev24/modal/io_streams.pyi +530 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/mount.py +66 -164
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/mount.pyi +33 -179
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/network_file_system.py +20 -51
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/network_file_system.pyi +53 -94
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/object.pyi +114 -22
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/output.py +2 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/parallel_map.py +42 -44
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/parallel_map.pyi +9 -17
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/partial_function.pyi +4 -3
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/proxy.py +14 -6
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/proxy.pyi +10 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/queue.py +44 -76
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/queue.pyi +81 -119
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/runner.py +99 -100
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/runner.pyi +44 -27
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/sandbox.py +225 -107
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/sandbox.pyi +226 -60
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/secret.py +57 -84
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/secret.pyi +28 -51
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/serving.py +7 -11
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/serving.pyi +7 -8
- modal-1.3.1.dev24/modal/snapshot.py +56 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/snapshot.pyi +18 -10
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/token_flow.py +1 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/token_flow.pyi +4 -6
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/volume.py +102 -96
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/volume.pyi +102 -120
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal.egg-info/PKG-INFO +10 -9
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal.egg-info/SOURCES.txt +13 -15
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal.egg-info/requires.txt +7 -2
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/api.proto +206 -70
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/api_grpc.py +75 -26
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/api_pb2.py +1215 -1100
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/api_pb2.pyi +556 -84
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/api_pb2_grpc.py +147 -49
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/api_pb2_grpc.pyi +49 -21
- modal-1.3.1.dev24/modal_proto/modal_api_grpc.py +195 -0
- modal-1.3.1.dev24/modal_proto/task_command_router.proto +164 -0
- modal-1.3.1.dev24/modal_proto/task_command_router_grpc.py +138 -0
- modal-1.3.1.dev24/modal_proto/task_command_router_pb2.py +180 -0
- modal-1.1.5.dev52/modal_proto/sandbox_router_pb2.pyi → modal-1.3.1.dev24/modal_proto/task_command_router_pb2.pyi +148 -57
- modal-1.3.1.dev24/modal_proto/task_command_router_pb2_grpc.py +272 -0
- modal-1.3.1.dev24/modal_proto/task_command_router_pb2_grpc.pyi +100 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_version/__init__.py +1 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_version/__main__.py +1 -1
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/pyproject.toml +34 -10
- modal-1.1.5.dev52/modal/_ipython.py +0 -11
- modal-1.1.5.dev52/modal/_utils/grpc_utils.py +0 -293
- modal-1.1.5.dev52/modal/cli/programs/launch_instance_ssh.py +0 -94
- modal-1.1.5.dev52/modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.5.dev52/modal/cli/token.py +0 -60
- modal-1.1.5.dev52/modal/container_process.py +0 -196
- modal-1.1.5.dev52/modal/io_streams.py +0 -471
- modal-1.1.5.dev52/modal/io_streams.pyi +0 -440
- modal-1.1.5.dev52/modal/snapshot.py +0 -42
- modal-1.1.5.dev52/modal_proto/modal_api_grpc.py +0 -192
- modal-1.1.5.dev52/modal_proto/modal_options_grpc.py +0 -3
- modal-1.1.5.dev52/modal_proto/options.proto +0 -19
- modal-1.1.5.dev52/modal_proto/options_grpc.py +0 -3
- modal-1.1.5.dev52/modal_proto/options_pb2.py +0 -35
- modal-1.1.5.dev52/modal_proto/options_pb2.pyi +0 -20
- modal-1.1.5.dev52/modal_proto/options_pb2_grpc.py +0 -4
- modal-1.1.5.dev52/modal_proto/options_pb2_grpc.pyi +0 -7
- modal-1.1.5.dev52/modal_proto/sandbox_router.proto +0 -125
- modal-1.1.5.dev52/modal_proto/sandbox_router_grpc.py +0 -89
- modal-1.1.5.dev52/modal_proto/sandbox_router_pb2.py +0 -128
- modal-1.1.5.dev52/modal_proto/sandbox_router_pb2_grpc.py +0 -169
- modal-1.1.5.dev52/modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/LICENSE +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_location.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_pty.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_resources.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/asgi.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_type_manager.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/logger.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/_watcher.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/builder/README.md +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/call_graph.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/_traceback.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/app.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/import_refs.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/profile.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/experimental/ipython.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/functions.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/gpu.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/object.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/partial_function.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/py.typed +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/retries.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/running_app.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/schedule.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/scheduler_placement.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal/stream_type.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_docs/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/__init__.py +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/modal_proto/py.typed +0 -0
- {modal-1.1.5.dev52 → modal-1.3.1.dev24}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.3.1.dev24
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
|
36
|
+
# Modal Python SDK
|
|
36
37
|
|
|
37
38
|
[](https://pypi.org/project/modal/)
|
|
38
39
|
[](https://github.com/modal-labs/modal-client/blob/master/LICENSE)
|
|
39
40
|
[](https://github.com/modal-labs/modal-client/actions/workflows/ci-cd.yml)
|
|
40
41
|
[](https://modal.com/slack)
|
|
41
42
|
|
|
42
|
-
The [Modal](https://modal.com/) Python
|
|
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.
|
|
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
|
|
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
|
|
@@ -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.
|
|
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,
|
|
5
|
-
raise RuntimeError("This version of Modal requires at least Python 3.
|
|
6
|
-
if sys.version_info[:2] >= (3,
|
|
7
|
-
raise RuntimeError("This version of Modal does not support Python 3.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
-
|
|
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
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
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
468
|
-
|
|
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__":
|