modAL 1.3.3.dev0__tar.gz → 1.3.4__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.3.dev0 → modal-1.3.4}/PKG-INFO +1 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/__init__.py +2 -2
- modal-1.3.4/modal/__main__.py +49 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_billing.py +8 -2
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_functions.py +5 -8
- modal-1.3.4/modal/_output/__init__.py +1 -0
- modal-1.3.4/modal/_output/manager.py +542 -0
- modal-1.3.4/modal/_output/pty.py +200 -0
- modal-1.3.3.dev0/modal/_output.py → modal-1.3.4/modal/_output/rich.py +326 -266
- modal-1.3.4/modal/_output/status.py +101 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_resolver.py +1 -62
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/asgi.py +2 -2
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_server.py +3 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/browser_utils.py +8 -7
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/function_utils.py +0 -87
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/grpc_utils.py +17 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/shell_utils.py +48 -2
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/task_command_router_client.py +3 -1
- modal-1.3.4/modal/_utils/time_utils.py +204 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_watcher.py +2 -3
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/app.py +5 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/app.pyi +10 -3
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/_traceback.py +3 -36
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/app.py +1 -4
- modal-1.3.4/modal/cli/billing.py +153 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/cluster.py +4 -5
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/config.py +2 -3
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/container.py +8 -2
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/dashboard.py +1 -4
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/dict.py +2 -3
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/entry_point.py +15 -10
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/import_refs.py +15 -10
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/launch.py +12 -15
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/network_file_system.py +21 -29
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/profile.py +6 -6
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/queues.py +4 -5
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/run.py +46 -45
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/secret.py +7 -6
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/token.py +11 -12
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/utils.py +28 -9
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/volume.py +19 -24
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/client.pyi +2 -10
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cloud_bucket_mount.py +3 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cloud_bucket_mount.pyi +3 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/container_process.py +6 -8
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/dict.py +42 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/dict.pyi +52 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/experimental/flash.py +10 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/experimental/flash.pyi +3 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/gpu.py +3 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/image.py +15 -8
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/image.pyi +7 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/io_streams.py +4 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/mount.py +4 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/network_file_system.py +1 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/network_file_system.pyi +3 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/object.pyi +57 -13
- modal-1.3.4/modal/output.py +47 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/partial_function.pyi +2 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/queue.py +42 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/queue.pyi +52 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/retries.py +0 -3
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/runner.py +49 -56
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/sandbox.py +149 -19
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/sandbox.pyi +203 -29
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/serving.py +10 -13
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/token_flow.py +18 -19
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/volume.py +71 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/volume.pyi +56 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.3.dev0 → modal-1.3.4}/modal.egg-info/SOURCES.txt +6 -2
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_docs/gen_reference_docs.py +1 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_docs/mdmd/mdmd.py +10 -2
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/api.proto +30 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/api_grpc.py +48 -0
- modal-1.3.4/modal_proto/api_pb2.py +5310 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/api_pb2.pyi +99 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/api_pb2_grpc.py +99 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/api_pb2_grpc.pyi +30 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/modal_api_grpc.py +3 -0
- modal-1.3.4/modal_proto/task_command_router_pb2.py +180 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_version/__init__.py +1 -1
- modal-1.3.3.dev0/modal/__main__.py +0 -53
- modal-1.3.3.dev0/modal/_pty.py +0 -57
- modal-1.3.3.dev0/modal/_utils/time_utils.py +0 -43
- modal-1.3.3.dev0/modal/output.py +0 -64
- modal-1.3.3.dev0/modal_proto/api_pb2.py +0 -1139
- modal-1.3.3.dev0/modal_proto/task_command_router_pb2.py +0 -60
- {modal-1.3.3.dev0 → modal-1.3.4}/LICENSE +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/README.md +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_clustered_functions.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_grpc_client.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_ipython.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_load_context.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_location.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_object.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_partial_function.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_resources.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_serialization.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_traceback.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_tunnel.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_tunnel.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_type_manager.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/logger.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/billing.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/README.md +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/builder/base-images.json +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/call_graph.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/_download.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/environment.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cli/shell.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/client.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cls.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/cls.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/config.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/container_process.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/environments.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/environments.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/exception.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/experimental/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/experimental/ipython.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/file_io.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/file_io.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/functions.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/functions.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/io_streams.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/mount.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/object.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/parallel_map.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/parallel_map.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/partial_function.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/proxy.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/proxy.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/py.typed +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/runner.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/running_app.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/schedule.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/scheduler_placement.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/secret.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/secret.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/server.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/server.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/serving.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/snapshot.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/snapshot.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/stream_type.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal/token_flow.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_docs/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/__init__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/py.typed +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/task_command_router.proto +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/modal_version/__main__.py +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/pyproject.toml +0 -0
- {modal-1.3.3.dev0 → modal-1.3.4}/setup.cfg +0 -0
|
@@ -9,6 +9,7 @@ if sys.version_info[:2] >= (3, 15):
|
|
|
9
9
|
from modal_version import __version__
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
|
+
from . import billing
|
|
12
13
|
from ._runtime.execution_context import current_function_call_id, current_input_id, interact, is_local
|
|
13
14
|
from ._tunnel import Tunnel, forward
|
|
14
15
|
from .app import App
|
|
@@ -41,7 +42,6 @@ try:
|
|
|
41
42
|
from .schedule import Cron, Period
|
|
42
43
|
from .scheduler_placement import SchedulerPlacement
|
|
43
44
|
from .secret import Secret
|
|
44
|
-
from .server import Server
|
|
45
45
|
from .snapshot import SandboxSnapshot
|
|
46
46
|
from .volume import Volume
|
|
47
47
|
except Exception:
|
|
@@ -76,11 +76,11 @@ __all__ = [
|
|
|
76
76
|
"SandboxSnapshot",
|
|
77
77
|
"SchedulerPlacement",
|
|
78
78
|
"Secret",
|
|
79
|
-
"Server",
|
|
80
79
|
"Tunnel",
|
|
81
80
|
"Volume",
|
|
82
81
|
"asgi_app",
|
|
83
82
|
"batched",
|
|
83
|
+
"billing",
|
|
84
84
|
"concurrent",
|
|
85
85
|
"current_function_call_id",
|
|
86
86
|
"current_input_id",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright Modal Labs 2022
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from ._traceback import reduce_traceback_to_user_code
|
|
5
|
+
from .cli._traceback import highlight_modal_warnings, setup_rich_traceback
|
|
6
|
+
from .cli.entry_point import entrypoint_cli
|
|
7
|
+
from .cli.import_refs import _CliUserExecutionError
|
|
8
|
+
from .config import config
|
|
9
|
+
from .output import OutputManager, enable_output
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
# Setup rich tracebacks, but only on user's end, when using the Modal CLI.
|
|
14
|
+
setup_rich_traceback()
|
|
15
|
+
highlight_modal_warnings()
|
|
16
|
+
|
|
17
|
+
# Enable rich output for the entire CLI session
|
|
18
|
+
with enable_output():
|
|
19
|
+
try:
|
|
20
|
+
entrypoint_cli()
|
|
21
|
+
|
|
22
|
+
except _CliUserExecutionError as exc:
|
|
23
|
+
if config.get("traceback"):
|
|
24
|
+
raise
|
|
25
|
+
|
|
26
|
+
assert exc.__cause__ # We should always raise this class from another error
|
|
27
|
+
tb = reduce_traceback_to_user_code(exc.__cause__.__traceback__, exc.user_source)
|
|
28
|
+
sys.excepthook(type(exc.__cause__), exc.__cause__, tb)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
except Exception as exc:
|
|
32
|
+
if (
|
|
33
|
+
# User has asked to alway see full tracebacks
|
|
34
|
+
config.get("traceback")
|
|
35
|
+
# The exception message is empty, so we need to provide _some_ actionable information
|
|
36
|
+
or not str(exc)
|
|
37
|
+
):
|
|
38
|
+
raise
|
|
39
|
+
|
|
40
|
+
content = str(exc)
|
|
41
|
+
if notes := getattr(exc, "__notes__", []):
|
|
42
|
+
content = f"{content}\n\nNote: {' '.join(notes)}"
|
|
43
|
+
|
|
44
|
+
OutputManager.get().print_error(content)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -40,8 +40,14 @@ async def _workspace_billing_report(
|
|
|
40
40
|
partial `end` intervals will be excluded.
|
|
41
41
|
|
|
42
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.
|
|
44
|
-
to
|
|
43
|
+
and `tag_names` (i.e., keys) are specified in the request. Alternatively, pass `tag_names=["*"]`
|
|
44
|
+
to include all tags in the report. Note that tags will be attributed to the entire interval even
|
|
45
|
+
if they were added or removed at some point within it. If the tag name was not in use during an
|
|
46
|
+
interval, it will be absent from the tags dictionary in that output row.
|
|
47
|
+
|
|
48
|
+
In most cases, billing data will be available in the database that this API queries within
|
|
49
|
+
minutes, although there may be collection delays. If completeness is important for your use
|
|
50
|
+
case, we recommend leaving a buffer after the end of the query interval.
|
|
45
51
|
|
|
46
52
|
"""
|
|
47
53
|
if client is None:
|
|
@@ -21,7 +21,8 @@ from modal_proto.modal_api_grpc import ModalClientModal
|
|
|
21
21
|
|
|
22
22
|
from ._load_context import LoadContext
|
|
23
23
|
from ._object import _Object, live_method, live_method_gen
|
|
24
|
-
from .
|
|
24
|
+
from ._output.pty import get_pty_info
|
|
25
|
+
from ._output.status import FunctionCreationStatus
|
|
25
26
|
from ._resolver import Resolver
|
|
26
27
|
from ._resources import convert_fn_config_to_resources_config
|
|
27
28
|
from ._runtime.execution_context import current_input_id, is_local
|
|
@@ -47,7 +48,6 @@ from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
|
47
48
|
from ._utils.function_utils import (
|
|
48
49
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
49
50
|
OUTPUTS_TIMEOUT,
|
|
50
|
-
FunctionCreationStatus,
|
|
51
51
|
FunctionInfo,
|
|
52
52
|
_create_input,
|
|
53
53
|
_process_result,
|
|
@@ -71,7 +71,7 @@ from .gpu import GPU_T, parse_gpu_config
|
|
|
71
71
|
from .image import _Image
|
|
72
72
|
from .mount import _get_client_mount, _Mount
|
|
73
73
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
|
74
|
-
from .output import
|
|
74
|
+
from .output import OutputManager
|
|
75
75
|
from .parallel_map import (
|
|
76
76
|
_experimental_spawn_map_async,
|
|
77
77
|
_experimental_spawn_map_sync,
|
|
@@ -922,7 +922,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
922
922
|
async def _load(
|
|
923
923
|
self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
924
924
|
):
|
|
925
|
-
with FunctionCreationStatus(
|
|
925
|
+
with FunctionCreationStatus(tag) as function_creation_status:
|
|
926
926
|
timeout_secs = timeout
|
|
927
927
|
|
|
928
928
|
if app and app.is_interactive and not is_builder_function:
|
|
@@ -1639,10 +1639,7 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1639
1639
|
raise InvalidError("A generator function cannot be called with `.map(...)`.")
|
|
1640
1640
|
|
|
1641
1641
|
assert self._function_name
|
|
1642
|
-
|
|
1643
|
-
count_update_callback = output_mgr.function_progress_callback(self._function_name, total=None)
|
|
1644
|
-
else:
|
|
1645
|
-
count_update_callback = None
|
|
1642
|
+
count_update_callback = OutputManager.get().function_progress_callback(self._function_name, total=None)
|
|
1646
1643
|
|
|
1647
1644
|
if self._input_plane_url:
|
|
1648
1645
|
async with aclosing(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
# Copyright Modal Labs 2022
|
|
2
|
+
"""Output management interface for Modal CLI.
|
|
3
|
+
|
|
4
|
+
This module defines the interface for output management and provides a no-op
|
|
5
|
+
implementation for when output is disabled. The rich-based implementation lives
|
|
6
|
+
in rich.py to avoid importing rich when it's not needed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import contextlib
|
|
11
|
+
import sys
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from collections.abc import Generator
|
|
14
|
+
from contextlib import AbstractContextManager
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
16
|
+
|
|
17
|
+
from modal._utils.shell_utils import write_to_fd
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from modal_proto import api_pb2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StatusContext(ABC):
|
|
24
|
+
"""Abstract base class for status context managers that support manual start/stop control."""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def start(self) -> None:
|
|
28
|
+
"""Start showing the status spinner."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def stop(self) -> None:
|
|
33
|
+
"""Stop showing the status spinner."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def update(self, status: str) -> None:
|
|
38
|
+
"""Update the status message."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def __enter__(self) -> "StatusContext": ...
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def __exit__(self, *args: Any) -> None: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class StatusRow(ABC):
|
|
49
|
+
"""Abstract base class describing a row in the object creation status tree."""
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def message(self, message: str) -> None:
|
|
53
|
+
"""Update the primary message shown for this row."""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def warn(self, warning: "api_pb2.Warning") -> None:
|
|
58
|
+
"""Append a warning message associated with this row."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def finish(self, message: str) -> None:
|
|
63
|
+
"""Mark the row as finished with the given message."""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DisabledStatusRow(StatusRow):
|
|
68
|
+
"""No-op StatusRow used when output is disabled."""
|
|
69
|
+
|
|
70
|
+
def message(self, message: str) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def warn(self, warning: "api_pb2.Warning") -> None:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def finish(self, message: str) -> None:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TransferProgressContext(ABC):
|
|
81
|
+
"""Abstract base class for transfer progress tracking context."""
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def progress(
|
|
85
|
+
self,
|
|
86
|
+
task_id: Any = None,
|
|
87
|
+
advance: Optional[float] = None,
|
|
88
|
+
name: Optional[str] = None,
|
|
89
|
+
size: Optional[float] = None,
|
|
90
|
+
reset: Optional[bool] = False,
|
|
91
|
+
complete: Optional[bool] = False,
|
|
92
|
+
) -> Any:
|
|
93
|
+
"""Update progress. Returns task_id when creating a new task."""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _DisabledTransferProgress(TransferProgressContext):
|
|
98
|
+
"""No-op transfer progress context for when output is disabled."""
|
|
99
|
+
|
|
100
|
+
def progress(
|
|
101
|
+
self,
|
|
102
|
+
task_id: Any = None,
|
|
103
|
+
advance: Optional[float] = None,
|
|
104
|
+
name: Optional[str] = None,
|
|
105
|
+
size: Optional[float] = None,
|
|
106
|
+
reset: Optional[bool] = False,
|
|
107
|
+
complete: Optional[bool] = False,
|
|
108
|
+
) -> None:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OutputManager(ABC):
|
|
113
|
+
"""Abstract base class defining the interface for output management.
|
|
114
|
+
|
|
115
|
+
This class allows for different implementations:
|
|
116
|
+
- RichOutputManager: Full rich-based terminal output with progress spinners, trees, etc.
|
|
117
|
+
- DisabledOutputManager: No-op implementation for when output is disabled.
|
|
118
|
+
|
|
119
|
+
Subclasses must implement all abstract methods. Use OutputManager.get() to get
|
|
120
|
+
the current output manager instance.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# Settings that can be modified at runtime - shared by all implementations
|
|
124
|
+
_quiet_mode: bool = False
|
|
125
|
+
_show_timestamps: bool = False
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def get(cls) -> "OutputManager":
|
|
129
|
+
"""Get the current output manager.
|
|
130
|
+
|
|
131
|
+
Returns a RichOutputManager when output is enabled, otherwise returns
|
|
132
|
+
a DisabledOutputManager that provides no-op implementations of all methods.
|
|
133
|
+
|
|
134
|
+
This allows code to call output methods without checking if output is enabled,
|
|
135
|
+
simplifying the calling code.
|
|
136
|
+
"""
|
|
137
|
+
return _current_output_manager
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def _set(cls, manager: "OutputManager") -> None:
|
|
141
|
+
"""Set the current output manager. Used internally by enable_output()."""
|
|
142
|
+
global _current_output_manager
|
|
143
|
+
_current_output_manager = manager
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def is_enabled(self) -> bool:
|
|
148
|
+
"""Whether output is currently being displayed somewhere.
|
|
149
|
+
|
|
150
|
+
Returns False if using DisabledOutputManager OR if quiet mode is active.
|
|
151
|
+
This reflects whether print(), status(), etc. will actually produce visible output.
|
|
152
|
+
"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def is_terminal(self) -> bool:
|
|
158
|
+
"""Whether the output is connected to a terminal (TTY)."""
|
|
159
|
+
...
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
@abstractmethod
|
|
163
|
+
def _show_image_logs(self) -> bool:
|
|
164
|
+
"""Whether to show image logs."""
|
|
165
|
+
...
|
|
166
|
+
|
|
167
|
+
@abstractmethod
|
|
168
|
+
@contextlib.contextmanager
|
|
169
|
+
def display_object_tree(self) -> Generator[None, None, None]:
|
|
170
|
+
"""Context manager that displays a tree of objects being created."""
|
|
171
|
+
...
|
|
172
|
+
|
|
173
|
+
@abstractmethod
|
|
174
|
+
def add_status_row(self) -> StatusRow:
|
|
175
|
+
"""Add a status row to the current object tree."""
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
@abstractmethod
|
|
179
|
+
def print(
|
|
180
|
+
self,
|
|
181
|
+
renderable: Any,
|
|
182
|
+
*,
|
|
183
|
+
stderr: bool = False,
|
|
184
|
+
highlight: bool = True,
|
|
185
|
+
style: Optional[str] = None,
|
|
186
|
+
sep: str = " ",
|
|
187
|
+
end: str = "\n",
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Print a renderable to the console.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
renderable: The content to print.
|
|
193
|
+
stderr: If True, print to stderr instead of stdout.
|
|
194
|
+
highlight: If True, apply syntax highlighting.
|
|
195
|
+
style: Optional Rich style string (e.g., "green", "bold cyan").
|
|
196
|
+
sep: The separator to use between items.
|
|
197
|
+
end: The string to use at the end of the output.
|
|
198
|
+
"""
|
|
199
|
+
...
|
|
200
|
+
|
|
201
|
+
@abstractmethod
|
|
202
|
+
def print_error(self, error_text: str) -> None:
|
|
203
|
+
"""Print an error message to stderr, ignoring quiet/disabled mode.
|
|
204
|
+
|
|
205
|
+
This method always prints the error message regardless of quiet mode
|
|
206
|
+
or whether output is disabled. It should be used for critical error
|
|
207
|
+
messages that must always be visible to the user.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
error_text: The error message text to display.
|
|
211
|
+
"""
|
|
212
|
+
...
|
|
213
|
+
|
|
214
|
+
@abstractmethod
|
|
215
|
+
def show_warning(
|
|
216
|
+
self,
|
|
217
|
+
warning: Warning,
|
|
218
|
+
category: type[Warning],
|
|
219
|
+
filename: str,
|
|
220
|
+
lineno: int,
|
|
221
|
+
base_showwarning: Callable[..., None],
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Display a warning message.
|
|
224
|
+
|
|
225
|
+
This method is called by the patched warnings.showwarning for all warnings.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
warning: The warning instance.
|
|
229
|
+
category: The warning category (class).
|
|
230
|
+
filename: The source file where the warning originated.
|
|
231
|
+
lineno: The line number where the warning originated.
|
|
232
|
+
base_showwarning: The original warnings.showwarning function to fall back to.
|
|
233
|
+
"""
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
@abstractmethod
|
|
237
|
+
def print_json(self, data: str) -> None:
|
|
238
|
+
"""Print JSON data with formatting."""
|
|
239
|
+
...
|
|
240
|
+
|
|
241
|
+
@abstractmethod
|
|
242
|
+
def status(self, message: str) -> "StatusContext":
|
|
243
|
+
"""Context manager that displays a status spinner with a message.
|
|
244
|
+
|
|
245
|
+
Returns a context manager that shows a spinner while active.
|
|
246
|
+
The returned object has start(), stop(), and update() methods for manual control.
|
|
247
|
+
"""
|
|
248
|
+
...
|
|
249
|
+
|
|
250
|
+
@abstractmethod
|
|
251
|
+
def make_live_spinner(self, message: str) -> AbstractContextManager[None]:
|
|
252
|
+
"""Context manager that shows a live spinner with a message.
|
|
253
|
+
|
|
254
|
+
This combines spinner creation and live display into a single interface,
|
|
255
|
+
hiding the implementation details of how spinners are rendered.
|
|
256
|
+
"""
|
|
257
|
+
...
|
|
258
|
+
|
|
259
|
+
@abstractmethod
|
|
260
|
+
def enable_image_logs(self) -> None:
|
|
261
|
+
"""Enable showing image logs."""
|
|
262
|
+
...
|
|
263
|
+
|
|
264
|
+
@abstractmethod
|
|
265
|
+
def show_status_spinner(self, status_text: str = "Running app...") -> AbstractContextManager[None]:
|
|
266
|
+
"""Context manager that shows a status spinner.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
status_text: The text to display next to the spinner. Defaults to "Running app...".
|
|
270
|
+
"""
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
@abstractmethod
|
|
274
|
+
def update_app_page_url(self, app_page_url: str) -> None:
|
|
275
|
+
"""Update the app page URL for display."""
|
|
276
|
+
...
|
|
277
|
+
|
|
278
|
+
@abstractmethod
|
|
279
|
+
def function_progress_callback(self, tag: str, total: Optional[int]) -> Callable[[int, int], None]:
|
|
280
|
+
"""Get a callback for updating function progress."""
|
|
281
|
+
...
|
|
282
|
+
|
|
283
|
+
@abstractmethod
|
|
284
|
+
def update_task_state(self, task_id: str, state: int) -> None:
|
|
285
|
+
"""Update the state of a task."""
|
|
286
|
+
...
|
|
287
|
+
|
|
288
|
+
@abstractmethod
|
|
289
|
+
def update_snapshot_progress(self, image_id: str, task_progress: "api_pb2.TaskProgress") -> None:
|
|
290
|
+
"""Update snapshot upload progress."""
|
|
291
|
+
...
|
|
292
|
+
|
|
293
|
+
@abstractmethod
|
|
294
|
+
def update_queueing_progress(
|
|
295
|
+
self, *, function_id: str, completed: int, total: Optional[int], description: Optional[str]
|
|
296
|
+
) -> None:
|
|
297
|
+
"""Update function queueing progress."""
|
|
298
|
+
...
|
|
299
|
+
|
|
300
|
+
@abstractmethod
|
|
301
|
+
async def put_log_content(self, log: "api_pb2.TaskLogs") -> None:
|
|
302
|
+
"""Process and display log content.
|
|
303
|
+
|
|
304
|
+
Note: In RichOutputManager, log output is always displayed even when quiet mode
|
|
305
|
+
is enabled. This is intentional - quiet mode suppresses progress indicators and
|
|
306
|
+
status messages, but not actual log output from running functions/images.
|
|
307
|
+
In contrast, DisabledOutputManager suppresses all output including logs.
|
|
308
|
+
"""
|
|
309
|
+
...
|
|
310
|
+
|
|
311
|
+
async def put_pty_content(self, log: "api_pb2.TaskLogs") -> None:
|
|
312
|
+
"""Write PTY content to stdout for interactive terminal sessions.
|
|
313
|
+
|
|
314
|
+
This handles raw PTY output from interactive shells/debuggers, writing
|
|
315
|
+
directly to stdout with proper handling of non-blocking writes.
|
|
316
|
+
|
|
317
|
+
Note: This method is implemented in the base class (not abstract) because
|
|
318
|
+
interactive/raw terminal content is always forwarded to stdout regardless
|
|
319
|
+
of whether output is enabled or quiet mode is active. This ensures that
|
|
320
|
+
interactive debugging sessions (e.g., pdb breakpoints) work correctly.
|
|
321
|
+
"""
|
|
322
|
+
stdout = sys.stdout
|
|
323
|
+
if hasattr(stdout, "buffer"):
|
|
324
|
+
data = log.data.encode("utf-8")
|
|
325
|
+
fd = None
|
|
326
|
+
try:
|
|
327
|
+
if hasattr(stdout, "fileno"):
|
|
328
|
+
fd = stdout.fileno()
|
|
329
|
+
except Exception:
|
|
330
|
+
fd = None
|
|
331
|
+
|
|
332
|
+
if fd is not None:
|
|
333
|
+
await write_to_fd(fd, data)
|
|
334
|
+
else:
|
|
335
|
+
# For streams without fileno(), use the normal write/flush path.
|
|
336
|
+
written = 0
|
|
337
|
+
n_retries = 0
|
|
338
|
+
while written < len(data):
|
|
339
|
+
try:
|
|
340
|
+
written += stdout.buffer.write(data[written:])
|
|
341
|
+
stdout.flush()
|
|
342
|
+
except BlockingIOError:
|
|
343
|
+
if n_retries >= 5:
|
|
344
|
+
raise
|
|
345
|
+
n_retries += 1
|
|
346
|
+
await asyncio.sleep(0.1)
|
|
347
|
+
else:
|
|
348
|
+
# stdout isn't always buffered (e.g. %%capture in Jupyter notebooks redirects it to io.StringIO)
|
|
349
|
+
stdout.write(log.data)
|
|
350
|
+
stdout.flush()
|
|
351
|
+
|
|
352
|
+
@abstractmethod
|
|
353
|
+
def flush_lines(self) -> None:
|
|
354
|
+
"""Flush any buffered output."""
|
|
355
|
+
...
|
|
356
|
+
|
|
357
|
+
@abstractmethod
|
|
358
|
+
def transfer_progress(self, type: str) -> AbstractContextManager["TransferProgressContext"]:
|
|
359
|
+
"""Context manager for tracking file transfer progress.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
type: Either "download" or "upload".
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
A context manager that yields a TransferProgressContext with a progress() method.
|
|
366
|
+
"""
|
|
367
|
+
...
|
|
368
|
+
|
|
369
|
+
@abstractmethod
|
|
370
|
+
def stop_status_spinner(self) -> None:
|
|
371
|
+
"""Stop the status spinner if it's running.
|
|
372
|
+
|
|
373
|
+
This is used to cleanly stop the spinner before entering PTY mode or
|
|
374
|
+
other situations where the spinner would interfere with output.
|
|
375
|
+
"""
|
|
376
|
+
...
|
|
377
|
+
|
|
378
|
+
def set_quiet_mode(self, quiet: bool) -> None:
|
|
379
|
+
"""Enable or disable quiet mode.
|
|
380
|
+
|
|
381
|
+
When quiet mode is enabled:
|
|
382
|
+
- All print() output is suppressed, including stderr
|
|
383
|
+
- Progress indicators (spinners, progress bars) are suppressed
|
|
384
|
+
- Warnings (via show_warning) are still displayed
|
|
385
|
+
- Log output (via put_log_content) is still displayed
|
|
386
|
+
- Interactive/raw PTY content (via put_pty_content) is still forwarded to stdout
|
|
387
|
+
- Error output (via print_error) is still displayed
|
|
388
|
+
|
|
389
|
+
Note: This differs from DisabledOutputManager, which suppresses all output
|
|
390
|
+
including logs. Quiet mode is meant for reducing noise while still showing
|
|
391
|
+
relevant content.
|
|
392
|
+
"""
|
|
393
|
+
self._quiet_mode = quiet
|
|
394
|
+
|
|
395
|
+
def set_timestamps(self, show: bool) -> None:
|
|
396
|
+
"""Enable or disable timestamp display in log output."""
|
|
397
|
+
self._show_timestamps = show
|
|
398
|
+
|
|
399
|
+
@abstractmethod
|
|
400
|
+
def step_completed(self, message: str) -> None:
|
|
401
|
+
"""Print a step completed message with appropriate formatting."""
|
|
402
|
+
...
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class _DisabledStatus(StatusContext):
|
|
406
|
+
"""No-op status context manager for when output is disabled."""
|
|
407
|
+
|
|
408
|
+
def start(self) -> None:
|
|
409
|
+
pass
|
|
410
|
+
|
|
411
|
+
def stop(self) -> None:
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
def update(self, status: str) -> None:
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
def __enter__(self) -> "_DisabledStatus":
|
|
418
|
+
return self
|
|
419
|
+
|
|
420
|
+
def __exit__(self, *args: Any) -> None:
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class DisabledOutputManager(OutputManager):
|
|
425
|
+
"""No-op implementation of OutputManager for when output is disabled.
|
|
426
|
+
|
|
427
|
+
Most methods are no-ops that do nothing, allowing code to call output methods
|
|
428
|
+
without checking if the output manager exists.
|
|
429
|
+
|
|
430
|
+
Exceptions:
|
|
431
|
+
- Interactive/raw PTY content (via put_pty_content) is always forwarded to stdout
|
|
432
|
+
to ensure interactive debugging sessions work correctly.
|
|
433
|
+
- Warnings (via show_warning) fall back to Python's default warning display.
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def is_enabled(self) -> bool:
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def is_terminal(self) -> bool:
|
|
442
|
+
return sys.stdout.isatty()
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def _show_image_logs(self) -> bool:
|
|
446
|
+
return True # Always "show" logs when disabled (don't filter them)
|
|
447
|
+
|
|
448
|
+
@contextlib.contextmanager
|
|
449
|
+
def display_object_tree(self) -> Generator[None, None, None]:
|
|
450
|
+
yield
|
|
451
|
+
|
|
452
|
+
def add_status_row(self) -> StatusRow:
|
|
453
|
+
return DisabledStatusRow()
|
|
454
|
+
|
|
455
|
+
def print(
|
|
456
|
+
self,
|
|
457
|
+
renderable: Any,
|
|
458
|
+
*,
|
|
459
|
+
stderr: bool = False,
|
|
460
|
+
highlight: bool = True,
|
|
461
|
+
style: Optional[str] = None,
|
|
462
|
+
sep: str = " ",
|
|
463
|
+
end: str = "\n",
|
|
464
|
+
) -> None:
|
|
465
|
+
pass
|
|
466
|
+
|
|
467
|
+
def print_error(self, error_text: str) -> None:
|
|
468
|
+
# Print error to stderr without any formatting (Rich is not available)
|
|
469
|
+
sys.stderr.write(error_text + "\n")
|
|
470
|
+
|
|
471
|
+
def show_warning(
|
|
472
|
+
self,
|
|
473
|
+
warning: Warning,
|
|
474
|
+
category: type[Warning],
|
|
475
|
+
filename: str,
|
|
476
|
+
lineno: int,
|
|
477
|
+
base_showwarning: Callable[..., None],
|
|
478
|
+
) -> None:
|
|
479
|
+
# When output is disabled, fall back to the default warning display
|
|
480
|
+
base_showwarning(warning, category, filename, lineno, file=None, line=None)
|
|
481
|
+
|
|
482
|
+
def print_json(self, data: str) -> None:
|
|
483
|
+
pass
|
|
484
|
+
|
|
485
|
+
def status(self, message: str) -> StatusContext:
|
|
486
|
+
return _DisabledStatus()
|
|
487
|
+
|
|
488
|
+
@contextlib.contextmanager
|
|
489
|
+
def make_live_spinner(self, message: str) -> Generator[None, None, None]:
|
|
490
|
+
yield
|
|
491
|
+
|
|
492
|
+
def enable_image_logs(self) -> None:
|
|
493
|
+
pass
|
|
494
|
+
|
|
495
|
+
@contextlib.contextmanager
|
|
496
|
+
def show_status_spinner(self, status_text: str = "Running app...") -> Generator[None, None, None]:
|
|
497
|
+
yield
|
|
498
|
+
|
|
499
|
+
def update_app_page_url(self, app_page_url: str) -> None:
|
|
500
|
+
pass
|
|
501
|
+
|
|
502
|
+
def function_progress_callback(self, tag: str, total: Optional[int]) -> Callable[[int, int], None]:
|
|
503
|
+
def noop(completed: int, total: int) -> None:
|
|
504
|
+
pass
|
|
505
|
+
|
|
506
|
+
return noop
|
|
507
|
+
|
|
508
|
+
def update_task_state(self, task_id: str, state: int) -> None:
|
|
509
|
+
pass
|
|
510
|
+
|
|
511
|
+
def update_snapshot_progress(self, image_id: str, task_progress: "api_pb2.TaskProgress") -> None:
|
|
512
|
+
pass
|
|
513
|
+
|
|
514
|
+
def update_queueing_progress(
|
|
515
|
+
self, *, function_id: str, completed: int, total: Optional[int], description: Optional[str]
|
|
516
|
+
) -> None:
|
|
517
|
+
pass
|
|
518
|
+
|
|
519
|
+
async def put_log_content(self, log: "api_pb2.TaskLogs") -> None:
|
|
520
|
+
pass
|
|
521
|
+
|
|
522
|
+
def flush_lines(self) -> None:
|
|
523
|
+
pass
|
|
524
|
+
|
|
525
|
+
@contextlib.contextmanager
|
|
526
|
+
def transfer_progress(self, type: str) -> Generator[TransferProgressContext, None, None]:
|
|
527
|
+
"""No-op transfer progress context manager."""
|
|
528
|
+
yield _DisabledTransferProgress()
|
|
529
|
+
|
|
530
|
+
def stop_status_spinner(self) -> None:
|
|
531
|
+
pass
|
|
532
|
+
|
|
533
|
+
def step_completed(self, message: str) -> None:
|
|
534
|
+
pass
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# Singleton instance of the disabled output manager
|
|
538
|
+
_DISABLED_OUTPUT_MANAGER = DisabledOutputManager()
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
# Module-level state for output management
|
|
542
|
+
_current_output_manager: OutputManager = _DISABLED_OUTPUT_MANAGER
|