modAL 1.5.3.dev9__tar.gz → 1.5.3.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.5.3.dev9 → modal-1.5.3.dev11}/PKG-INFO +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_function_variants.py +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_functions.py +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/task_command_router_client.py +5 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/client.pyi +2 -2
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cloud_bucket_mount.py +15 -3
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cloud_bucket_mount.pyi +2 -2
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/exception.py +4 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/sandbox.py +156 -30
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/sandbox.pyi +143 -9
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/secret.py +9 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/secret.pyi +18 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/snapshot.py +17 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/snapshot.pyi +22 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/api_grpc.py +16 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/api_pb2.py +507 -439
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/api_pb2.pyi +108 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_version/__init__.py +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/pyproject.toml +0 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/LICENSE +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/README.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/__main__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_billing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_clustered_functions.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_environments.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_grpc_client.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_image.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_ipython.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_load_context.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_location.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_logs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_object.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_output/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_output/manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_output/pty.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_output/rich.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_output/status.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_partial_function.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_resolver.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_resources.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_serialization.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_server.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_traceback.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_tunnel.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_tunnel.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_type_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/logger.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_vendor/version.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_watcher.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/_workspace.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/app.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/app.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/billing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/billing.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/README.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/builder/base-images.json +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/call_graph.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/_download.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/_help.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/_traceback.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/app.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/billing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/changelog.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/cluster.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/config.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/container.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/curl.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/dashboard.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/dict.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/endpoint.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/entry_point.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/environment.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/image.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/import_refs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/launch.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/logo.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/profile.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/queues.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/run.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/secret.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/selector.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/shell.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/skills.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/token.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/volume.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cli/workspace.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/client.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cls.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/cls.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/config.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/container_process.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/container_process.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/dict.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/dict.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/environments.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/environments.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/experimental/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/experimental/flash.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/experimental/ipython.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/file_io.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/file_io.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/functions.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/functions.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/image.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/image.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/io_streams.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/io_streams.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/mount.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/mount.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/network_file_system.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/network_file_system.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/object.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/object.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/output.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/parallel_map.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/parallel_map.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/partial_function.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/partial_function.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/proxy.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/proxy.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/py.typed +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/queue.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/queue.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/retries.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/runner.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/runner.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/running_app.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/sandbox_fs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/schedule.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/scheduler_placement.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/server.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/server.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/serving.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/serving.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/stream_type.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/token_flow.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/token_flow.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/types.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/volume.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/volume.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/workspace.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal/workspace.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/py.typed +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/modal_version/__main__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev11}/setup.cfg +0 -0
|
@@ -171,7 +171,7 @@ class _FunctionOptions:
|
|
|
171
171
|
replace_secret_ids=bool(self.secrets),
|
|
172
172
|
replace_volume_mounts=len(volume_mounts) > 0,
|
|
173
173
|
volume_mounts=volume_mounts,
|
|
174
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts),
|
|
174
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts)[0],
|
|
175
175
|
replace_cloud_bucket_mounts=bool(self.cloud_bucket_mounts),
|
|
176
176
|
resources=self.resources,
|
|
177
177
|
retry_policy=self.retry_policy,
|
|
@@ -1000,7 +1000,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1000
1000
|
untrusted=restrict_modal_access,
|
|
1001
1001
|
single_use_containers=single_use_containers,
|
|
1002
1002
|
max_inputs=int(single_use_containers), # TODO(michael) remove after worker rollover
|
|
1003
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
1003
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts)[0],
|
|
1004
1004
|
scheduler_placement=scheduler_placement,
|
|
1005
1005
|
is_class=info.is_service_class(),
|
|
1006
1006
|
class_parameter_info=info.class_parameter_info(),
|
|
@@ -974,3 +974,8 @@ class TaskCommandRouterClient:
|
|
|
974
974
|
return await self._unary_call_with_deadline(
|
|
975
975
|
self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
|
|
976
976
|
)
|
|
977
|
+
|
|
978
|
+
async def snapshot_memory(
|
|
979
|
+
self, request: sr_pb2.TaskSnapshotMemoryRequest, *, timeout: float, **kwargs
|
|
980
|
+
) -> sr_pb2.TaskSnapshotMemoryResponse:
|
|
981
|
+
return await self._unary_call_with_deadline(self._stub.TaskSnapshotMemory, request, timeout=timeout, **kwargs)
|
|
@@ -37,7 +37,7 @@ class _Client:
|
|
|
37
37
|
server_url: str,
|
|
38
38
|
client_type: int,
|
|
39
39
|
credentials: typing.Optional[tuple[str, str]],
|
|
40
|
-
version: str = "1.5.3.
|
|
40
|
+
version: str = "1.5.3.dev11",
|
|
41
41
|
):
|
|
42
42
|
"""mdmd:hidden
|
|
43
43
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -207,7 +207,7 @@ class Client:
|
|
|
207
207
|
server_url: str,
|
|
208
208
|
client_type: int,
|
|
209
209
|
credentials: typing.Optional[tuple[str, str]],
|
|
210
|
-
version: str = "1.5.3.
|
|
210
|
+
version: str = "1.5.3.dev11",
|
|
211
211
|
):
|
|
212
212
|
"""mdmd:hidden
|
|
213
213
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -121,11 +121,14 @@ class _CloudBucketMount:
|
|
|
121
121
|
force_path_style: bool = False
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def cloud_bucket_mounts_to_proto(
|
|
124
|
+
def cloud_bucket_mounts_to_proto(
|
|
125
|
+
mounts: Sequence[tuple[str, _CloudBucketMount]], split_ephemeral_credentials: bool = False
|
|
126
|
+
) -> tuple[list[api_pb2.CloudBucketMount], dict[str, api_pb2.StringMap]]:
|
|
125
127
|
"""mdmd:hidden
|
|
126
128
|
Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server.
|
|
127
129
|
"""
|
|
128
130
|
cloud_bucket_mounts: list[api_pb2.CloudBucketMount] = []
|
|
131
|
+
cloud_bucket_credentials: dict[str, api_pb2.StringMap] = {}
|
|
129
132
|
|
|
130
133
|
for path, mount in mounts:
|
|
131
134
|
# crude mapping from mount arguments to type.
|
|
@@ -153,11 +156,20 @@ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]
|
|
|
153
156
|
else:
|
|
154
157
|
key_prefix = mount.key_prefix
|
|
155
158
|
|
|
159
|
+
credentials_secret_id = ""
|
|
160
|
+
if secret := mount.secret:
|
|
161
|
+
if split_ephemeral_credentials and secret._load_env_dict:
|
|
162
|
+
env_dict = secret._load_env_dict()
|
|
163
|
+
credientials = api_pb2.StringMap(contents=env_dict)
|
|
164
|
+
cloud_bucket_credentials[path] = credientials
|
|
165
|
+
else:
|
|
166
|
+
credentials_secret_id = secret.object_id
|
|
167
|
+
|
|
156
168
|
cloud_bucket_mount = api_pb2.CloudBucketMount(
|
|
157
169
|
bucket_name=mount.bucket_name,
|
|
158
170
|
bucket_endpoint_url=mount.bucket_endpoint_url,
|
|
159
171
|
mount_path=path,
|
|
160
|
-
credentials_secret_id=
|
|
172
|
+
credentials_secret_id=credentials_secret_id,
|
|
161
173
|
read_only=mount.read_only,
|
|
162
174
|
bucket_type=bucket_type,
|
|
163
175
|
requester_pays=mount.requester_pays,
|
|
@@ -167,7 +179,7 @@ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]
|
|
|
167
179
|
)
|
|
168
180
|
cloud_bucket_mounts.append(cloud_bucket_mount)
|
|
169
181
|
|
|
170
|
-
return cloud_bucket_mounts
|
|
182
|
+
return cloud_bucket_mounts, cloud_bucket_credentials
|
|
171
183
|
|
|
172
184
|
|
|
173
185
|
CloudBucketMount = synchronize_api(_CloudBucketMount)
|
|
@@ -126,8 +126,8 @@ class _CloudBucketMount:
|
|
|
126
126
|
...
|
|
127
127
|
|
|
128
128
|
def cloud_bucket_mounts_to_proto(
|
|
129
|
-
mounts: collections.abc.Sequence[tuple[str, _CloudBucketMount]],
|
|
130
|
-
) -> list[modal_proto.api_pb2.CloudBucketMount]:
|
|
129
|
+
mounts: collections.abc.Sequence[tuple[str, _CloudBucketMount]], split_ephemeral_credentials: bool = False
|
|
130
|
+
) -> tuple[list[modal_proto.api_pb2.CloudBucketMount], dict[str, modal_proto.api_pb2.StringMap]]:
|
|
131
131
|
"""mdmd:hidden
|
|
132
132
|
Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server.
|
|
133
133
|
"""
|
|
@@ -188,6 +188,10 @@ class SandboxTimeoutError(TimeoutError):
|
|
|
188
188
|
"""Raised when a Sandbox exceeds its execution duration limit and times out."""
|
|
189
189
|
|
|
190
190
|
|
|
191
|
+
class SnapshotCreationError(Error):
|
|
192
|
+
"""Raised when a Sandbox fails to create an exit snapshot."""
|
|
193
|
+
|
|
194
|
+
|
|
191
195
|
class ExecTimeoutError(TimeoutError):
|
|
192
196
|
"""Raised when a container process exceeds its execution duration limit and times out."""
|
|
193
197
|
|
|
@@ -52,10 +52,13 @@ from .exception import (
|
|
|
52
52
|
ClientClosed,
|
|
53
53
|
ConflictError,
|
|
54
54
|
ExecutionError,
|
|
55
|
+
InternalError,
|
|
55
56
|
InvalidError,
|
|
56
57
|
NotFoundError,
|
|
57
58
|
SandboxTerminatedError,
|
|
58
59
|
SandboxTimeoutError,
|
|
60
|
+
SnapshotCreationError,
|
|
61
|
+
TimeoutError,
|
|
59
62
|
)
|
|
60
63
|
from .file_io import _FileIO, ls, mkdir, rm, watch
|
|
61
64
|
from .io_streams import (
|
|
@@ -77,6 +80,8 @@ from .stream_type import StreamType
|
|
|
77
80
|
from .types import FileWatchEvent, FileWatchEventType, SandboxConnectCredentials
|
|
78
81
|
|
|
79
82
|
_default_image: _Image = _Image.debian_slim()
|
|
83
|
+
_EXIT_SNAPSHOT_POLL_INTERVAL_SECONDS = 1.0
|
|
84
|
+
_EXIT_SNAPSHOT_NOT_FOUND_ERROR_CODES = frozenset((api_pb2.SandboxGetExitSnapshotResponse.ERROR_CODE_TIMEOUT,))
|
|
80
85
|
|
|
81
86
|
|
|
82
87
|
async def _gather_load_with_timings(
|
|
@@ -472,7 +477,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
472
477
|
nfs_mounts=network_file_system_mount_protos(validated_network_file_systems),
|
|
473
478
|
runtime=config.get("function_runtime"),
|
|
474
479
|
runtime_debug=config.get("function_runtime_debug"),
|
|
475
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
480
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts)[0],
|
|
476
481
|
volume_mounts=volume_mounts,
|
|
477
482
|
pty_info=pty_info,
|
|
478
483
|
scheduler_placement=scheduler_placement,
|
|
@@ -845,6 +850,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
845
850
|
verbose: bool = False,
|
|
846
851
|
custom_domain: str | None = None,
|
|
847
852
|
client: _Client | None = None,
|
|
853
|
+
_experimental_enable_snapshot: bool = False,
|
|
848
854
|
) -> "_Sandbox":
|
|
849
855
|
"""Create a sandbox using the V2 backend.
|
|
850
856
|
|
|
@@ -860,8 +866,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
860
866
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
861
867
|
in MiB), matching `Sandbox.create()`.
|
|
862
868
|
|
|
863
|
-
|
|
864
|
-
|
|
869
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
870
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
871
|
+
file systems and GPUs are not supported.
|
|
865
872
|
|
|
866
873
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
867
874
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -961,8 +968,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
961
968
|
for _, vol in validated_volumes:
|
|
962
969
|
dep_tasks.append(resolver.load(vol, load_context))
|
|
963
970
|
for _, cloud_bucket_mount in cloud_bucket_mounts:
|
|
964
|
-
if cloud_bucket_mount.secret:
|
|
965
|
-
|
|
971
|
+
if secret := cloud_bucket_mount.secret:
|
|
972
|
+
if not secret._is_ephemeral:
|
|
973
|
+
dep_tasks.append(resolver.load(cloud_bucket_mount.secret, load_context))
|
|
966
974
|
if proxy:
|
|
967
975
|
dep_tasks.append(resolver.load(proxy, load_context))
|
|
968
976
|
dep_timings = await _gather_load_with_timings(dep_tasks) if dep_tasks else []
|
|
@@ -971,6 +979,10 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
971
979
|
|
|
972
980
|
volume_mounts = [_volume_to_mount_proto(path, volume) for path, volume in validated_volumes]
|
|
973
981
|
|
|
982
|
+
cloud_bucket_mount_protos, cloud_bucket_credientials = cloud_bucket_mounts_to_proto(
|
|
983
|
+
cloud_bucket_mounts, split_ephemeral_credentials=True
|
|
984
|
+
)
|
|
985
|
+
|
|
974
986
|
definition = api_pb2.Sandbox(
|
|
975
987
|
entrypoint_args=args,
|
|
976
988
|
image_id=image.object_id,
|
|
@@ -995,12 +1007,13 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
995
1007
|
inbound_cidr_allowlist=list(inbound_cidr_allowlist) if inbound_cidr_allowlist is not None else [],
|
|
996
1008
|
i6pn_enabled=i6pn,
|
|
997
1009
|
volume_mounts=volume_mounts,
|
|
998
|
-
cloud_bucket_mounts=
|
|
1010
|
+
cloud_bucket_mounts=cloud_bucket_mount_protos,
|
|
999
1011
|
readiness_probe=(readiness_probe._to_proto() if readiness_probe else None),
|
|
1000
1012
|
experimental_options_v2=(
|
|
1001
1013
|
{k: str(v) for k, v in experimental_options.items()} if experimental_options else None
|
|
1002
1014
|
),
|
|
1003
1015
|
custom_domain=custom_domain,
|
|
1016
|
+
enable_snapshot=_experimental_enable_snapshot,
|
|
1004
1017
|
)
|
|
1005
1018
|
|
|
1006
1019
|
tag_protos = [api_pb2.SandboxTag(tag_name=k, tag_value=v) for k, v in tags.items()] if tags else []
|
|
@@ -1009,6 +1022,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1009
1022
|
definition=definition,
|
|
1010
1023
|
ephemeral_secrets=api_pb2.StringMap(contents=env_dict) if env_dict else None,
|
|
1011
1024
|
tags=tag_protos,
|
|
1025
|
+
cloud_bucket_mount_credentials=cloud_bucket_credientials,
|
|
1012
1026
|
)
|
|
1013
1027
|
assert load_context.client._auth_token_manager
|
|
1014
1028
|
auth_token = await load_context.client._auth_token_manager.get_token()
|
|
@@ -1401,6 +1415,68 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1401
1415
|
req = sr_pb2.TaskSetNetworkAccessRequest(task_id=task_id, network_access=network_access)
|
|
1402
1416
|
await command_router_client.set_network_access(req)
|
|
1403
1417
|
|
|
1418
|
+
async def _experimental_get_exit_snapshot(self, timeout: float | None = 60) -> _Image:
|
|
1419
|
+
"""Get the exit filesystem snapshot image.
|
|
1420
|
+
|
|
1421
|
+
Args:
|
|
1422
|
+
timeout: Client-side deadline in seconds (default 60). Use `None` to
|
|
1423
|
+
poll until the snapshot reaches a terminal state. Use `0` to
|
|
1424
|
+
perform an immediate check.
|
|
1425
|
+
|
|
1426
|
+
Returns:
|
|
1427
|
+
The exit snapshot Image.
|
|
1428
|
+
|
|
1429
|
+
Raises:
|
|
1430
|
+
InvalidError: If `timeout` is negative, or if exit snapshot is not
|
|
1431
|
+
enabled for the sandbox.
|
|
1432
|
+
TimeoutError: If `timeout` elapses before the snapshot reaches a
|
|
1433
|
+
terminal state. This includes `timeout=0` when the snapshot is
|
|
1434
|
+
still pending.
|
|
1435
|
+
SnapshotCreationError: If no exit snapshot image will be produced.
|
|
1436
|
+
NotFoundError: If the sandbox does not exist.
|
|
1437
|
+
PermissionDeniedError: If the caller cannot access the sandbox.
|
|
1438
|
+
InternalError: If persisted snapshot state is malformed.
|
|
1439
|
+
ServiceError: If a transient client/server communication failure occurs.
|
|
1440
|
+
"""
|
|
1441
|
+
if timeout is not None and timeout < 0:
|
|
1442
|
+
raise InvalidError("timeout must be non-negative or None")
|
|
1443
|
+
|
|
1444
|
+
deadline = None if timeout is None else time.monotonic() + timeout
|
|
1445
|
+
timeout_message = f"timed out waiting for exit snapshot for Sandbox {self.object_id}"
|
|
1446
|
+
|
|
1447
|
+
while True:
|
|
1448
|
+
resp: api_pb2.SandboxGetExitSnapshotResponse = await self._client.stub.SandboxGetExitSnapshot(
|
|
1449
|
+
api_pb2.SandboxGetExitSnapshotRequest(sandbox_id=self.object_id)
|
|
1450
|
+
)
|
|
1451
|
+
outcome = resp.WhichOneof("outcome")
|
|
1452
|
+
|
|
1453
|
+
if outcome == "success":
|
|
1454
|
+
if not resp.success.image_id:
|
|
1455
|
+
raise InternalError("Exit snapshot result is missing image ID")
|
|
1456
|
+
return _Image._new_hydrated(resp.success.image_id, self._client, None)
|
|
1457
|
+
|
|
1458
|
+
if outcome == "error":
|
|
1459
|
+
if resp.error.error_code in _EXIT_SNAPSHOT_NOT_FOUND_ERROR_CODES:
|
|
1460
|
+
message = resp.error.message or "No exit snapshot image will be produced"
|
|
1461
|
+
raise SnapshotCreationError(message)
|
|
1462
|
+
message = resp.error.message or "Exit snapshot state is malformed"
|
|
1463
|
+
raise InternalError(message)
|
|
1464
|
+
|
|
1465
|
+
if outcome != "pending":
|
|
1466
|
+
raise InternalError("Exit snapshot response is missing an outcome")
|
|
1467
|
+
|
|
1468
|
+
sleep_for = _EXIT_SNAPSHOT_POLL_INTERVAL_SECONDS
|
|
1469
|
+
if deadline is not None:
|
|
1470
|
+
remaining = deadline - time.monotonic()
|
|
1471
|
+
if remaining <= 0:
|
|
1472
|
+
raise TimeoutError(timeout_message)
|
|
1473
|
+
sleep_for = min(sleep_for, remaining)
|
|
1474
|
+
|
|
1475
|
+
await asyncio.sleep(sleep_for)
|
|
1476
|
+
|
|
1477
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
1478
|
+
raise TimeoutError(timeout_message)
|
|
1479
|
+
|
|
1404
1480
|
async def snapshot_filesystem(
|
|
1405
1481
|
self,
|
|
1406
1482
|
timeout: int = 55,
|
|
@@ -2079,19 +2155,27 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2079
2155
|
)
|
|
2080
2156
|
|
|
2081
2157
|
async def _experimental_snapshot(self) -> _SandboxSnapshot:
|
|
2082
|
-
self.
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2158
|
+
if self._is_v2:
|
|
2159
|
+
task_id = await self._get_task_id()
|
|
2160
|
+
command_router_client = await self._get_command_router_client(task_id)
|
|
2161
|
+
snap_v2_resp = await command_router_client.snapshot_memory(
|
|
2162
|
+
sr_pb2.TaskSnapshotMemoryRequest(task_id=task_id, idempotency_key=str(uuid.uuid4())),
|
|
2163
|
+
timeout=55.0,
|
|
2164
|
+
)
|
|
2165
|
+
snapshot_id = snap_v2_resp.snapshot_id
|
|
2166
|
+
else:
|
|
2167
|
+
await self._get_task_id()
|
|
2168
|
+
snap_req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id)
|
|
2169
|
+
snap_resp = await self._client.stub.SandboxSnapshot(snap_req)
|
|
2086
2170
|
|
|
2087
|
-
|
|
2171
|
+
snapshot_id = snap_resp.snapshot_id
|
|
2088
2172
|
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2173
|
+
# wait for the snapshot to succeed. this is implemented as a second idempotent rpc
|
|
2174
|
+
# because the snapshot itself may take a while to complete.
|
|
2175
|
+
wait_req = api_pb2.SandboxSnapshotWaitRequest(snapshot_id=snapshot_id, timeout=55.0)
|
|
2176
|
+
wait_resp = await self._client.stub.SandboxSnapshotWait(wait_req)
|
|
2177
|
+
if wait_resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
|
|
2178
|
+
raise ExecutionError(wait_resp.result.exception)
|
|
2095
2179
|
|
|
2096
2180
|
async def _load(
|
|
2097
2181
|
self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None
|
|
@@ -2102,7 +2186,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2102
2186
|
rep = "SandboxSnapshot()"
|
|
2103
2187
|
# TODO: use ._new_hydrated instead
|
|
2104
2188
|
obj = _SandboxSnapshot._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
|
|
2105
|
-
|
|
2189
|
+
metadata = api_pb2.SandboxSnapshotHandleMetadata(is_v2=self._is_v2)
|
|
2190
|
+
obj._hydrate(snapshot_id, self._client, metadata)
|
|
2106
2191
|
|
|
2107
2192
|
return obj
|
|
2108
2193
|
|
|
@@ -2113,27 +2198,41 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2113
2198
|
*,
|
|
2114
2199
|
name: str | None = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
2115
2200
|
):
|
|
2201
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
2202
|
+
|
|
2203
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
2204
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
2205
|
+
"""
|
|
2116
2206
|
client = client or await _Client.from_env()
|
|
2117
2207
|
|
|
2208
|
+
if snapshot._is_v2 is None:
|
|
2209
|
+
await snapshot.hydrate(client=client)
|
|
2210
|
+
use_v2 = bool(snapshot._is_v2)
|
|
2211
|
+
|
|
2118
2212
|
if name is not None and name != _DEFAULT_SANDBOX_NAME_OVERRIDE:
|
|
2119
2213
|
check_object_name(name, "Sandbox")
|
|
2120
2214
|
|
|
2121
2215
|
if name is _DEFAULT_SANDBOX_NAME_OVERRIDE:
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_UNSPECIFIED,
|
|
2125
|
-
)
|
|
2216
|
+
override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_UNSPECIFIED
|
|
2217
|
+
name_override = None
|
|
2126
2218
|
elif name is None:
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_NONE,
|
|
2130
|
-
)
|
|
2219
|
+
override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_NONE
|
|
2220
|
+
name_override = None
|
|
2131
2221
|
else:
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2222
|
+
override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_STRING
|
|
2223
|
+
name_override = name
|
|
2224
|
+
|
|
2225
|
+
if use_v2:
|
|
2226
|
+
return await _Sandbox._experimental_from_snapshot_v2(
|
|
2227
|
+
snapshot, client, override_type=override_type, name_override=name_override
|
|
2136
2228
|
)
|
|
2229
|
+
|
|
2230
|
+
restore_req = api_pb2.SandboxRestoreRequest(
|
|
2231
|
+
snapshot_id=snapshot.object_id,
|
|
2232
|
+
sandbox_name_override_type=override_type,
|
|
2233
|
+
)
|
|
2234
|
+
if name_override is not None:
|
|
2235
|
+
restore_req.sandbox_name_override = name_override
|
|
2137
2236
|
# Pin the restored Sandbox to a specific worker when MODAL_WORKER_ID is
|
|
2138
2237
|
# set.
|
|
2139
2238
|
if worker_id := config.get("worker_id"):
|
|
@@ -2153,6 +2252,33 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2153
2252
|
raise ExecutionError(resp.task_result.exception)
|
|
2154
2253
|
return sandbox
|
|
2155
2254
|
|
|
2255
|
+
@staticmethod
|
|
2256
|
+
async def _experimental_from_snapshot_v2(
|
|
2257
|
+
snapshot: _SandboxSnapshot,
|
|
2258
|
+
client: _Client,
|
|
2259
|
+
*,
|
|
2260
|
+
override_type: "api_pb2.SandboxRestoreRequest.SandboxNameOverrideType.ValueType",
|
|
2261
|
+
name_override: str | None,
|
|
2262
|
+
) -> "_Sandbox":
|
|
2263
|
+
restore_req = api_pb2.SandboxRestoreV2Request(
|
|
2264
|
+
snapshot_id=snapshot.object_id,
|
|
2265
|
+
sandbox_name_override_type=override_type,
|
|
2266
|
+
)
|
|
2267
|
+
if name_override is not None:
|
|
2268
|
+
restore_req.sandbox_name_override = name_override
|
|
2269
|
+
if worker_id := config.get("worker_id"):
|
|
2270
|
+
restore_req.worker_id = worker_id
|
|
2271
|
+
|
|
2272
|
+
assert client._auth_token_manager
|
|
2273
|
+
auth_token = await client._auth_token_manager.get_token()
|
|
2274
|
+
restore_resp: api_pb2.SandboxRestoreV2Response = await client.stub.SandboxRestoreV2(
|
|
2275
|
+
restore_req, metadata=[("x-modal-auth-token", auth_token)]
|
|
2276
|
+
)
|
|
2277
|
+
|
|
2278
|
+
sandbox = await _Sandbox.from_id(restore_resp.sandbox_id, client)
|
|
2279
|
+
sandbox._task_id = restore_resp.task_id
|
|
2280
|
+
return sandbox
|
|
2281
|
+
|
|
2156
2282
|
@property
|
|
2157
2283
|
def filesystem(self) -> _SandboxFilesystem:
|
|
2158
2284
|
"""Namespace for Sandbox filesystem APIs."""
|
|
@@ -393,6 +393,7 @@ class _Sandbox(modal._object._Object):
|
|
|
393
393
|
verbose: bool = False,
|
|
394
394
|
custom_domain: typing.Optional[str] = None,
|
|
395
395
|
client: typing.Optional[modal.client._Client] = None,
|
|
396
|
+
_experimental_enable_snapshot: bool = False,
|
|
396
397
|
) -> _Sandbox:
|
|
397
398
|
"""Create a sandbox using the V2 backend.
|
|
398
399
|
|
|
@@ -408,8 +409,9 @@ class _Sandbox(modal._object._Object):
|
|
|
408
409
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
409
410
|
in MiB), matching `Sandbox.create()`.
|
|
410
411
|
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
413
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
414
|
+
file systems and GPUs are not supported.
|
|
413
415
|
|
|
414
416
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
415
417
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -571,6 +573,31 @@ class _Sandbox(modal._object._Object):
|
|
|
571
573
|
"""
|
|
572
574
|
...
|
|
573
575
|
|
|
576
|
+
async def _experimental_get_exit_snapshot(self, timeout: typing.Optional[float] = 60) -> modal._image._Image:
|
|
577
|
+
"""Get the exit filesystem snapshot image.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
timeout: Client-side deadline in seconds (default 60). Use `None` to
|
|
581
|
+
poll until the snapshot reaches a terminal state. Use `0` to
|
|
582
|
+
perform an immediate check.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
The exit snapshot Image.
|
|
586
|
+
|
|
587
|
+
Raises:
|
|
588
|
+
InvalidError: If `timeout` is negative, or if exit snapshot is not
|
|
589
|
+
enabled for the sandbox.
|
|
590
|
+
TimeoutError: If `timeout` elapses before the snapshot reaches a
|
|
591
|
+
terminal state. This includes `timeout=0` when the snapshot is
|
|
592
|
+
still pending.
|
|
593
|
+
SnapshotCreationError: If no exit snapshot image will be produced.
|
|
594
|
+
NotFoundError: If the sandbox does not exist.
|
|
595
|
+
PermissionDeniedError: If the caller cannot access the sandbox.
|
|
596
|
+
InternalError: If persisted snapshot state is malformed.
|
|
597
|
+
ServiceError: If a transient client/server communication failure occurs.
|
|
598
|
+
"""
|
|
599
|
+
...
|
|
600
|
+
|
|
574
601
|
async def snapshot_filesystem(
|
|
575
602
|
self, timeout: int = 55, *, ttl: typing.Optional[int] = 2592000
|
|
576
603
|
) -> modal._image._Image:
|
|
@@ -852,7 +879,22 @@ class _Sandbox(modal._object._Object):
|
|
|
852
879
|
client: typing.Optional[modal.client._Client] = None,
|
|
853
880
|
*,
|
|
854
881
|
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
855
|
-
):
|
|
882
|
+
):
|
|
883
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
884
|
+
|
|
885
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
886
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
887
|
+
"""
|
|
888
|
+
...
|
|
889
|
+
|
|
890
|
+
@staticmethod
|
|
891
|
+
async def _experimental_from_snapshot_v2(
|
|
892
|
+
snapshot: modal.snapshot._SandboxSnapshot,
|
|
893
|
+
client: modal.client._Client,
|
|
894
|
+
*,
|
|
895
|
+
override_type: int,
|
|
896
|
+
name_override: typing.Optional[str],
|
|
897
|
+
) -> _Sandbox: ...
|
|
856
898
|
@property
|
|
857
899
|
def filesystem(self) -> modal.sandbox_fs._SandboxFilesystem:
|
|
858
900
|
"""Namespace for Sandbox filesystem APIs."""
|
|
@@ -1768,6 +1810,7 @@ class Sandbox(modal.object.Object):
|
|
|
1768
1810
|
verbose: bool = False,
|
|
1769
1811
|
custom_domain: typing.Optional[str] = None,
|
|
1770
1812
|
client: typing.Optional[modal.client.Client] = None,
|
|
1813
|
+
_experimental_enable_snapshot: bool = False,
|
|
1771
1814
|
) -> Sandbox:
|
|
1772
1815
|
"""Create a sandbox using the V2 backend.
|
|
1773
1816
|
|
|
@@ -1783,8 +1826,9 @@ class Sandbox(modal.object.Object):
|
|
|
1783
1826
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
1784
1827
|
in MiB), matching `Sandbox.create()`.
|
|
1785
1828
|
|
|
1786
|
-
|
|
1787
|
-
|
|
1829
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
1830
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
1831
|
+
file systems and GPUs are not supported.
|
|
1788
1832
|
|
|
1789
1833
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
1790
1834
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -1837,6 +1881,7 @@ class Sandbox(modal.object.Object):
|
|
|
1837
1881
|
verbose: bool = False,
|
|
1838
1882
|
custom_domain: typing.Optional[str] = None,
|
|
1839
1883
|
client: typing.Optional[modal.client.Client] = None,
|
|
1884
|
+
_experimental_enable_snapshot: bool = False,
|
|
1840
1885
|
) -> Sandbox:
|
|
1841
1886
|
"""Create a sandbox using the V2 backend.
|
|
1842
1887
|
|
|
@@ -1852,8 +1897,9 @@ class Sandbox(modal.object.Object):
|
|
|
1852
1897
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
1853
1898
|
in MiB), matching `Sandbox.create()`.
|
|
1854
1899
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1900
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
1901
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
1902
|
+
file systems and GPUs are not supported.
|
|
1857
1903
|
|
|
1858
1904
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
1859
1905
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -2177,6 +2223,59 @@ class Sandbox(modal.object.Object):
|
|
|
2177
2223
|
|
|
2178
2224
|
_experimental_set_outbound_network_policy: ___experimental_set_outbound_network_policy_spec
|
|
2179
2225
|
|
|
2226
|
+
class ___experimental_get_exit_snapshot_spec(typing_extensions.Protocol):
|
|
2227
|
+
def __call__(self, /, timeout: typing.Optional[float] = 60) -> modal.image.Image:
|
|
2228
|
+
"""Get the exit filesystem snapshot image.
|
|
2229
|
+
|
|
2230
|
+
Args:
|
|
2231
|
+
timeout: Client-side deadline in seconds (default 60). Use `None` to
|
|
2232
|
+
poll until the snapshot reaches a terminal state. Use `0` to
|
|
2233
|
+
perform an immediate check.
|
|
2234
|
+
|
|
2235
|
+
Returns:
|
|
2236
|
+
The exit snapshot Image.
|
|
2237
|
+
|
|
2238
|
+
Raises:
|
|
2239
|
+
InvalidError: If `timeout` is negative, or if exit snapshot is not
|
|
2240
|
+
enabled for the sandbox.
|
|
2241
|
+
TimeoutError: If `timeout` elapses before the snapshot reaches a
|
|
2242
|
+
terminal state. This includes `timeout=0` when the snapshot is
|
|
2243
|
+
still pending.
|
|
2244
|
+
SnapshotCreationError: If no exit snapshot image will be produced.
|
|
2245
|
+
NotFoundError: If the sandbox does not exist.
|
|
2246
|
+
PermissionDeniedError: If the caller cannot access the sandbox.
|
|
2247
|
+
InternalError: If persisted snapshot state is malformed.
|
|
2248
|
+
ServiceError: If a transient client/server communication failure occurs.
|
|
2249
|
+
"""
|
|
2250
|
+
...
|
|
2251
|
+
|
|
2252
|
+
async def aio(self, /, timeout: typing.Optional[float] = 60) -> modal.image.Image:
|
|
2253
|
+
"""Get the exit filesystem snapshot image.
|
|
2254
|
+
|
|
2255
|
+
Args:
|
|
2256
|
+
timeout: Client-side deadline in seconds (default 60). Use `None` to
|
|
2257
|
+
poll until the snapshot reaches a terminal state. Use `0` to
|
|
2258
|
+
perform an immediate check.
|
|
2259
|
+
|
|
2260
|
+
Returns:
|
|
2261
|
+
The exit snapshot Image.
|
|
2262
|
+
|
|
2263
|
+
Raises:
|
|
2264
|
+
InvalidError: If `timeout` is negative, or if exit snapshot is not
|
|
2265
|
+
enabled for the sandbox.
|
|
2266
|
+
TimeoutError: If `timeout` elapses before the snapshot reaches a
|
|
2267
|
+
terminal state. This includes `timeout=0` when the snapshot is
|
|
2268
|
+
still pending.
|
|
2269
|
+
SnapshotCreationError: If no exit snapshot image will be produced.
|
|
2270
|
+
NotFoundError: If the sandbox does not exist.
|
|
2271
|
+
PermissionDeniedError: If the caller cannot access the sandbox.
|
|
2272
|
+
InternalError: If persisted snapshot state is malformed.
|
|
2273
|
+
ServiceError: If a transient client/server communication failure occurs.
|
|
2274
|
+
"""
|
|
2275
|
+
...
|
|
2276
|
+
|
|
2277
|
+
_experimental_get_exit_snapshot: ___experimental_get_exit_snapshot_spec
|
|
2278
|
+
|
|
2180
2279
|
class __snapshot_filesystem_spec(typing_extensions.Protocol):
|
|
2181
2280
|
def __call__(self, /, timeout: int = 55, *, ttl: typing.Optional[int] = 2592000) -> modal.image.Image:
|
|
2182
2281
|
"""Snapshot the filesystem of the Sandbox.
|
|
@@ -2802,7 +2901,14 @@ class Sandbox(modal.object.Object):
|
|
|
2802
2901
|
client: typing.Optional[modal.client.Client] = None,
|
|
2803
2902
|
*,
|
|
2804
2903
|
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
2805
|
-
):
|
|
2904
|
+
):
|
|
2905
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
2906
|
+
|
|
2907
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
2908
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
2909
|
+
"""
|
|
2910
|
+
...
|
|
2911
|
+
|
|
2806
2912
|
async def aio(
|
|
2807
2913
|
self,
|
|
2808
2914
|
/,
|
|
@@ -2810,10 +2916,38 @@ class Sandbox(modal.object.Object):
|
|
|
2810
2916
|
client: typing.Optional[modal.client.Client] = None,
|
|
2811
2917
|
*,
|
|
2812
2918
|
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
2813
|
-
):
|
|
2919
|
+
):
|
|
2920
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
2921
|
+
|
|
2922
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
2923
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
2924
|
+
"""
|
|
2925
|
+
...
|
|
2814
2926
|
|
|
2815
2927
|
_experimental_from_snapshot: typing.ClassVar[___experimental_from_snapshot_spec]
|
|
2816
2928
|
|
|
2929
|
+
class ___experimental_from_snapshot_v2_spec(typing_extensions.Protocol):
|
|
2930
|
+
def __call__(
|
|
2931
|
+
self,
|
|
2932
|
+
/,
|
|
2933
|
+
snapshot: modal.snapshot.SandboxSnapshot,
|
|
2934
|
+
client: modal.client.Client,
|
|
2935
|
+
*,
|
|
2936
|
+
override_type: int,
|
|
2937
|
+
name_override: typing.Optional[str],
|
|
2938
|
+
) -> Sandbox: ...
|
|
2939
|
+
async def aio(
|
|
2940
|
+
self,
|
|
2941
|
+
/,
|
|
2942
|
+
snapshot: modal.snapshot.SandboxSnapshot,
|
|
2943
|
+
client: modal.client.Client,
|
|
2944
|
+
*,
|
|
2945
|
+
override_type: int,
|
|
2946
|
+
name_override: typing.Optional[str],
|
|
2947
|
+
) -> Sandbox: ...
|
|
2948
|
+
|
|
2949
|
+
_experimental_from_snapshot_v2: typing.ClassVar[___experimental_from_snapshot_v2_spec]
|
|
2950
|
+
|
|
2817
2951
|
@property
|
|
2818
2952
|
def filesystem(self) -> modal.sandbox_fs.SandboxFilesystem:
|
|
2819
2953
|
"""Namespace for Sandbox filesystem APIs."""
|