modAL 1.3.6.dev9__tar.gz → 1.3.6.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.3.6.dev9 → modal-1.3.6.dev11}/PKG-INFO +1 -1
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/task_command_router_client.py +38 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/shell.py +57 -7
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/client.pyi +2 -2
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/sandbox.py +252 -12
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/sandbox.pyi +235 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_version/__init__.py +1 -1
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/LICENSE +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/README.md +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/__main__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_billing.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_clustered_functions.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_functions.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_grpc_client.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_ipython.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_load_context.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_location.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_logs.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_object.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_output/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_output/manager.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_output/pty.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_output/rich.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_output/status.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_partial_function.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_resolver.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_resources.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_serialization.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_server.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_traceback.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_tunnel.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_tunnel.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_type_manager.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/browser_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/logger.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_vendor/version.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/_watcher.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/app.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/app.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/billing.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/README.md +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/builder/base-images.json +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/call_graph.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/_download.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/_traceback.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/app.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/billing.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/changelog.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/cluster.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/config.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/container.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/dashboard.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/dict.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/entry_point.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/environment.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/import_refs.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/launch.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/profile.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/queues.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/run.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/secret.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/selector.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/token.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/utils.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cli/volume.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/client.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cls.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/cls.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/config.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/container_process.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/container_process.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/dict.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/dict.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/environments.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/environments.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/exception.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/experimental/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/experimental/flash.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/experimental/ipython.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/file_io.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/file_io.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/functions.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/functions.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/gpu.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/image.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/image.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/io_streams.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/io_streams.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/mount.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/mount.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/network_file_system.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/network_file_system.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/object.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/object.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/output.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/parallel_map.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/parallel_map.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/partial_function.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/partial_function.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/proxy.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/proxy.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/py.typed +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/queue.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/queue.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/retries.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/runner.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/runner.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/running_app.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/sandbox_fs.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/sandbox_fs.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/schedule.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/scheduler_placement.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/secret.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/secret.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/server.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/server.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/serving.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/serving.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/snapshot.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/snapshot.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/stream_type.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/token_flow.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/token_flow.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/volume.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal/volume.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/__init__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/api_grpc.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/api_pb2.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/py.typed +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/modal_version/__main__.py +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/pyproject.toml +0 -0
- {modal-1.3.6.dev9 → modal-1.3.6.dev11}/setup.cfg +0 -0
|
@@ -242,6 +242,44 @@ class TaskCommandRouterClient:
|
|
|
242
242
|
lambda: self._call_with_auth_retry(self._stub.TaskExecStart, request)
|
|
243
243
|
)
|
|
244
244
|
|
|
245
|
+
async def container_create(self, request: sr_pb2.TaskContainerCreateRequest) -> sr_pb2.TaskContainerCreateResponse:
|
|
246
|
+
"""Create an additional container via task command router."""
|
|
247
|
+
with grpc_error_converter():
|
|
248
|
+
return await call_with_retries_on_transient_errors(
|
|
249
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerCreate, request)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
async def container_terminate(
|
|
253
|
+
self,
|
|
254
|
+
request: sr_pb2.TaskContainerTerminateRequest,
|
|
255
|
+
) -> sr_pb2.TaskContainerTerminateResponse:
|
|
256
|
+
"""Terminate an additional container via task command router."""
|
|
257
|
+
with grpc_error_converter():
|
|
258
|
+
return await call_with_retries_on_transient_errors(
|
|
259
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerTerminate, request)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
async def container_wait(self, request: sr_pb2.TaskContainerWaitRequest) -> sr_pb2.TaskContainerWaitResponse:
|
|
263
|
+
"""Wait for an additional container via task command router."""
|
|
264
|
+
with grpc_error_converter():
|
|
265
|
+
return await call_with_retries_on_transient_errors(
|
|
266
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerWait, request)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
async def container_get(self, request: sr_pb2.TaskContainerGetRequest) -> sr_pb2.TaskContainerGetResponse:
|
|
270
|
+
"""Get the latest tracked container for a logical name via task command router."""
|
|
271
|
+
with grpc_error_converter():
|
|
272
|
+
return await call_with_retries_on_transient_errors(
|
|
273
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerGet, request)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
async def container_list(self, request: sr_pb2.TaskContainerListRequest) -> sr_pb2.TaskContainerListResponse:
|
|
277
|
+
"""List sandbox containers via task command router."""
|
|
278
|
+
with grpc_error_converter():
|
|
279
|
+
return await call_with_retries_on_transient_errors(
|
|
280
|
+
lambda: self._call_with_auth_retry(self._stub.TaskContainerList, request)
|
|
281
|
+
)
|
|
282
|
+
|
|
245
283
|
async def exec_stdio_read(
|
|
246
284
|
self,
|
|
247
285
|
task_id: str,
|
|
@@ -18,6 +18,7 @@ from ..mount import _Mount
|
|
|
18
18
|
from ..runner import interactive_shell
|
|
19
19
|
from ..sandbox import Sandbox
|
|
20
20
|
from ..secret import Secret
|
|
21
|
+
from ..stream_type import StreamType
|
|
21
22
|
from ..volume import Volume
|
|
22
23
|
from .container import exec
|
|
23
24
|
from .import_refs import (
|
|
@@ -64,26 +65,75 @@ def _is_valid_modal_id(ref: str, prefix: str) -> bool:
|
|
|
64
65
|
return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
def _parse_sandbox_container_ref(ref: str) -> tuple[str, Optional[str]]:
|
|
69
|
+
"""Parse a sandbox:container reference into (sandbox_id, container_name).
|
|
70
|
+
|
|
71
|
+
Supports the pattern `sb-xyz:container_name` for accessing sandbox containers.
|
|
72
|
+
Returns (ref, None) if no container name is specified.
|
|
73
|
+
"""
|
|
74
|
+
if _is_valid_modal_id(ref, "sb-"):
|
|
75
|
+
return ref, None
|
|
76
|
+
|
|
77
|
+
if ":" in ref:
|
|
78
|
+
parts = ref.split(":", 1)
|
|
79
|
+
sandbox_id, container_name = parts[0], parts[1]
|
|
80
|
+
if _is_valid_modal_id(sandbox_id, "sb-") and container_name:
|
|
81
|
+
return sandbox_id, container_name
|
|
82
|
+
|
|
83
|
+
return ref, None
|
|
84
|
+
|
|
85
|
+
|
|
67
86
|
def _is_running_container_ref(ref: Optional[str]) -> bool:
|
|
68
87
|
if ref is None:
|
|
69
88
|
return False
|
|
70
|
-
|
|
89
|
+
sandbox_id, _ = _parse_sandbox_container_ref(ref)
|
|
90
|
+
return _is_valid_modal_id(sandbox_id, "sb-") or _is_valid_modal_id(ref, "ta-")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _start_shell_in_sandbox_container(sandbox_id: str, container_name: str, cmd: str, pty: bool) -> None:
|
|
94
|
+
"""Shell into a named container within a sandbox."""
|
|
95
|
+
try:
|
|
96
|
+
sandbox = Sandbox.from_id(sandbox_id)
|
|
97
|
+
except NotFoundError:
|
|
98
|
+
raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
sandbox_container = sandbox._experimental_containers.get(name=container_name)
|
|
104
|
+
if pty:
|
|
105
|
+
process = sandbox_container.exec(*shlex.split(cmd), pty=pty)
|
|
106
|
+
process.attach()
|
|
107
|
+
else:
|
|
108
|
+
process = sandbox_container.exec(
|
|
109
|
+
*shlex.split(cmd), pty=pty, stdout=StreamType.STDOUT, stderr=StreamType.STDOUT
|
|
110
|
+
)
|
|
111
|
+
process.wait()
|
|
112
|
+
except NotFoundError:
|
|
113
|
+
raise ClickException(f"Container '{container_name}' not found in Sandbox '{sandbox_id}'.")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
raise ClickException(f"Error exec-ing into container '{container_name}' in Sandbox '{sandbox_id}': {str(e)}")
|
|
71
116
|
|
|
72
117
|
|
|
73
118
|
def _start_shell_in_running_container(ref: str, cmd: str, pty: bool) -> None:
|
|
74
|
-
|
|
119
|
+
sandbox_id, container_name = _parse_sandbox_container_ref(ref)
|
|
120
|
+
|
|
121
|
+
if container_name is not None:
|
|
122
|
+
return _start_shell_in_sandbox_container(sandbox_id, container_name, cmd, pty)
|
|
123
|
+
|
|
124
|
+
if _is_valid_modal_id(sandbox_id, "sb-"):
|
|
75
125
|
try:
|
|
76
|
-
sandbox = Sandbox.from_id(
|
|
126
|
+
sandbox = Sandbox.from_id(sandbox_id)
|
|
77
127
|
ref = sandbox._get_task_id()
|
|
78
|
-
except NotFoundError
|
|
79
|
-
raise ClickException(f"Sandbox '{
|
|
128
|
+
except NotFoundError:
|
|
129
|
+
raise ClickException(f"Sandbox '{sandbox_id}' not found (is it still running?)")
|
|
80
130
|
except Exception as e:
|
|
81
|
-
raise ClickException(f"Error connecting to Sandbox '{
|
|
131
|
+
raise ClickException(f"Error connecting to Sandbox '{sandbox_id}': {str(e)}")
|
|
82
132
|
|
|
83
133
|
assert _is_valid_modal_id(ref, "ta-")
|
|
84
134
|
try:
|
|
85
135
|
exec(container_id=ref, command=shlex.split(cmd), pty=pty)
|
|
86
|
-
except NotFoundError
|
|
136
|
+
except NotFoundError:
|
|
87
137
|
raise ClickException(f"Container '{ref}' not found (is it still running?)")
|
|
88
138
|
except Exception as e:
|
|
89
139
|
raise ClickException(f"Error connecting to container '{ref}': {str(e)}")
|
|
@@ -35,7 +35,7 @@ class _Client:
|
|
|
35
35
|
server_url: str,
|
|
36
36
|
client_type: int,
|
|
37
37
|
credentials: typing.Optional[tuple[str, str]],
|
|
38
|
-
version: str = "1.3.6.
|
|
38
|
+
version: str = "1.3.6.dev11",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -171,7 +171,7 @@ class Client:
|
|
|
171
171
|
server_url: str,
|
|
172
172
|
client_type: int,
|
|
173
173
|
credentials: typing.Optional[tuple[str, str]],
|
|
174
|
-
version: str = "1.3.6.
|
|
174
|
+
version: str = "1.3.6.dev11",
|
|
175
175
|
):
|
|
176
176
|
"""mdmd:hidden
|
|
177
177
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -35,7 +35,14 @@ from ._utils.name_utils import check_object_name
|
|
|
35
35
|
from ._utils.task_command_router_client import TaskCommandRouterClient
|
|
36
36
|
from .client import _Client
|
|
37
37
|
from .container_process import _ContainerProcess
|
|
38
|
-
from .exception import
|
|
38
|
+
from .exception import (
|
|
39
|
+
ClientClosed,
|
|
40
|
+
Error,
|
|
41
|
+
ExecutionError,
|
|
42
|
+
InvalidError,
|
|
43
|
+
SandboxTerminatedError,
|
|
44
|
+
SandboxTimeoutError,
|
|
45
|
+
)
|
|
39
46
|
from .file_io import FileWatchEvent, FileWatchEventType, _FileIO, ls, mkdir, rm, watch
|
|
40
47
|
from .gpu import GPU_T
|
|
41
48
|
from .image import _Image
|
|
@@ -68,6 +75,16 @@ if TYPE_CHECKING:
|
|
|
68
75
|
import modal.app
|
|
69
76
|
|
|
70
77
|
|
|
78
|
+
def _result_returncode(result: Optional[api_pb2.GenericResult]) -> Optional[int]:
|
|
79
|
+
if result is None or result.status == api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
|
|
80
|
+
return None
|
|
81
|
+
if result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
|
|
82
|
+
return 124
|
|
83
|
+
if result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
|
|
84
|
+
return 137
|
|
85
|
+
return result.exitcode
|
|
86
|
+
|
|
87
|
+
|
|
71
88
|
def _validate_exec_args(args: Sequence[str]) -> None:
|
|
72
89
|
# Entrypoint args must be strings.
|
|
73
90
|
if not all(isinstance(arg, str) for arg in args):
|
|
@@ -904,6 +921,12 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
904
921
|
self._command_router_client = await TaskCommandRouterClient.try_init(self._client, task_id)
|
|
905
922
|
return self._command_router_client
|
|
906
923
|
|
|
924
|
+
@property
|
|
925
|
+
def _experimental_containers(self) -> "_SandboxContainerManager":
|
|
926
|
+
"""Manage additional containers running in this Sandbox."""
|
|
927
|
+
self._ensure_attached()
|
|
928
|
+
return _SandboxContainerManager(self)
|
|
929
|
+
|
|
907
930
|
@overload
|
|
908
931
|
async def exec(
|
|
909
932
|
self,
|
|
@@ -1006,6 +1029,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1006
1029
|
secrets: Optional[Collection[_Secret]] = None,
|
|
1007
1030
|
text: bool = True,
|
|
1008
1031
|
bufsize: Literal[-1, 1] = -1,
|
|
1032
|
+
container_id: Optional[str] = None,
|
|
1009
1033
|
) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
|
|
1010
1034
|
"""Private method used internally.
|
|
1011
1035
|
|
|
@@ -1035,6 +1059,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1035
1059
|
"text": text,
|
|
1036
1060
|
"bufsize": bufsize,
|
|
1037
1061
|
"runtime_debug": config.get("function_runtime_debug"),
|
|
1062
|
+
"container_id": container_id,
|
|
1038
1063
|
}
|
|
1039
1064
|
# NB: This must come after the task ID is set, since the sandbox must be
|
|
1040
1065
|
# scheduled before we can create a router client.
|
|
@@ -1057,8 +1082,11 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1057
1082
|
text: bool = True,
|
|
1058
1083
|
bufsize: Literal[-1, 1] = -1,
|
|
1059
1084
|
runtime_debug: bool = False,
|
|
1085
|
+
container_id: Optional[str] = None,
|
|
1060
1086
|
) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
|
|
1061
1087
|
"""Execute a command through the Modal server."""
|
|
1088
|
+
if container_id:
|
|
1089
|
+
raise RuntimeError("Internal error: additional container exec requires task command router support")
|
|
1062
1090
|
req = api_pb2.ContainerExecRequest(
|
|
1063
1091
|
task_id=task_id,
|
|
1064
1092
|
command=args,
|
|
@@ -1097,6 +1125,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1097
1125
|
text: bool = True,
|
|
1098
1126
|
bufsize: Literal[-1, 1] = -1,
|
|
1099
1127
|
runtime_debug: bool = False,
|
|
1128
|
+
container_id: Optional[str] = None,
|
|
1100
1129
|
) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
|
|
1101
1130
|
"""Execute a command through a task command router running on the Modal worker."""
|
|
1102
1131
|
|
|
@@ -1134,6 +1163,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1134
1163
|
secret_ids=secret_ids,
|
|
1135
1164
|
pty_info=pty_info,
|
|
1136
1165
|
runtime_debug=runtime_debug,
|
|
1166
|
+
container_id=container_id or "",
|
|
1137
1167
|
)
|
|
1138
1168
|
_ = await command_router_client.exec_start(start_req)
|
|
1139
1169
|
|
|
@@ -1332,17 +1362,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1332
1362
|
@property
|
|
1333
1363
|
def returncode(self) -> Optional[int]:
|
|
1334
1364
|
"""Return code of the Sandbox process if it has finished running, else `None`."""
|
|
1335
|
-
|
|
1336
|
-
return None
|
|
1337
|
-
|
|
1338
|
-
# Statuses are converted to exitcodes so we can conform to subprocess API.
|
|
1339
|
-
# TODO: perhaps there should be a separate property that returns an enum directly?
|
|
1340
|
-
elif self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
|
|
1341
|
-
return 124
|
|
1342
|
-
elif self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
|
|
1343
|
-
return 137
|
|
1344
|
-
else:
|
|
1345
|
-
return self._result.exitcode
|
|
1365
|
+
return _result_returncode(self._result)
|
|
1346
1366
|
|
|
1347
1367
|
@staticmethod
|
|
1348
1368
|
async def list(
|
|
@@ -1382,4 +1402,224 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1382
1402
|
before_timestamp = resp.sandboxes[-1].created_at
|
|
1383
1403
|
|
|
1384
1404
|
|
|
1405
|
+
class _SandboxContainer:
|
|
1406
|
+
"""Handle to an additional container running in a Sandbox."""
|
|
1407
|
+
|
|
1408
|
+
_result: Optional[api_pb2.GenericResult]
|
|
1409
|
+
|
|
1410
|
+
def __init__(
|
|
1411
|
+
self,
|
|
1412
|
+
sandbox: _Sandbox,
|
|
1413
|
+
container_id: str,
|
|
1414
|
+
container_name: str,
|
|
1415
|
+
result: Optional[api_pb2.GenericResult] = None,
|
|
1416
|
+
) -> None:
|
|
1417
|
+
self._sandbox = sandbox
|
|
1418
|
+
self._container_id = container_id
|
|
1419
|
+
self._container_name = container_name
|
|
1420
|
+
self._result = result
|
|
1421
|
+
|
|
1422
|
+
@property
|
|
1423
|
+
def object_id(self) -> str:
|
|
1424
|
+
return self._container_id
|
|
1425
|
+
|
|
1426
|
+
@property
|
|
1427
|
+
def name(self) -> str:
|
|
1428
|
+
return self._container_name
|
|
1429
|
+
|
|
1430
|
+
@staticmethod
|
|
1431
|
+
def _from_container_info(sandbox: "_Sandbox", container_info: sr_pb2.TaskContainerInfo) -> "_SandboxContainer":
|
|
1432
|
+
result = container_info.result if container_info.HasField("result") else None
|
|
1433
|
+
return _SandboxContainer(sandbox, container_info.container_id, container_info.container_name, result)
|
|
1434
|
+
|
|
1435
|
+
async def _get_command_router(self) -> tuple[str, "TaskCommandRouterClient"]:
|
|
1436
|
+
"""Get task ID and command router client, raising if unavailable."""
|
|
1437
|
+
task_id = await self._sandbox._get_task_id()
|
|
1438
|
+
command_router_client = await self._sandbox._get_command_router_client(task_id)
|
|
1439
|
+
if command_router_client is None:
|
|
1440
|
+
raise RuntimeError("Internal error: additional container operations require task command router support")
|
|
1441
|
+
return task_id, command_router_client
|
|
1442
|
+
|
|
1443
|
+
async def exec(
|
|
1444
|
+
self,
|
|
1445
|
+
*args: str,
|
|
1446
|
+
stdout: StreamType = StreamType.PIPE,
|
|
1447
|
+
stderr: StreamType = StreamType.PIPE,
|
|
1448
|
+
timeout: Optional[int] = None,
|
|
1449
|
+
workdir: Optional[str] = None,
|
|
1450
|
+
env: Optional[dict[str, Optional[str]]] = None,
|
|
1451
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1452
|
+
text: bool = True,
|
|
1453
|
+
bufsize: Literal[-1, 1] = -1,
|
|
1454
|
+
pty: bool = False,
|
|
1455
|
+
) -> Union[_ContainerProcess[bytes], _ContainerProcess[str]]:
|
|
1456
|
+
pty_info = self._sandbox._default_pty_info() if pty else None
|
|
1457
|
+
return await self._sandbox._exec(
|
|
1458
|
+
*args,
|
|
1459
|
+
pty_info=pty_info,
|
|
1460
|
+
stdout=stdout,
|
|
1461
|
+
stderr=stderr,
|
|
1462
|
+
timeout=timeout,
|
|
1463
|
+
workdir=workdir,
|
|
1464
|
+
env=env,
|
|
1465
|
+
secrets=secrets,
|
|
1466
|
+
text=text,
|
|
1467
|
+
bufsize=bufsize,
|
|
1468
|
+
container_id=self._container_id,
|
|
1469
|
+
)
|
|
1470
|
+
|
|
1471
|
+
async def wait(self, raise_on_termination: bool = True) -> None:
|
|
1472
|
+
if self._result is not None and self._result.status != api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
|
|
1473
|
+
if self._result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED and raise_on_termination:
|
|
1474
|
+
raise SandboxTerminatedError()
|
|
1475
|
+
return
|
|
1476
|
+
|
|
1477
|
+
task_id, command_router_client = await self._get_command_router()
|
|
1478
|
+
while True:
|
|
1479
|
+
resp = await command_router_client.container_wait(
|
|
1480
|
+
sr_pb2.TaskContainerWaitRequest(
|
|
1481
|
+
task_id=task_id,
|
|
1482
|
+
container_id=self._container_id,
|
|
1483
|
+
timeout=10,
|
|
1484
|
+
)
|
|
1485
|
+
)
|
|
1486
|
+
if resp.result.status:
|
|
1487
|
+
self._result = resp.result
|
|
1488
|
+
if resp.result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED and raise_on_termination:
|
|
1489
|
+
raise SandboxTerminatedError()
|
|
1490
|
+
return
|
|
1491
|
+
|
|
1492
|
+
async def poll(self) -> Optional[int]:
|
|
1493
|
+
if self._result is not None and self._result.status != api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
|
|
1494
|
+
return _result_returncode(self._result)
|
|
1495
|
+
|
|
1496
|
+
task_id, command_router_client = await self._get_command_router()
|
|
1497
|
+
resp = await command_router_client.container_wait(
|
|
1498
|
+
sr_pb2.TaskContainerWaitRequest(
|
|
1499
|
+
task_id=task_id,
|
|
1500
|
+
container_id=self._container_id,
|
|
1501
|
+
timeout=0,
|
|
1502
|
+
)
|
|
1503
|
+
)
|
|
1504
|
+
if resp.result.status:
|
|
1505
|
+
self._result = resp.result
|
|
1506
|
+
return _result_returncode(self._result)
|
|
1507
|
+
|
|
1508
|
+
@overload
|
|
1509
|
+
async def terminate(
|
|
1510
|
+
self,
|
|
1511
|
+
*,
|
|
1512
|
+
wait: Literal[True],
|
|
1513
|
+
) -> int: ...
|
|
1514
|
+
|
|
1515
|
+
@overload
|
|
1516
|
+
async def terminate(
|
|
1517
|
+
self,
|
|
1518
|
+
*,
|
|
1519
|
+
wait: Literal[False] = False,
|
|
1520
|
+
) -> None: ...
|
|
1521
|
+
|
|
1522
|
+
async def terminate(
|
|
1523
|
+
self,
|
|
1524
|
+
*,
|
|
1525
|
+
wait: bool = False,
|
|
1526
|
+
) -> int | None:
|
|
1527
|
+
task_id, command_router_client = await self._get_command_router()
|
|
1528
|
+
await command_router_client.container_terminate(
|
|
1529
|
+
sr_pb2.TaskContainerTerminateRequest(
|
|
1530
|
+
task_id=task_id,
|
|
1531
|
+
container_id=self._container_id,
|
|
1532
|
+
)
|
|
1533
|
+
)
|
|
1534
|
+
if wait:
|
|
1535
|
+
await self.wait(raise_on_termination=False)
|
|
1536
|
+
return _result_returncode(self._result)
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
class _SandboxContainerManager:
|
|
1540
|
+
"""Creates and manages additional containers in a Sandbox."""
|
|
1541
|
+
|
|
1542
|
+
def __init__(self, sandbox: _Sandbox) -> None:
|
|
1543
|
+
self._sandbox = sandbox
|
|
1544
|
+
|
|
1545
|
+
async def _get_command_router(self) -> tuple[str, "TaskCommandRouterClient"]:
|
|
1546
|
+
"""Get task ID and command router client, raising if unavailable."""
|
|
1547
|
+
task_id = await self._sandbox._get_task_id()
|
|
1548
|
+
command_router_client = await self._sandbox._get_command_router_client(task_id)
|
|
1549
|
+
if command_router_client is None:
|
|
1550
|
+
raise RuntimeError("Internal error: additional container operations require task command router support")
|
|
1551
|
+
return task_id, command_router_client
|
|
1552
|
+
|
|
1553
|
+
async def create(
|
|
1554
|
+
self,
|
|
1555
|
+
*args: str,
|
|
1556
|
+
name: str,
|
|
1557
|
+
image: _Image,
|
|
1558
|
+
env: Optional[dict[str, str]] = None,
|
|
1559
|
+
secrets: Optional[Collection[_Secret]] = None,
|
|
1560
|
+
workdir: Optional[str] = None,
|
|
1561
|
+
) -> _SandboxContainer:
|
|
1562
|
+
if workdir is not None and not workdir.startswith("/"):
|
|
1563
|
+
raise InvalidError(f"workdir must be an absolute path, got: {workdir}")
|
|
1564
|
+
_validate_exec_args(args)
|
|
1565
|
+
|
|
1566
|
+
if image._mount_layers:
|
|
1567
|
+
raise InvalidError(
|
|
1568
|
+
"Sandbox._experimental_containers.create(image=...) only supports pre-built images. "
|
|
1569
|
+
"When using `add_local*` methods, specify `copy=True` and call `.build()` before passing "
|
|
1570
|
+
"the image to `._experimental_containers.create()`:\n\nE.g.\n"
|
|
1571
|
+
'img = modal.Image.debian_slim().add_local_file("foo", "/foo", copy=True).build(app)\n'
|
|
1572
|
+
'sandbox._experimental_containers.create(name="worker", image=img)'
|
|
1573
|
+
)
|
|
1574
|
+
if not image._object_id:
|
|
1575
|
+
raise InvalidError(
|
|
1576
|
+
"Sandbox._experimental_containers.create(image=...) currently only supports Images that are "
|
|
1577
|
+
"either:\n"
|
|
1578
|
+
"- prebuilt using `image.build()`\n"
|
|
1579
|
+
"- referenced by id, e.g. `Image.from_id()`\n"
|
|
1580
|
+
"- filesystem/directory snapshots e.g. created by `.snapshot_directory()` "
|
|
1581
|
+
"or `.snapshot_filesystem()`\n"
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
secrets = secrets or []
|
|
1585
|
+
secret_coros = [secret.hydrate(client=self._sandbox._client) for secret in secrets]
|
|
1586
|
+
await TaskContext.gather(*secret_coros)
|
|
1587
|
+
|
|
1588
|
+
task_id, command_router_client = await self._get_command_router()
|
|
1589
|
+
|
|
1590
|
+
create_req = sr_pb2.TaskContainerCreateRequest(
|
|
1591
|
+
task_id=task_id,
|
|
1592
|
+
container_name=name,
|
|
1593
|
+
image_id=image.object_id,
|
|
1594
|
+
args=list(args),
|
|
1595
|
+
env=env or {},
|
|
1596
|
+
workdir=workdir or "",
|
|
1597
|
+
secret_ids=[secret.object_id for secret in secrets],
|
|
1598
|
+
)
|
|
1599
|
+
create_resp = await command_router_client.container_create(create_req)
|
|
1600
|
+
container_id = create_resp.container_id
|
|
1601
|
+
container_name = create_resp.container_name or name
|
|
1602
|
+
return _SandboxContainer(self._sandbox, container_id, container_name)
|
|
1603
|
+
|
|
1604
|
+
async def get(self, *, name: str, include_terminated: bool = False) -> "_SandboxContainer":
|
|
1605
|
+
task_id, command_router_client = await self._get_command_router()
|
|
1606
|
+
resp = await command_router_client.container_get(
|
|
1607
|
+
sr_pb2.TaskContainerGetRequest(
|
|
1608
|
+
task_id=task_id,
|
|
1609
|
+
container_name=name,
|
|
1610
|
+
include_terminated=include_terminated,
|
|
1611
|
+
)
|
|
1612
|
+
)
|
|
1613
|
+
return _SandboxContainer._from_container_info(self._sandbox, resp.container)
|
|
1614
|
+
|
|
1615
|
+
async def list(self, include_terminated: bool = False) -> builtins.list[_SandboxContainer]:
|
|
1616
|
+
task_id, command_router_client = await self._get_command_router()
|
|
1617
|
+
resp = await command_router_client.container_list(
|
|
1618
|
+
sr_pb2.TaskContainerListRequest(task_id=task_id, include_terminated=include_terminated)
|
|
1619
|
+
)
|
|
1620
|
+
return [_SandboxContainer._from_container_info(self._sandbox, container) for container in resp.containers]
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
SandboxContainer = synchronize_api(_SandboxContainer)
|
|
1624
|
+
SandboxContainerManager = synchronize_api(_SandboxContainerManager)
|
|
1385
1625
|
Sandbox = synchronize_api(_Sandbox)
|