modAL 1.2.7.dev9__tar.gz → 1.2.7.dev11__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.2.7.dev9 → modal-1.2.7.dev11}/PKG-INFO +1 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_container_entrypoint.py +0 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_grpc_client.py +1 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_output.py +8 -9
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/function_utils.py +8 -9
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/grpc_utils.py +0 -2
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/package_utils.py +0 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/task_command_router_client.py +14 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/client.pyi +2 -2
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/dict.py +12 -6
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/exception.py +5 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/file_io.py +10 -13
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/image.py +12 -6
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/io_streams.py +10 -13
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/mount.py +2 -3
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/queue.py +11 -5
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/sandbox.py +44 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/sandbox.pyi +39 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_version/__init__.py +1 -1
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/LICENSE +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/README.md +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/__main__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_billing.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_clustered_functions.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_functions.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_ipython.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_load_context.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_location.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_object.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_partial_function.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_pty.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_resolver.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_resources.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_serialization.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_traceback.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_tunnel.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_tunnel.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_type_manager.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/logger.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/_watcher.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/app.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/app.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/billing.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/README.md +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/builder/base-images.json +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/call_graph.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/_download.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/_traceback.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/app.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/cluster.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/config.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/container.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/dict.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/entry_point.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/environment.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/import_refs.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/launch.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/profile.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/queues.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/run.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/secret.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/shell.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/token.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/utils.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cli/volume.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/client.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cls.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/cls.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/config.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/container_process.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/container_process.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/dict.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/environments.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/environments.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/flash.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/experimental/ipython.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/file_io.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/functions.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/functions.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/gpu.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/image.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/io_streams.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/mount.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/network_file_system.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/network_file_system.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/object.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/object.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/output.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/parallel_map.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/parallel_map.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/partial_function.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/partial_function.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/proxy.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/proxy.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/py.typed +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/queue.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/retries.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/runner.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/runner.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/running_app.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/schedule.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/scheduler_placement.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/secret.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/secret.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/serving.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/serving.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/snapshot.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/snapshot.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/stream_type.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/token_flow.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/token_flow.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/volume.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal/volume.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/__init__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api.proto +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/py.typed +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/modal_version/__main__.py +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/pyproject.toml +0 -0
- {modal-1.2.7.dev9 → modal-1.2.7.dev11}/setup.cfg +0 -0
|
@@ -38,7 +38,7 @@ _STATUS_TO_EXCEPTION: dict[Status, type[exception._GRPCErrorWrapper]] = {
|
|
|
38
38
|
Status.UNIMPLEMENTED: exception.UnimplementedError,
|
|
39
39
|
Status.INTERNAL: exception.InternalError,
|
|
40
40
|
Status.UNAVAILABLE: exception.ServiceError,
|
|
41
|
-
Status.DATA_LOSS: exception.
|
|
41
|
+
Status.DATA_LOSS: exception.DataLossError,
|
|
42
42
|
Status.UNAUTHENTICATED: exception.AuthError,
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -12,7 +12,7 @@ from collections.abc import Generator
|
|
|
12
12
|
from datetime import timedelta
|
|
13
13
|
from typing import Callable, ClassVar
|
|
14
14
|
|
|
15
|
-
from grpclib.exceptions import
|
|
15
|
+
from grpclib.exceptions import StreamTerminatedError
|
|
16
16
|
from rich.console import Console, Group, RenderableType
|
|
17
17
|
from rich.live import Live
|
|
18
18
|
from rich.panel import Panel
|
|
@@ -34,10 +34,11 @@ from rich.text import Text
|
|
|
34
34
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
35
35
|
from modal_proto import api_pb2
|
|
36
36
|
|
|
37
|
-
from ._utils.grpc_utils import
|
|
37
|
+
from ._utils.grpc_utils import Retry
|
|
38
38
|
from ._utils.shell_utils import stream_from_stdin, write_to_fd
|
|
39
39
|
from .client import _Client
|
|
40
40
|
from .config import logger
|
|
41
|
+
from .exception import InternalError, ServiceError
|
|
41
42
|
|
|
42
43
|
if platform.system() == "Windows":
|
|
43
44
|
default_spinner = "line"
|
|
@@ -644,13 +645,11 @@ async def get_app_logs_loop(
|
|
|
644
645
|
while True:
|
|
645
646
|
try:
|
|
646
647
|
await _get_logs()
|
|
647
|
-
except (
|
|
648
|
-
if isinstance(exc,
|
|
649
|
-
if
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
logger.debug("Log fetching timed out. Retrying ...")
|
|
653
|
-
continue
|
|
648
|
+
except (ServiceError, InternalError, StreamTerminatedError, socket.gaierror, AttributeError) as exc:
|
|
649
|
+
if isinstance(exc, (ServiceError, InternalError)):
|
|
650
|
+
# Try again if we had a temporary connection drop, for example if computer went to sleep.
|
|
651
|
+
logger.debug("Log fetching timed out. Retrying ...")
|
|
652
|
+
continue
|
|
654
653
|
elif isinstance(exc, StreamTerminatedError):
|
|
655
654
|
logger.debug("Stream closed. Retrying ...")
|
|
656
655
|
continue
|
|
@@ -8,7 +8,6 @@ from enum import Enum
|
|
|
8
8
|
from pathlib import Path, PurePosixPath
|
|
9
9
|
from typing import Any, Callable, Literal, Optional
|
|
10
10
|
|
|
11
|
-
from grpclib import GRPCError
|
|
12
11
|
from grpclib.exceptions import StreamTerminatedError
|
|
13
12
|
|
|
14
13
|
import modal_proto
|
|
@@ -29,9 +28,11 @@ from ..exception import (
|
|
|
29
28
|
DeserializationError,
|
|
30
29
|
ExecutionError,
|
|
31
30
|
FunctionTimeoutError,
|
|
31
|
+
InternalError,
|
|
32
32
|
InternalFailure,
|
|
33
33
|
InvalidError,
|
|
34
34
|
RemoteError,
|
|
35
|
+
ServiceError,
|
|
35
36
|
)
|
|
36
37
|
from ..mount import ROOT_DIR, _is_modal_path, _Mount
|
|
37
38
|
from .blob_utils import (
|
|
@@ -39,7 +40,6 @@ from .blob_utils import (
|
|
|
39
40
|
blob_download,
|
|
40
41
|
blob_upload_with_r2_failure_info,
|
|
41
42
|
)
|
|
42
|
-
from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
43
43
|
|
|
44
44
|
if typing.TYPE_CHECKING:
|
|
45
45
|
import modal._functions
|
|
@@ -437,15 +437,14 @@ async def _stream_function_call_data(
|
|
|
437
437
|
|
|
438
438
|
last_index = chunk.index
|
|
439
439
|
yield message
|
|
440
|
-
except (
|
|
440
|
+
except (ServiceError, InternalError, StreamTerminatedError) as exc:
|
|
441
441
|
if retries_remaining > 0:
|
|
442
442
|
retries_remaining -= 1
|
|
443
|
-
if isinstance(exc,
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
continue
|
|
443
|
+
if isinstance(exc, (ServiceError, InternalError)):
|
|
444
|
+
logger.debug(f"{variant} stream retrying with delay {delay_ms}ms due to {exc}")
|
|
445
|
+
await asyncio.sleep(delay_ms / 1000)
|
|
446
|
+
delay_ms = min(1000, delay_ms * 10)
|
|
447
|
+
continue
|
|
449
448
|
elif isinstance(exc, StreamTerminatedError):
|
|
450
449
|
continue
|
|
451
450
|
raise
|
|
@@ -16,8 +16,6 @@ from typing import Any, Optional, Sequence, TypeVar
|
|
|
16
16
|
import grpclib.client
|
|
17
17
|
import grpclib.config
|
|
18
18
|
import grpclib.events
|
|
19
|
-
import grpclib.protocol
|
|
20
|
-
import grpclib.stream
|
|
21
19
|
from google.protobuf.message import Message
|
|
22
20
|
from google.protobuf.symbol_database import SymbolDatabase
|
|
23
21
|
from grpclib import GRPCError, Status
|
|
@@ -521,3 +521,17 @@ class TaskCommandRouterClient:
|
|
|
521
521
|
await sleep_and_update_delay_and_num_retries_remaining(e)
|
|
522
522
|
else:
|
|
523
523
|
raise ConnectionError(str(e))
|
|
524
|
+
|
|
525
|
+
async def mount_image(self, request: sr_pb2.TaskMountDirectoryRequest):
|
|
526
|
+
with grpc_error_converter():
|
|
527
|
+
return await call_with_retries_on_transient_errors(
|
|
528
|
+
lambda: self._call_with_auth_retry(self._stub.TaskMountDirectory, request)
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
async def snapshot_directory(
|
|
532
|
+
self, request: sr_pb2.TaskSnapshotDirectoryRequest
|
|
533
|
+
) -> sr_pb2.TaskSnapshotDirectoryResponse:
|
|
534
|
+
with grpc_error_converter():
|
|
535
|
+
return await call_with_retries_on_transient_errors(
|
|
536
|
+
lambda: self._call_with_auth_retry(self._stub.TaskSnapshotDirectory, request)
|
|
537
|
+
)
|
|
@@ -32,7 +32,7 @@ class _Client:
|
|
|
32
32
|
server_url: str,
|
|
33
33
|
client_type: int,
|
|
34
34
|
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.7.
|
|
35
|
+
version: str = "1.2.7.dev11",
|
|
36
36
|
):
|
|
37
37
|
"""mdmd:hidden
|
|
38
38
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -163,7 +163,7 @@ class Client:
|
|
|
163
163
|
server_url: str,
|
|
164
164
|
client_type: int,
|
|
165
165
|
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.2.7.
|
|
166
|
+
version: str = "1.2.7.dev11",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -5,7 +5,6 @@ from datetime import datetime
|
|
|
5
5
|
from typing import Any, Optional, Union
|
|
6
6
|
|
|
7
7
|
from google.protobuf.message import Message
|
|
8
|
-
from grpclib import GRPCError
|
|
9
8
|
from synchronicity import classproperty
|
|
10
9
|
from synchronicity.async_wrap import asynccontextmanager
|
|
11
10
|
|
|
@@ -28,7 +27,14 @@ from ._utils.name_utils import check_object_name
|
|
|
28
27
|
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
|
29
28
|
from .client import _Client
|
|
30
29
|
from .config import logger
|
|
31
|
-
from .exception import
|
|
30
|
+
from .exception import (
|
|
31
|
+
AlreadyExistsError,
|
|
32
|
+
DeserializationError,
|
|
33
|
+
Error,
|
|
34
|
+
InvalidError,
|
|
35
|
+
NotFoundError,
|
|
36
|
+
RequestSizeError,
|
|
37
|
+
)
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
class _NoDefaultSentinel:
|
|
@@ -502,8 +508,8 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
502
508
|
req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized)
|
|
503
509
|
try:
|
|
504
510
|
await self._client.stub.DictUpdate(req)
|
|
505
|
-
except
|
|
506
|
-
if "status = '413'" in exc
|
|
511
|
+
except Error as exc:
|
|
512
|
+
if "status = '413'" in str(exc):
|
|
507
513
|
raise RequestSizeError("Dict.update request is too large") from exc
|
|
508
514
|
else:
|
|
509
515
|
raise exc
|
|
@@ -521,8 +527,8 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
521
527
|
try:
|
|
522
528
|
resp = await self._client.stub.DictUpdate(req)
|
|
523
529
|
return resp.created
|
|
524
|
-
except
|
|
525
|
-
if "status = '413'" in exc
|
|
530
|
+
except Error as exc:
|
|
531
|
+
if "status = '413'" in str(exc):
|
|
526
532
|
raise RequestSizeError("Dict.put request is too large") from exc
|
|
527
533
|
else:
|
|
528
534
|
raise exc
|
|
@@ -29,7 +29,7 @@ OUT_OF_RANGE -> InvalidError
|
|
|
29
29
|
UNIMPLEMENTED -> UnimplementedError
|
|
30
30
|
INTERNAL -> InternalError
|
|
31
31
|
UNAVAILABLE -> ServiceError
|
|
32
|
-
DATA_LOSS ->
|
|
32
|
+
DATA_LOSS -> DataLossError
|
|
33
33
|
UNAUTHENTICATED -> AuthError
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -150,6 +150,10 @@ class ConflictError(InvalidError, _GRPCErrorWrapper):
|
|
|
150
150
|
"""Raised when a resource conflict occurs between the request and current system state."""
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
class DataLossError(Error, _GRPCErrorWrapper):
|
|
154
|
+
"""Raised when data is lost or corrupted."""
|
|
155
|
+
|
|
156
|
+
|
|
153
157
|
class NotFoundError(Error, _GRPCErrorWrapper):
|
|
154
158
|
"""Raised when a requested resource was not found."""
|
|
155
159
|
|
|
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
12
|
|
|
13
|
-
from grpclib.exceptions import
|
|
13
|
+
from grpclib.exceptions import StreamTerminatedError
|
|
14
14
|
|
|
15
15
|
from modal._utils.async_utils import TaskContext
|
|
16
16
|
from modal.exception import ClientClosed
|
|
@@ -18,9 +18,8 @@ from modal_proto import api_pb2
|
|
|
18
18
|
|
|
19
19
|
from ._utils.async_utils import synchronize_api
|
|
20
20
|
from ._utils.deprecation import deprecation_error
|
|
21
|
-
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
22
21
|
from .client import _Client
|
|
23
|
-
from .exception import FilesystemExecutionError
|
|
22
|
+
from .exception import FilesystemExecutionError, InternalError, ServiceError
|
|
24
23
|
|
|
25
24
|
WRITE_CHUNK_SIZE = 16 * 1024 * 1024 # 16 MiB
|
|
26
25
|
WRITE_FILE_SIZE_LIMIT = 1024 * 1024 * 1024 # 1 GiB
|
|
@@ -166,13 +165,12 @@ class _FileIO(Generic[T]):
|
|
|
166
165
|
completed = True
|
|
167
166
|
break
|
|
168
167
|
|
|
169
|
-
except (
|
|
168
|
+
except (ServiceError, InternalError, StreamTerminatedError, ClientClosed) as exc:
|
|
170
169
|
if retries_remaining > 0:
|
|
171
170
|
retries_remaining -= 1
|
|
172
|
-
if isinstance(exc,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
continue
|
|
171
|
+
if isinstance(exc, (ServiceError, InternalError)):
|
|
172
|
+
await asyncio.sleep(1.0)
|
|
173
|
+
continue
|
|
176
174
|
elif isinstance(exc, StreamTerminatedError):
|
|
177
175
|
continue
|
|
178
176
|
elif isinstance(exc, ClientClosed):
|
|
@@ -202,13 +200,12 @@ class _FileIO(Generic[T]):
|
|
|
202
200
|
if isinstance(data, Exception):
|
|
203
201
|
raise data
|
|
204
202
|
output_buffer.write(data)
|
|
205
|
-
except (
|
|
203
|
+
except (ServiceError, InternalError, StreamTerminatedError) as exc:
|
|
206
204
|
if retries_remaining > 0:
|
|
207
205
|
retries_remaining -= 1
|
|
208
|
-
if isinstance(exc,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
continue
|
|
206
|
+
if isinstance(exc, (ServiceError, InternalError)):
|
|
207
|
+
await asyncio.sleep(1.0)
|
|
208
|
+
continue
|
|
212
209
|
elif isinstance(exc, StreamTerminatedError):
|
|
213
210
|
continue
|
|
214
211
|
raise
|
|
@@ -22,7 +22,7 @@ from typing import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
from google.protobuf.message import Message
|
|
25
|
-
from grpclib.exceptions import
|
|
25
|
+
from grpclib.exceptions import StreamTerminatedError
|
|
26
26
|
from typing_extensions import Self
|
|
27
27
|
|
|
28
28
|
from modal._serialization import serialize_data_format
|
|
@@ -39,13 +39,20 @@ from ._utils.docker_utils import (
|
|
|
39
39
|
find_dockerignore_file,
|
|
40
40
|
)
|
|
41
41
|
from ._utils.function_utils import FunctionInfo
|
|
42
|
-
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
43
42
|
from ._utils.mount_utils import validate_only_modal_volumes
|
|
44
43
|
from .client import _Client
|
|
45
44
|
from .cloud_bucket_mount import _CloudBucketMount
|
|
46
45
|
from .config import config, logger, user_config_path
|
|
47
46
|
from .environments import _get_environment_cached
|
|
48
|
-
from .exception import
|
|
47
|
+
from .exception import (
|
|
48
|
+
ExecutionError,
|
|
49
|
+
InternalError,
|
|
50
|
+
InvalidError,
|
|
51
|
+
NotFoundError,
|
|
52
|
+
RemoteError,
|
|
53
|
+
ServiceError,
|
|
54
|
+
VersionError,
|
|
55
|
+
)
|
|
49
56
|
from .file_pattern_matcher import NON_PYTHON_FILES, FilePatternMatcher, _ignore_fn
|
|
50
57
|
from .gpu import GPU_T, parse_gpu_config
|
|
51
58
|
from .mount import _Mount, python_standalone_mount_name
|
|
@@ -374,9 +381,7 @@ async def _image_await_build_result(image_id: str, client: _Client) -> api_pb2.I
|
|
|
374
381
|
while result_response is None:
|
|
375
382
|
try:
|
|
376
383
|
await join()
|
|
377
|
-
except (
|
|
378
|
-
if isinstance(exc, GRPCError) and exc.status not in RETRYABLE_GRPC_STATUS_CODES:
|
|
379
|
-
raise exc
|
|
384
|
+
except (ServiceError, InternalError, StreamTerminatedError) as exc:
|
|
380
385
|
retry_count += 1
|
|
381
386
|
if retry_count >= 3:
|
|
382
387
|
raise exc
|
|
@@ -874,6 +879,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
874
879
|
|
|
875
880
|
rep = f"Image.from_id({image_id!r})"
|
|
876
881
|
obj = _Image._from_loader(_load, rep, load_context_overrides=LoadContext(client=client))
|
|
882
|
+
obj._object_id = image_id
|
|
877
883
|
|
|
878
884
|
return obj
|
|
879
885
|
|
|
@@ -18,17 +18,16 @@ from typing import (
|
|
|
18
18
|
cast,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
from grpclib.exceptions import
|
|
21
|
+
from grpclib.exceptions import StreamTerminatedError
|
|
22
22
|
|
|
23
23
|
from modal.exception import ClientClosed, ExecTimeoutError, InvalidError
|
|
24
24
|
from modal_proto import api_pb2
|
|
25
25
|
|
|
26
26
|
from ._utils.async_utils import aclosing, synchronize_api, synchronizer
|
|
27
|
-
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
28
27
|
from ._utils.task_command_router_client import TaskCommandRouterClient
|
|
29
28
|
from .client import _Client
|
|
30
29
|
from .config import logger
|
|
31
|
-
from .exception import ConflictError
|
|
30
|
+
from .exception import ConflictError, InternalError, ServiceError
|
|
32
31
|
from .stream_type import StreamType
|
|
33
32
|
|
|
34
33
|
if TYPE_CHECKING:
|
|
@@ -193,13 +192,12 @@ class _StreamReaderThroughServer(Generic[T]):
|
|
|
193
192
|
else:
|
|
194
193
|
last_index = batch_index
|
|
195
194
|
|
|
196
|
-
except (
|
|
195
|
+
except (ServiceError, InternalError, StreamTerminatedError, ClientClosed) as exc:
|
|
197
196
|
if retries_remaining > 0:
|
|
198
197
|
retries_remaining -= 1
|
|
199
|
-
if isinstance(exc,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
continue
|
|
198
|
+
if isinstance(exc, (ServiceError, InternalError)):
|
|
199
|
+
await asyncio.sleep(1.0)
|
|
200
|
+
continue
|
|
203
201
|
elif isinstance(exc, StreamTerminatedError):
|
|
204
202
|
continue
|
|
205
203
|
elif isinstance(exc, ClientClosed):
|
|
@@ -270,13 +268,12 @@ class _StreamReaderThroughServer(Generic[T]):
|
|
|
270
268
|
|
|
271
269
|
yield message
|
|
272
270
|
|
|
273
|
-
except (
|
|
271
|
+
except (ServiceError, InternalError, StreamTerminatedError) as exc:
|
|
274
272
|
if retries_remaining > 0:
|
|
275
273
|
retries_remaining -= 1
|
|
276
|
-
if isinstance(exc,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
continue
|
|
274
|
+
if isinstance(exc, (ServiceError, InternalError)):
|
|
275
|
+
await asyncio.sleep(1.0)
|
|
276
|
+
continue
|
|
280
277
|
elif isinstance(exc, StreamTerminatedError):
|
|
281
278
|
continue
|
|
282
279
|
raise
|
|
@@ -13,7 +13,6 @@ from pathlib import Path, PurePosixPath
|
|
|
13
13
|
from typing import Callable, Optional, Sequence, Union
|
|
14
14
|
|
|
15
15
|
from google.protobuf.message import Message
|
|
16
|
-
from grpclib import GRPCError
|
|
17
16
|
|
|
18
17
|
import modal.exception
|
|
19
18
|
import modal.file_pattern_matcher
|
|
@@ -838,8 +837,8 @@ async def _create_single_client_dependency_mount(
|
|
|
838
837
|
client=client,
|
|
839
838
|
)
|
|
840
839
|
print(f"✅ Deployed mount {mount_name} to global namespace.") # noqa: T201
|
|
841
|
-
except
|
|
842
|
-
print(f"⚠️ Mount creation failed with {e.
|
|
840
|
+
except modal.exception.Error as e:
|
|
841
|
+
print(f"⚠️ Mount creation failed with {type(e).__name__}: {e}") # noqa: T201
|
|
843
842
|
else:
|
|
844
843
|
print(f"Dry run - skipping deployment of mount {mount_name}") # noqa: T201
|
|
845
844
|
|
|
@@ -30,7 +30,7 @@ from ._utils.grpc_utils import Retry
|
|
|
30
30
|
from ._utils.name_utils import check_object_name
|
|
31
31
|
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
|
32
32
|
from .client import _Client
|
|
33
|
-
from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError, ResourceExhaustedError
|
|
33
|
+
from .exception import AlreadyExistsError, Error, InvalidError, NotFoundError, RequestSizeError, ResourceExhaustedError
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
@dataclass
|
|
@@ -595,11 +595,14 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
595
595
|
total_timeout=timeout,
|
|
596
596
|
),
|
|
597
597
|
)
|
|
598
|
-
except
|
|
598
|
+
except Error as exc:
|
|
599
599
|
if "status = '413'" in str(exc):
|
|
600
600
|
method = "put_many" if len(vs) > 1 else "put"
|
|
601
601
|
raise RequestSizeError(f"Queue.{method} request is too large") from exc
|
|
602
|
-
|
|
602
|
+
elif isinstance(exc, ResourceExhaustedError):
|
|
603
|
+
raise queue.Full(str(exc))
|
|
604
|
+
else:
|
|
605
|
+
raise exc
|
|
603
606
|
|
|
604
607
|
async def _put_many_nonblocking(self, partition: Optional[str], partition_ttl: int, vs: list[Any]):
|
|
605
608
|
vs_encoded = [serialize(v) for v in vs]
|
|
@@ -611,11 +614,14 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
611
614
|
)
|
|
612
615
|
try:
|
|
613
616
|
await self._client.stub.QueuePut(request)
|
|
614
|
-
except
|
|
617
|
+
except Error as exc:
|
|
615
618
|
if "status = '413'" in str(exc):
|
|
616
619
|
method = "put_many" if len(vs) > 1 else "put"
|
|
617
620
|
raise RequestSizeError(f"Queue.{method} request is too large") from exc
|
|
618
|
-
|
|
621
|
+
elif isinstance(exc, ResourceExhaustedError):
|
|
622
|
+
raise queue.Full(str(exc))
|
|
623
|
+
else:
|
|
624
|
+
raise exc
|
|
619
625
|
|
|
620
626
|
@live_method
|
|
621
627
|
async def len(self, *, partition: Optional[str] = None, total: bool = False) -> int:
|
|
@@ -6,6 +6,7 @@ import time
|
|
|
6
6
|
import uuid
|
|
7
7
|
from collections.abc import AsyncGenerator, Collection, Sequence
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
+
from pathlib import PurePosixPath
|
|
9
10
|
from typing import TYPE_CHECKING, Any, AsyncIterator, Literal, Optional, Union, overload
|
|
10
11
|
|
|
11
12
|
from ._pty import get_pty_info
|
|
@@ -600,6 +601,49 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
600
601
|
|
|
601
602
|
return image
|
|
602
603
|
|
|
604
|
+
async def _experimental_mount_image(self, path: Union[PurePosixPath, str], image: Optional[_Image]):
|
|
605
|
+
"""Mount an Image at a path in the Sandbox filesystem."""
|
|
606
|
+
|
|
607
|
+
image_id = None
|
|
608
|
+
|
|
609
|
+
if image:
|
|
610
|
+
if not image._object_id:
|
|
611
|
+
# FIXME
|
|
612
|
+
raise InvalidError("Image has not been built.")
|
|
613
|
+
image_id = image._object_id
|
|
614
|
+
else:
|
|
615
|
+
image_id = "" # empty string indicates mount an empty dir
|
|
616
|
+
|
|
617
|
+
task_id = await self._get_task_id()
|
|
618
|
+
if (command_router_client := await self._get_command_router_client(task_id)) is None:
|
|
619
|
+
raise InvalidError("Mounting directories requires direct Sandbox control - please contact Modal support.")
|
|
620
|
+
|
|
621
|
+
posix_path = PurePosixPath(path)
|
|
622
|
+
if not posix_path.is_absolute():
|
|
623
|
+
raise InvalidError(f"Mount path must be absolute; got: {posix_path}")
|
|
624
|
+
path_bytes = posix_path.as_posix().encode("utf8")
|
|
625
|
+
|
|
626
|
+
req = sr_pb2.TaskMountDirectoryRequest(task_id=task_id, path=path_bytes, image_id=image_id)
|
|
627
|
+
await command_router_client.mount_image(req)
|
|
628
|
+
|
|
629
|
+
async def _experimental_snapshot_directory(self, path: Union[PurePosixPath, str]) -> _Image:
|
|
630
|
+
"""Snapshot local changes to a previously mounted Image, creating a new Image."""
|
|
631
|
+
|
|
632
|
+
task_id = await self._get_task_id()
|
|
633
|
+
if (command_router_client := await self._get_command_router_client(task_id)) is None:
|
|
634
|
+
raise InvalidError(
|
|
635
|
+
"Snapshotting directories requires direct Sandbox control - please contact Modal support."
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
posix_path = PurePosixPath(path)
|
|
639
|
+
if not posix_path.is_absolute():
|
|
640
|
+
raise InvalidError(f"Snapshot path must be absolute; got: {posix_path}")
|
|
641
|
+
path_bytes = posix_path.as_posix().encode("utf8")
|
|
642
|
+
|
|
643
|
+
req = sr_pb2.TaskSnapshotDirectoryRequest(task_id=task_id, path=path_bytes)
|
|
644
|
+
res = await command_router_client.snapshot_directory(req)
|
|
645
|
+
return _Image._new_hydrated(res.image_id, self._client, None)
|
|
646
|
+
|
|
603
647
|
# Live handle methods
|
|
604
648
|
|
|
605
649
|
async def wait(self, raise_on_termination: bool = True):
|
|
@@ -22,6 +22,7 @@ import modal.stream_type
|
|
|
22
22
|
import modal.volume
|
|
23
23
|
import modal_proto.api_pb2
|
|
24
24
|
import os
|
|
25
|
+
import pathlib
|
|
25
26
|
import typing
|
|
26
27
|
import typing_extensions
|
|
27
28
|
|
|
@@ -254,6 +255,18 @@ class _Sandbox(modal._object._Object):
|
|
|
254
255
|
"""
|
|
255
256
|
...
|
|
256
257
|
|
|
258
|
+
async def _experimental_mount_image(
|
|
259
|
+
self, path: typing.Union[pathlib.PurePosixPath, str], image: typing.Optional[modal.image._Image]
|
|
260
|
+
):
|
|
261
|
+
"""Mount an Image at a path in the Sandbox filesystem."""
|
|
262
|
+
...
|
|
263
|
+
|
|
264
|
+
async def _experimental_snapshot_directory(
|
|
265
|
+
self, path: typing.Union[pathlib.PurePosixPath, str]
|
|
266
|
+
) -> modal.image._Image:
|
|
267
|
+
"""Snapshot local changes to a previously mounted Image, creating a new Image."""
|
|
268
|
+
...
|
|
269
|
+
|
|
257
270
|
async def wait(self, raise_on_termination: bool = True):
|
|
258
271
|
"""Wait for the Sandbox to finish running."""
|
|
259
272
|
...
|
|
@@ -820,6 +833,32 @@ class Sandbox(modal.object.Object):
|
|
|
820
833
|
|
|
821
834
|
snapshot_filesystem: __snapshot_filesystem_spec[typing_extensions.Self]
|
|
822
835
|
|
|
836
|
+
class ___experimental_mount_image_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
837
|
+
def __call__(
|
|
838
|
+
self, /, path: typing.Union[pathlib.PurePosixPath, str], image: typing.Optional[modal.image.Image]
|
|
839
|
+
):
|
|
840
|
+
"""Mount an Image at a path in the Sandbox filesystem."""
|
|
841
|
+
...
|
|
842
|
+
|
|
843
|
+
async def aio(
|
|
844
|
+
self, /, path: typing.Union[pathlib.PurePosixPath, str], image: typing.Optional[modal.image.Image]
|
|
845
|
+
):
|
|
846
|
+
"""Mount an Image at a path in the Sandbox filesystem."""
|
|
847
|
+
...
|
|
848
|
+
|
|
849
|
+
_experimental_mount_image: ___experimental_mount_image_spec[typing_extensions.Self]
|
|
850
|
+
|
|
851
|
+
class ___experimental_snapshot_directory_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
852
|
+
def __call__(self, /, path: typing.Union[pathlib.PurePosixPath, str]) -> modal.image.Image:
|
|
853
|
+
"""Snapshot local changes to a previously mounted Image, creating a new Image."""
|
|
854
|
+
...
|
|
855
|
+
|
|
856
|
+
async def aio(self, /, path: typing.Union[pathlib.PurePosixPath, str]) -> modal.image.Image:
|
|
857
|
+
"""Snapshot local changes to a previously mounted Image, creating a new Image."""
|
|
858
|
+
...
|
|
859
|
+
|
|
860
|
+
_experimental_snapshot_directory: ___experimental_snapshot_directory_spec[typing_extensions.Self]
|
|
861
|
+
|
|
823
862
|
class __wait_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
824
863
|
def __call__(self, /, raise_on_termination: bool = True):
|
|
825
864
|
"""Wait for the Sandbox to finish running."""
|
|
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
|