modal 1.3.1.dev28__tar.gz → 1.3.1.dev30__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.3.1.dev28 → modal-1.3.1.dev30}/PKG-INFO +1 -1
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/__main__.py +2 -1
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_load_context.py +26 -2
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_object.py +10 -7
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_resolver.py +6 -4
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/async_utils.py +51 -22
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/_traceback.py +2 -1
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/dict.py +4 -3
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/queues.py +4 -3
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/client.py +2 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/client.pyi +5 -2
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/image.py +4 -2
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/mount.py +3 -2
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/runner.py +19 -12
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/runner.pyi +5 -1
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/sandbox.py +3 -2
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_version/__init__.py +1 -1
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/LICENSE +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/README.md +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_billing.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_clustered_functions.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_functions.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_grpc_client.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_ipython.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_location.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_output.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_partial_function.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_pty.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_resources.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_serialization.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_server.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_traceback.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_tunnel.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_tunnel.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_type_manager.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/logger.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/_watcher.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/app.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/app.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/billing.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/README.md +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/builder/base-images.json +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/call_graph.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/_download.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/app.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/cluster.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/config.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/container.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/entry_point.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/environment.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/import_refs.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/launch.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/profile.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/run.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/secret.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/shell.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/token.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/utils.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cli/volume.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cls.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/cls.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/config.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/container_process.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/container_process.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/dict.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/dict.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/environments.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/environments.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/exception.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/experimental/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/experimental/flash.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/experimental/ipython.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/file_io.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/file_io.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/functions.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/functions.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/gpu.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/image.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/io_streams.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/io_streams.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/mount.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/network_file_system.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/network_file_system.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/object.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/object.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/output.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/parallel_map.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/parallel_map.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/partial_function.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/partial_function.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/proxy.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/proxy.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/py.typed +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/queue.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/queue.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/retries.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/running_app.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/sandbox.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/schedule.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/scheduler_placement.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/secret.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/secret.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/server.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/server.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/serving.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/serving.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/snapshot.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/snapshot.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/stream_type.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/token_flow.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/token_flow.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/volume.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal/volume.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_docs/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/__init__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/api.proto +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/api_grpc.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/api_pb2.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/py.typed +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/task_command_router.proto +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/modal_version/__main__.py +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/pyproject.toml +0 -0
- {modal-1.3.1.dev28 → modal-1.3.1.dev30}/setup.cfg +0 -0
|
@@ -35,6 +35,7 @@ def main():
|
|
|
35
35
|
):
|
|
36
36
|
raise
|
|
37
37
|
|
|
38
|
+
from rich.markup import escape
|
|
38
39
|
from rich.panel import Panel
|
|
39
40
|
|
|
40
41
|
title = "Error"
|
|
@@ -43,7 +44,7 @@ def main():
|
|
|
43
44
|
content = f"{content}\n\nNote: {' '.join(notes)}"
|
|
44
45
|
|
|
45
46
|
console = make_console(stderr=True)
|
|
46
|
-
panel = Panel(content, title=title, title_align="left", border_style="red")
|
|
47
|
+
panel = Panel(escape(content), title=title, title_align="left", border_style="red")
|
|
47
48
|
console.print(panel, highlight=False)
|
|
48
49
|
sys.exit(1)
|
|
49
50
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
3
|
|
|
4
4
|
from .client import _Client
|
|
5
5
|
from .config import config
|
|
6
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ._utils.async_utils import TaskContext
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
class LoadContext:
|
|
9
12
|
"""Encapsulates optional metadata values used during object loading.
|
|
@@ -15,6 +18,7 @@ class LoadContext:
|
|
|
15
18
|
_client: Optional[_Client] = None
|
|
16
19
|
_environment_name: Optional[str] = None
|
|
17
20
|
_app_id: Optional[str] = None
|
|
21
|
+
_task_context: Optional["TaskContext"] = None
|
|
18
22
|
|
|
19
23
|
def __init__(
|
|
20
24
|
self,
|
|
@@ -22,10 +26,12 @@ class LoadContext:
|
|
|
22
26
|
client: Optional[_Client] = None,
|
|
23
27
|
environment_name: Optional[str] = None,
|
|
24
28
|
app_id: Optional[str] = None,
|
|
29
|
+
task_context: Optional["TaskContext"] = None,
|
|
25
30
|
):
|
|
26
31
|
self._client = client
|
|
27
32
|
self._environment_name = environment_name
|
|
28
33
|
self._app_id = app_id
|
|
34
|
+
self._task_context = task_context
|
|
29
35
|
|
|
30
36
|
@property
|
|
31
37
|
def client(self) -> _Client:
|
|
@@ -41,6 +47,11 @@ class LoadContext:
|
|
|
41
47
|
def app_id(self) -> Optional[str]:
|
|
42
48
|
return self._app_id
|
|
43
49
|
|
|
50
|
+
@property
|
|
51
|
+
def task_context(self) -> "TaskContext":
|
|
52
|
+
assert self._task_context is not None, "LoadContext has no TaskContext"
|
|
53
|
+
return self._task_context
|
|
54
|
+
|
|
44
55
|
@classmethod
|
|
45
56
|
def empty(cls) -> "LoadContext":
|
|
46
57
|
"""Create an empty LoadContext with all fields set to None.
|
|
@@ -59,6 +70,7 @@ class LoadContext:
|
|
|
59
70
|
client=self._client if self._client is not None else parent._client,
|
|
60
71
|
environment_name=self._environment_name if self._environment_name is not None else parent._environment_name,
|
|
61
72
|
app_id=self._app_id if self._app_id is not None else parent._app_id,
|
|
73
|
+
task_context=self._task_context if self._task_context is not None else parent._task_context,
|
|
62
74
|
) # TODO (elias): apply_defaults?
|
|
63
75
|
|
|
64
76
|
async def apply_defaults(self) -> "LoadContext":
|
|
@@ -71,16 +83,27 @@ class LoadContext:
|
|
|
71
83
|
client=self.client if is_valid_client else await _Client.from_env(),
|
|
72
84
|
environment_name=self._environment_name or config.get("environment") or "",
|
|
73
85
|
app_id=self._app_id,
|
|
86
|
+
task_context=self._task_context,
|
|
74
87
|
)
|
|
75
88
|
|
|
76
89
|
def reset(self) -> "LoadContext":
|
|
90
|
+
"""In-place replace all values with None, such that any inferred values/upgrades
|
|
91
|
+
will work. This is useful in cases where a load context reference may have leaked
|
|
92
|
+
into objects and used/upgraded but you want to make a fresh re-load, e.g. when doing
|
|
93
|
+
multiple `app.run()` calls in the same interpreter session.
|
|
94
|
+
"""
|
|
77
95
|
self._client = None
|
|
78
96
|
self._environment_name = None
|
|
79
97
|
self._app_id = None
|
|
98
|
+
self._task_context = None
|
|
80
99
|
return self
|
|
81
100
|
|
|
82
101
|
async def in_place_upgrade(
|
|
83
|
-
self,
|
|
102
|
+
self,
|
|
103
|
+
client: Optional[_Client] = None,
|
|
104
|
+
environment_name: Optional[str] = None,
|
|
105
|
+
app_id: Optional[str] = None,
|
|
106
|
+
task_context: Optional["TaskContext"] = None,
|
|
84
107
|
) -> "LoadContext":
|
|
85
108
|
"""In-place set values if they aren't already set, or set default values
|
|
86
109
|
|
|
@@ -103,4 +126,5 @@ class LoadContext:
|
|
|
103
126
|
self._client = self._client or client or await _Client.from_env()
|
|
104
127
|
self._environment_name = self._environment_name or environment_name or config.get("environment") or ""
|
|
105
128
|
self._app_id = self._app_id or app_id
|
|
129
|
+
self._task_context = self._task_context or task_context
|
|
106
130
|
return self
|
|
@@ -13,7 +13,7 @@ from modal._traceback import suppress_tb_frame
|
|
|
13
13
|
|
|
14
14
|
from ._load_context import LoadContext
|
|
15
15
|
from ._resolver import Resolver
|
|
16
|
-
from ._utils.async_utils import aclosing
|
|
16
|
+
from ._utils.async_utils import TaskContext, aclosing
|
|
17
17
|
from ._utils.deprecation import deprecation_warning
|
|
18
18
|
from .client import _Client
|
|
19
19
|
from .config import config, logger
|
|
@@ -314,9 +314,10 @@ class _Object:
|
|
|
314
314
|
self._is_hydrated = False # un-hydrate and re-resolve
|
|
315
315
|
# we don't set an explicit Client here, relying on the default
|
|
316
316
|
# env client to be applied by LoadContext.apply_default
|
|
317
|
-
root_load_context = LoadContext.empty()
|
|
318
317
|
resolver = Resolver()
|
|
319
|
-
|
|
318
|
+
async with TaskContext() as tc:
|
|
319
|
+
root_load_context = LoadContext(task_context=tc)
|
|
320
|
+
await resolver.load(typing.cast(_Object, self), root_load_context)
|
|
320
321
|
else:
|
|
321
322
|
logger.debug(f"reloading non-lazy {self} by replacing client")
|
|
322
323
|
self._client = client or await _Client.from_env()
|
|
@@ -325,11 +326,13 @@ class _Object:
|
|
|
325
326
|
elif not self._hydrate_lazily:
|
|
326
327
|
self._validate_is_hydrated()
|
|
327
328
|
else:
|
|
328
|
-
# Set the client on LoadContext before loading
|
|
329
|
-
|
|
329
|
+
# Set the client on LoadContext before loading, with a TaskContext for proper
|
|
330
|
+
# exception handling when loading shared dependencies
|
|
330
331
|
resolver = Resolver()
|
|
331
|
-
with
|
|
332
|
-
|
|
332
|
+
async with TaskContext() as tc:
|
|
333
|
+
root_load_context = LoadContext(client=client, task_context=tc)
|
|
334
|
+
with suppress_tb_frame(): # skip this frame by default
|
|
335
|
+
await resolver.load(self, root_load_context)
|
|
333
336
|
return self
|
|
334
337
|
|
|
335
338
|
|
|
@@ -13,7 +13,6 @@ from modal._traceback import suppress_tb_frame
|
|
|
13
13
|
from modal_proto import api_pb2
|
|
14
14
|
|
|
15
15
|
from ._load_context import LoadContext
|
|
16
|
-
from ._utils.async_utils import TaskContext
|
|
17
16
|
|
|
18
17
|
if TYPE_CHECKING:
|
|
19
18
|
from rich.tree import Tree
|
|
@@ -123,8 +122,10 @@ class Resolver:
|
|
|
123
122
|
with suppress_tb_frame():
|
|
124
123
|
load_context = await obj._load_context_overrides.merged_with(parent_load_context).apply_defaults()
|
|
125
124
|
|
|
126
|
-
#
|
|
127
|
-
|
|
125
|
+
# Use asyncio.gather here (not TaskContext.gather) - the shared TaskContext
|
|
126
|
+
# in load_context handles cancellation at the top level, preventing premature
|
|
127
|
+
# cancellation of shared dependencies when sibling tasks fail.
|
|
128
|
+
await asyncio.gather(*[self.load(dep, load_context) for dep in obj.deps()])
|
|
128
129
|
|
|
129
130
|
# Load the object itself
|
|
130
131
|
if not obj._load:
|
|
@@ -147,7 +148,8 @@ class Resolver:
|
|
|
147
148
|
|
|
148
149
|
return obj
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
# use task_context from load_context to make sure tasks are cleaned up eventually
|
|
152
|
+
cached_future = parent_load_context.task_context.create_task(loader())
|
|
151
153
|
self._local_uuid_to_future[obj.local_uuid] = cached_future
|
|
152
154
|
if deduplication_key is not None:
|
|
153
155
|
self._deduplication_cache[deduplication_key] = cached_future
|
|
@@ -402,26 +402,32 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
|
|
|
402
402
|
class TaskContext:
|
|
403
403
|
"""A structured group that helps manage stray tasks.
|
|
404
404
|
|
|
405
|
-
This differs from the standard library `asyncio.TaskGroup` in that it cancels
|
|
405
|
+
This differs from the standard library `asyncio.TaskGroup` in that it *cancels* tasks still
|
|
406
406
|
running after exiting the context manager, rather than waiting for them to finish.
|
|
407
407
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
Arguments:
|
|
409
|
+
`grace: float`: period in seconds, which will wait for a certain amount of time before cancelling
|
|
410
|
+
all remaining tasks. This is useful for allowing tasks to finish after the context exits.
|
|
411
|
+
|
|
412
|
+
`cancellation_grace: float = 1.0`: period in seconds that cancelled tasks are allowed to stall before
|
|
413
|
+
they exit once they get cancelled (e.g. if they do async handling of the CancelledError). If tasks
|
|
414
|
+
take longer than this to exit the tasks are left dangling when the context exits.
|
|
411
415
|
|
|
412
416
|
Usage:
|
|
413
417
|
|
|
414
418
|
```python notest
|
|
415
|
-
async with TaskContext() as task_context:
|
|
419
|
+
async with TaskContext(grace=1.0) as task_context:
|
|
416
420
|
task = task_context.create_task(coro())
|
|
417
421
|
```
|
|
418
422
|
"""
|
|
419
423
|
|
|
420
424
|
_loops: set[asyncio.Task]
|
|
421
425
|
|
|
422
|
-
def __init__(self, grace: Optional[float] = None):
|
|
426
|
+
def __init__(self, grace: Optional[float] = None, *, cancellation_grace: float = 1.0):
|
|
423
427
|
self._grace = grace # grace is the time we want for tasks to finish before cancelling them
|
|
424
|
-
self._cancellation_grace: float =
|
|
428
|
+
self._cancellation_grace: float = (
|
|
429
|
+
cancellation_grace # extra graceperiod for the cancellation itself to "bubble up"
|
|
430
|
+
)
|
|
425
431
|
self._loops = set()
|
|
426
432
|
|
|
427
433
|
async def start(self):
|
|
@@ -438,10 +444,19 @@ class TaskContext:
|
|
|
438
444
|
return self
|
|
439
445
|
|
|
440
446
|
async def stop(self):
|
|
447
|
+
"""This is called when exiting the TaskContext
|
|
448
|
+
|
|
449
|
+
Two important properties that we need to maintain here:
|
|
450
|
+
* Should never raise exceptions as a result
|
|
451
|
+
of exceptions (incl. cancellations) in the contained tasks
|
|
452
|
+
* Should not have an open-ended runtime, even if
|
|
453
|
+
the contained tasks are uncooperative with cancellations.
|
|
454
|
+
"""
|
|
441
455
|
self._exited.set()
|
|
442
456
|
await asyncio.sleep(0) # Causes any just-created tasks to get started
|
|
443
457
|
unfinished_tasks = [t for t in self._tasks if not t.done()]
|
|
444
458
|
gather_future = None
|
|
459
|
+
|
|
445
460
|
try:
|
|
446
461
|
if self._grace is not None and unfinished_tasks:
|
|
447
462
|
gather_future = asyncio.gather(*unfinished_tasks, return_exceptions=True)
|
|
@@ -458,17 +473,16 @@ class TaskContext:
|
|
|
458
473
|
|
|
459
474
|
cancelled_tasks: list[asyncio.Task] = []
|
|
460
475
|
for task in self._tasks:
|
|
461
|
-
if task.done() and not task.cancelled():
|
|
462
|
-
# Raise any exceptions if they happened.
|
|
463
|
-
# Only tasks without a done_callback will still be present in self._tasks
|
|
464
|
-
task.result()
|
|
465
|
-
|
|
466
476
|
if task.done():
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
477
|
+
# consume potential exceptions so we don't get warnings
|
|
478
|
+
# not that this is not supposed to reraise exceptions
|
|
479
|
+
# since those are expected to be reraised by aexit anyway
|
|
480
|
+
with contextlib.suppress(BaseException):
|
|
481
|
+
task.result()
|
|
482
|
+
else:
|
|
483
|
+
# Cancel any remaining unfinished tasks.
|
|
484
|
+
task.cancel()
|
|
485
|
+
cancelled_tasks.append(task)
|
|
472
486
|
|
|
473
487
|
cancellation_gather = asyncio.gather(*cancelled_tasks, return_exceptions=True)
|
|
474
488
|
try:
|
|
@@ -479,7 +493,22 @@ class TaskContext:
|
|
|
479
493
|
await asyncio.sleep(0) # wake up coroutines waiting for cancellations
|
|
480
494
|
|
|
481
495
|
async def __aexit__(self, exc_type, value, tb):
|
|
482
|
-
|
|
496
|
+
"""
|
|
497
|
+
This is a bit involved:
|
|
498
|
+
* If there is an exception within the "context", we typically always want to reraise that
|
|
499
|
+
* If a cancellation comes in *during* aexit/stop execution itself, we don't actually cancel
|
|
500
|
+
the exit logic (it's already performing cancellation logic of sorts), but we do reraise
|
|
501
|
+
the CancelledError to prevent muting cancellation chains
|
|
502
|
+
"""
|
|
503
|
+
stop_task = asyncio.ensure_future(self.stop())
|
|
504
|
+
try:
|
|
505
|
+
await asyncio.shield(stop_task)
|
|
506
|
+
except asyncio.CancelledError:
|
|
507
|
+
if not stop_task.done():
|
|
508
|
+
# External cancellation - wait for stop() to finish, then propagate
|
|
509
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
510
|
+
await stop_task # always run stop_task to completion
|
|
511
|
+
raise
|
|
483
512
|
|
|
484
513
|
def create_task(self, coro_or_task) -> asyncio.Task:
|
|
485
514
|
if isinstance(coro_or_task, asyncio.Task):
|
|
@@ -537,9 +566,9 @@ class TaskContext:
|
|
|
537
566
|
For example, if you use `asyncio.gather(t1, t2, t3)` and t2 raises an exception, then t1 and
|
|
538
567
|
t3 would continue running. With `TaskContext.gather(t1, t2, t3)`, they are cancelled.
|
|
539
568
|
|
|
540
|
-
|
|
569
|
+
It's still useful to use `asyncio.gather()` if you don't need cancellation — for
|
|
541
570
|
example, if you're just gathering quick coroutines with no side-effects. Or if you're
|
|
542
|
-
gathering the tasks with `return_exceptions=True`.
|
|
571
|
+
gathering the tasks with `return_exceptions=True`.
|
|
543
572
|
|
|
544
573
|
Usage:
|
|
545
574
|
|
|
@@ -557,8 +586,8 @@ class TaskContext:
|
|
|
557
586
|
```
|
|
558
587
|
"""
|
|
559
588
|
async with TaskContext() as tc:
|
|
560
|
-
|
|
561
|
-
|
|
589
|
+
tasks = [tc.create_task(coro) for coro in coros]
|
|
590
|
+
return await asyncio.gather(*tasks)
|
|
562
591
|
|
|
563
592
|
|
|
564
593
|
def run_coro_blocking(coro):
|
|
@@ -7,6 +7,7 @@ import warnings
|
|
|
7
7
|
from typing import Optional
|
|
8
8
|
|
|
9
9
|
from rich.console import RenderResult, group
|
|
10
|
+
from rich.markup import escape
|
|
10
11
|
from rich.panel import Panel
|
|
11
12
|
from rich.syntax import Syntax
|
|
12
13
|
from rich.text import Text
|
|
@@ -189,7 +190,7 @@ def highlight_modal_warnings() -> None:
|
|
|
189
190
|
if date:
|
|
190
191
|
title += f" ({date})"
|
|
191
192
|
panel = Panel(
|
|
192
|
-
message,
|
|
193
|
+
escape(message),
|
|
193
194
|
border_style="yellow",
|
|
194
195
|
title=title,
|
|
195
196
|
title_align="left",
|
|
@@ -7,7 +7,7 @@ from typer import Argument, Option, Typer
|
|
|
7
7
|
from modal._load_context import LoadContext
|
|
8
8
|
from modal._output import make_console
|
|
9
9
|
from modal._resolver import Resolver
|
|
10
|
-
from modal._utils.async_utils import synchronizer
|
|
10
|
+
from modal._utils.async_utils import TaskContext, synchronizer
|
|
11
11
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
12
12
|
from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
|
|
13
13
|
from modal.client import _Client
|
|
@@ -32,8 +32,9 @@ async def create(name: str, *, env: Optional[str] = ENV_OPTION):
|
|
|
32
32
|
client = await _Client.from_env()
|
|
33
33
|
resolver = Resolver()
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
async with TaskContext() as tc:
|
|
36
|
+
load_context = LoadContext(client=client, environment_name=env, task_context=tc)
|
|
37
|
+
await resolver.load(d, load_context)
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@dict_cli.command(name="list", rich_help_panel="Management")
|
|
@@ -8,7 +8,7 @@ from typer import Argument, Option, Typer
|
|
|
8
8
|
from modal._load_context import LoadContext
|
|
9
9
|
from modal._output import make_console
|
|
10
10
|
from modal._resolver import Resolver
|
|
11
|
-
from modal._utils.async_utils import synchronizer
|
|
11
|
+
from modal._utils.async_utils import TaskContext, synchronizer
|
|
12
12
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
13
13
|
from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table
|
|
14
14
|
from modal.client import _Client
|
|
@@ -40,8 +40,9 @@ async def create(name: str, *, env: Optional[str] = ENV_OPTION):
|
|
|
40
40
|
q = _Queue.from_name(name, environment_name=env, create_if_missing=True)
|
|
41
41
|
client = await _Client.from_env()
|
|
42
42
|
resolver = Resolver()
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
async with TaskContext() as tc:
|
|
44
|
+
load_context = LoadContext(client=client, environment_name=env, task_context=tc)
|
|
45
|
+
await resolver.load(q, load_context)
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@queue_cli.command(name="delete", rich_help_panel="Management")
|
|
@@ -74,6 +74,7 @@ class _Client:
|
|
|
74
74
|
_stub: Optional[modal_api_grpc.ModalClientModal] = None
|
|
75
75
|
_auth_token_manager: Optional[_AuthTokenManager] = None
|
|
76
76
|
_snapshotted: bool = False
|
|
77
|
+
_connection_manager: Optional[ConnectionManager] = None
|
|
77
78
|
client_type: "api_pb2.ClientType.ValueType"
|
|
78
79
|
|
|
79
80
|
def __init__(
|
|
@@ -315,6 +316,7 @@ class _Client:
|
|
|
315
316
|
# Get a valid grpclib channel, reusing existing channels if possible.
|
|
316
317
|
# This prevents usage of stale channels across forks of processes.
|
|
317
318
|
await self._reset_on_pid_change()
|
|
319
|
+
assert self._connection_manager # invariant: ._open() should always have been called before this
|
|
318
320
|
return await self._connection_manager.get_or_create_channel(server_url)
|
|
319
321
|
|
|
320
322
|
@synchronizer.nowrap
|
|
@@ -5,6 +5,7 @@ import google.protobuf.message
|
|
|
5
5
|
import grpclib.client
|
|
6
6
|
import modal._utils.async_utils
|
|
7
7
|
import modal._utils.auth_token_manager
|
|
8
|
+
import modal._utils.grpc_utils
|
|
8
9
|
import modal_proto.modal_api_grpc
|
|
9
10
|
import synchronicity.combined_types
|
|
10
11
|
import typing
|
|
@@ -26,6 +27,7 @@ class _Client:
|
|
|
26
27
|
_stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
|
|
27
28
|
_auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
|
|
28
29
|
_snapshotted: bool
|
|
30
|
+
_connection_manager: typing.Optional[modal._utils.grpc_utils.ConnectionManager]
|
|
29
31
|
client_type: int
|
|
30
32
|
|
|
31
33
|
def __init__(
|
|
@@ -33,7 +35,7 @@ class _Client:
|
|
|
33
35
|
server_url: str,
|
|
34
36
|
client_type: int,
|
|
35
37
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.3.1.
|
|
38
|
+
version: str = "1.3.1.dev30",
|
|
37
39
|
):
|
|
38
40
|
"""mdmd:hidden
|
|
39
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -156,6 +158,7 @@ class Client:
|
|
|
156
158
|
_stub: typing.Optional[modal_proto.modal_api_grpc.ModalClientModal]
|
|
157
159
|
_auth_token_manager: typing.Optional[modal._utils.auth_token_manager._AuthTokenManager]
|
|
158
160
|
_snapshotted: bool
|
|
161
|
+
_connection_manager: typing.Optional[modal._utils.grpc_utils.ConnectionManager]
|
|
159
162
|
client_type: int
|
|
160
163
|
|
|
161
164
|
def __init__(
|
|
@@ -163,7 +166,7 @@ class Client:
|
|
|
163
166
|
server_url: str,
|
|
164
167
|
client_type: int,
|
|
165
168
|
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.3.1.
|
|
169
|
+
version: str = "1.3.1.dev30",
|
|
167
170
|
):
|
|
168
171
|
"""mdmd:hidden
|
|
169
172
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -33,7 +33,7 @@ from ._load_context import LoadContext
|
|
|
33
33
|
from ._object import _Object, live_method_gen
|
|
34
34
|
from ._resolver import Resolver
|
|
35
35
|
from ._serialization import get_preferred_payload_format, serialize
|
|
36
|
-
from ._utils.async_utils import deprecate_aio_usage, synchronize_api, synchronizer
|
|
36
|
+
from ._utils.async_utils import TaskContext, deprecate_aio_usage, synchronize_api, synchronizer
|
|
37
37
|
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
38
38
|
from ._utils.docker_utils import (
|
|
39
39
|
extract_copy_command_patterns,
|
|
@@ -959,7 +959,9 @@ class _Image(_Object, type_prefix="im"):
|
|
|
959
959
|
raise InvalidError("App has not been initialized yet. Use the content manager `app.run()` or `App.lookup`")
|
|
960
960
|
|
|
961
961
|
resolver = Resolver()
|
|
962
|
-
|
|
962
|
+
async with TaskContext() as tc:
|
|
963
|
+
load_context = LoadContext(task_context=tc).merged_with(app._root_load_context)
|
|
964
|
+
await resolver.load(self, load_context)
|
|
963
965
|
return self
|
|
964
966
|
|
|
965
967
|
def pip_install(
|
|
@@ -679,8 +679,9 @@ class _Mount(_Object, type_prefix="mo"):
|
|
|
679
679
|
self._namespace = namespace
|
|
680
680
|
self._allow_overwrite = allow_overwrite
|
|
681
681
|
resolver = Resolver()
|
|
682
|
-
|
|
683
|
-
|
|
682
|
+
async with TaskContext() as tc:
|
|
683
|
+
load_context = LoadContext(client=client, environment_name=environment_name, task_context=tc)
|
|
684
|
+
await resolver.load(self, load_context)
|
|
684
685
|
|
|
685
686
|
def _get_metadata(self) -> api_pb2.MountHandleMetadata:
|
|
686
687
|
if self._content_checksum_sha256_hex is None:
|
|
@@ -128,8 +128,13 @@ async def _create_all_objects(
|
|
|
128
128
|
local_app_state: "modal.app._LocalAppState",
|
|
129
129
|
load_context: LoadContext,
|
|
130
130
|
) -> None:
|
|
131
|
-
"""Create objects that have been defined but not created on the server.
|
|
131
|
+
"""Create objects that have been defined but not created on the server.
|
|
132
|
+
|
|
133
|
+
The load_context must have a task_context set for proper exception handling
|
|
134
|
+
when loading shared dependencies.
|
|
135
|
+
"""
|
|
132
136
|
indexed_objects: dict[str, _Object] = {**local_app_state.functions, **local_app_state.classes}
|
|
137
|
+
tc = load_context.task_context
|
|
133
138
|
|
|
134
139
|
resolver = Resolver()
|
|
135
140
|
with resolver.display():
|
|
@@ -158,8 +163,6 @@ async def _create_all_objects(
|
|
|
158
163
|
if obj.is_hydrated:
|
|
159
164
|
tag_to_object_id[tag] = obj.object_id
|
|
160
165
|
|
|
161
|
-
await TaskContext.gather(*(_preload(tag, obj) for tag, obj in indexed_objects.items()))
|
|
162
|
-
|
|
163
166
|
async def _load(tag, obj):
|
|
164
167
|
existing_object_id = tag_to_object_id.get(tag)
|
|
165
168
|
# Pass load_context so dependencies can inherit app_id, client, etc.
|
|
@@ -171,7 +174,8 @@ async def _create_all_objects(
|
|
|
171
174
|
else:
|
|
172
175
|
raise RuntimeError(f"Unexpected object {obj.object_id}")
|
|
173
176
|
|
|
174
|
-
await
|
|
177
|
+
await asyncio.gather(*[tc.create_task(_preload(tag, obj)) for tag, obj in indexed_objects.items()])
|
|
178
|
+
await asyncio.gather(*[tc.create_task(_load(tag, obj)) for tag, obj in indexed_objects.items()])
|
|
175
179
|
|
|
176
180
|
|
|
177
181
|
async def _publish_app(
|
|
@@ -296,10 +300,12 @@ async def _run_app(
|
|
|
296
300
|
app_state=app_state,
|
|
297
301
|
interactive=interactive,
|
|
298
302
|
)
|
|
299
|
-
await load_context.in_place_upgrade(app_id=running_app.app_id)
|
|
300
303
|
|
|
301
304
|
logs_timeout = config["logs_timeout"]
|
|
302
305
|
async with app._set_local_app(load_context.client, running_app), TaskContext(grace=logs_timeout) as tc:
|
|
306
|
+
# Inject TaskContext into load_context for proper exception handling when loading shared dependencies
|
|
307
|
+
await load_context.in_place_upgrade(task_context=tc, app_id=running_app.app_id)
|
|
308
|
+
|
|
303
309
|
# Start heartbeats loop to keep the client alive
|
|
304
310
|
# we don't log heartbeat exceptions in detached mode
|
|
305
311
|
# as losing the local connection will not affect the running app
|
|
@@ -443,10 +449,12 @@ async def _serve_update(
|
|
|
443
449
|
load_context = await app._root_load_context.reset().in_place_upgrade(environment_name=environment_name)
|
|
444
450
|
try:
|
|
445
451
|
running_app: RunningApp = await _init_local_app_existing(load_context.client, existing_app_id, environment_name)
|
|
446
|
-
await load_context.in_place_upgrade(app_id=running_app.app_id)
|
|
447
452
|
local_app_state = app._local_state
|
|
448
|
-
|
|
449
|
-
|
|
453
|
+
|
|
454
|
+
# Create objects with a TaskContext for proper exception handling
|
|
455
|
+
async with TaskContext() as tc:
|
|
456
|
+
await load_context.in_place_upgrade(task_context=tc, app_id=running_app.app_id)
|
|
457
|
+
await _create_all_objects(running_app, local_app_state, load_context)
|
|
450
458
|
|
|
451
459
|
# Publish the updated app
|
|
452
460
|
await _publish_app(
|
|
@@ -526,11 +534,10 @@ async def _deploy_app(
|
|
|
526
534
|
root_load_context.client, name, local_app_state.tags, environment_name=root_load_context.environment_name
|
|
527
535
|
)
|
|
528
536
|
|
|
529
|
-
await root_load_context.in_place_upgrade(
|
|
530
|
-
app_id=running_app.app_id,
|
|
531
|
-
)
|
|
532
|
-
|
|
533
537
|
async with TaskContext(0) as tc:
|
|
538
|
+
# Inject TaskContext into load_context for proper exception handling when loading shared dependencies
|
|
539
|
+
await root_load_context.in_place_upgrade(task_context=tc, app_id=running_app.app_id)
|
|
540
|
+
|
|
534
541
|
# Start heartbeats loop to keep the client alive
|
|
535
542
|
def heartbeat():
|
|
536
543
|
return _heartbeat(client, running_app.app_id)
|
|
@@ -30,7 +30,11 @@ async def _create_all_objects(
|
|
|
30
30
|
local_app_state: modal.app._LocalAppState,
|
|
31
31
|
load_context: modal._load_context.LoadContext,
|
|
32
32
|
) -> None:
|
|
33
|
-
"""Create objects that have been defined but not created on the server.
|
|
33
|
+
"""Create objects that have been defined but not created on the server.
|
|
34
|
+
|
|
35
|
+
The load_context must have a task_context set for proper exception handling
|
|
36
|
+
when loading shared dependencies.
|
|
37
|
+
"""
|
|
34
38
|
...
|
|
35
39
|
|
|
36
40
|
async def _publish_app(
|
|
@@ -491,8 +491,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
491
491
|
client = client or app_client
|
|
492
492
|
|
|
493
493
|
resolver = Resolver()
|
|
494
|
-
|
|
495
|
-
|
|
494
|
+
async with TaskContext() as tc:
|
|
495
|
+
load_context = LoadContext(client=client, app_id=app_id, task_context=tc)
|
|
496
|
+
await resolver.load(obj, load_context)
|
|
496
497
|
return obj
|
|
497
498
|
|
|
498
499
|
def _hydrate_metadata(self, handle_metadata: Optional[Message]):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|