modal 1.4.1.dev1__tar.gz → 1.4.1.dev3__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.4.1.dev1 → modal-1.4.1.dev3}/PKG-INFO +1 -1
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/__init__.py +2 -1
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/sandbox_fs_utils.py +13 -1
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/run.py +4 -2
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/client.py +4 -1
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/client.pyi +2 -2
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/config.py +30 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/functions.pyi +6 -6
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox.py +108 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox.pyi +127 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_version/__init__.py +1 -1
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/LICENSE +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/README.md +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/__main__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_billing.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_clustered_functions.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_functions.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_grpc_client.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_ipython.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_load_context.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_location.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_logs.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_object.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/manager.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/pty.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/rich.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_output/status.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_partial_function.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_resolver.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_resources.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_serialization.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_server.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_traceback.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_tunnel.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_tunnel.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_type_manager.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/logger.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_vendor/version.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/_watcher.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/app.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/app.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/billing.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/README.md +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/builder/base-images.json +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/call_graph.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/_download.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/_traceback.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/app.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/billing.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/changelog.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/cluster.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/config.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/container.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/dashboard.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/dict.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/entry_point.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/environment.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/import_refs.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/launch.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/profile.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/queues.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/secret.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/selector.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/shell.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/token.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/utils.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cli/volume.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cls.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/cls.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/container_process.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/container_process.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/dict.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/dict.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/environments.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/environments.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/exception.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/flash.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/experimental/ipython.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/file_io.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/file_io.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/functions.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/image.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/image.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/io_streams.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/io_streams.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/mount.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/mount.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/network_file_system.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/network_file_system.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/object.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/object.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/output.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/parallel_map.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/parallel_map.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/partial_function.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/partial_function.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/proxy.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/proxy.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/py.typed +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/queue.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/queue.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/retries.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/runner.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/runner.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/running_app.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox_fs.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/sandbox_fs.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/schedule.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/scheduler_placement.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/secret.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/secret.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/server.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/server.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/serving.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/serving.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/snapshot.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/snapshot.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/stream_type.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/token_flow.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/token_flow.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/volume.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal/volume.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/__init__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_grpc.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/py.typed +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/modal_version/__main__.py +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/pyproject.toml +0 -0
- {modal-1.4.1.dev1 → modal-1.4.1.dev3}/setup.cfg +0 -0
|
@@ -38,7 +38,7 @@ try:
|
|
|
38
38
|
from .proxy import Proxy
|
|
39
39
|
from .queue import Queue
|
|
40
40
|
from .retries import Retries
|
|
41
|
-
from .sandbox import Sandbox
|
|
41
|
+
from .sandbox import Probe, Sandbox
|
|
42
42
|
from .schedule import Cron, Period
|
|
43
43
|
from .scheduler_placement import SchedulerPlacement
|
|
44
44
|
from .secret import Secret
|
|
@@ -68,6 +68,7 @@ __all__ = [
|
|
|
68
68
|
"Image",
|
|
69
69
|
"NetworkFileSystem",
|
|
70
70
|
"Period",
|
|
71
|
+
"Probe",
|
|
71
72
|
"Proxy",
|
|
72
73
|
"Queue",
|
|
73
74
|
"Retries",
|
|
@@ -28,6 +28,7 @@ _EXEC_SANDBOX_UNAVAILABLE_ERROR_TYPES = (NotFoundError, ServiceError, Connection
|
|
|
28
28
|
class ErrorPayload:
|
|
29
29
|
error_kind: str
|
|
30
30
|
message: str
|
|
31
|
+
detail: str = ""
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def _stderr_to_text(stderr: Union[str, bytes]) -> str:
|
|
@@ -51,11 +52,18 @@ def try_parse_error_payload(stderr: Union[str, bytes]) -> Optional[ErrorPayload]
|
|
|
51
52
|
return None
|
|
52
53
|
if not isinstance(message, str) or not message.strip():
|
|
53
54
|
return None
|
|
54
|
-
|
|
55
|
+
detail = payload.get("detail", "")
|
|
56
|
+
if not isinstance(detail, str):
|
|
57
|
+
detail = ""
|
|
58
|
+
return ErrorPayload(error_kind=error_kind, message=message, detail=detail)
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
def raise_read_file_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
|
|
58
62
|
if payload := try_parse_error_payload(stderr):
|
|
63
|
+
logger.debug(
|
|
64
|
+
f"sandbox-fs-tools read error: path={remote_path}, "
|
|
65
|
+
f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
|
|
66
|
+
)
|
|
59
67
|
if payload.error_kind == "NotFound":
|
|
60
68
|
raise SandboxFilesystemNotFoundError(f"{payload.message}: {remote_path}")
|
|
61
69
|
if payload.error_kind == "IsDirectory":
|
|
@@ -106,6 +114,10 @@ def translate_exec_unexpected_error(operation: str, path: str, exc: Exception) -
|
|
|
106
114
|
|
|
107
115
|
def raise_write_file_error(returncode: int, stderr: Union[str, bytes], remote_path: str) -> NoReturn:
|
|
108
116
|
if payload := try_parse_error_payload(stderr):
|
|
117
|
+
logger.debug(
|
|
118
|
+
f"sandbox-fs-tools write error: path={remote_path}, "
|
|
119
|
+
f"error_kind={payload.error_kind}, message={payload.message}, detail={payload.detail}"
|
|
120
|
+
)
|
|
109
121
|
if payload.error_kind == "NotDirectory" or payload.error_kind == "AlreadyExists":
|
|
110
122
|
raise SandboxFilesystemNotADirectoryError(f"{payload.message}: {remote_path}")
|
|
111
123
|
if payload.error_kind == "IsDirectory":
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
import time
|
|
8
8
|
import typing
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from typing import Any, Callable, Optional
|
|
10
|
+
from typing import Any, Callable, Optional, get_args
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
import typer
|
|
@@ -504,7 +504,9 @@ def deploy(
|
|
|
504
504
|
False, "-m", help="Interpret argument as a Python module path instead of a file/script path"
|
|
505
505
|
),
|
|
506
506
|
timestamps: bool = typer.Option(False, "--timestamps", help="Show timestamps for each log line."),
|
|
507
|
-
strategy:
|
|
507
|
+
strategy: str = typer.Option(
|
|
508
|
+
"rolling", help="Deployment strategy", click_type=click.Choice(get_args(DEPLOYMENT_STRATEGY_TYPE))
|
|
509
|
+
),
|
|
508
510
|
):
|
|
509
511
|
"""Deploy a Modal application.
|
|
510
512
|
|
|
@@ -22,7 +22,7 @@ from ._utils import async_utils
|
|
|
22
22
|
from ._utils.async_utils import TaskContext, synchronize_api
|
|
23
23
|
from ._utils.auth_token_manager import _AuthTokenManager
|
|
24
24
|
from ._utils.grpc_utils import ConnectionManager
|
|
25
|
-
from .config import _check_config, _is_remote, config, logger
|
|
25
|
+
from .config import _agent_environment, _check_config, _is_remote, config, logger
|
|
26
26
|
from .exception import AuthError, ClientClosed
|
|
27
27
|
|
|
28
28
|
HEARTBEAT_INTERVAL: float = config.get("heartbeat_interval")
|
|
@@ -56,6 +56,9 @@ def _get_metadata(client_type: int, credentials: Optional[tuple[str, str]], vers
|
|
|
56
56
|
"x-modal-token-secret": token_secret,
|
|
57
57
|
}
|
|
58
58
|
)
|
|
59
|
+
agent_env = _agent_environment()
|
|
60
|
+
if agent_env:
|
|
61
|
+
metadata["x-modal-agent-harness"] = urllib.parse.quote(agent_env)
|
|
59
62
|
return metadata
|
|
60
63
|
|
|
61
64
|
|
|
@@ -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.4.1.
|
|
38
|
+
version: str = "1.4.1.dev3",
|
|
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.4.1.
|
|
174
|
+
version: str = "1.4.1.dev3",
|
|
175
175
|
):
|
|
176
176
|
"""mdmd:hidden
|
|
177
177
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -121,6 +121,36 @@ def _is_remote() -> bool:
|
|
|
121
121
|
return os.environ.get("MODAL_IS_REMOTE") == "1"
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def _agent_environment() -> Optional[str]:
|
|
125
|
+
"""Detect if the current process is running inside an AI agent harness.
|
|
126
|
+
|
|
127
|
+
Returns the agent name if detected, or None otherwise.
|
|
128
|
+
|
|
129
|
+
Checks the emerging `AGENT` standard variable first, then falls back to
|
|
130
|
+
tool-specific environment variables used by known agent harnesses.
|
|
131
|
+
"""
|
|
132
|
+
# Emerging standard: AGENT=<name>
|
|
133
|
+
if agent := os.environ.get("AGENT"):
|
|
134
|
+
return agent
|
|
135
|
+
|
|
136
|
+
# Tool-specific environment variables
|
|
137
|
+
_TOOL_SPECIFIC_ENV_VARS: list[tuple[str, str]] = [
|
|
138
|
+
("CLAUDECODE", "claude-code"),
|
|
139
|
+
("GEMINI_CLI", "gemini-cli"),
|
|
140
|
+
("CURSOR_AGENT", "cursor"),
|
|
141
|
+
("CLINE_ACTIVE", "cline"),
|
|
142
|
+
("AUGMENT_AGENT", "augment"),
|
|
143
|
+
("OPENCODE_CLIENT", "opencode"),
|
|
144
|
+
("GOOSE_TERMINAL", "goose"),
|
|
145
|
+
("TRAE_AI_SHELL_ID", "trae"),
|
|
146
|
+
]
|
|
147
|
+
for env_var, agent_name in _TOOL_SPECIFIC_ENV_VARS:
|
|
148
|
+
if os.environ.get(env_var):
|
|
149
|
+
return agent_name
|
|
150
|
+
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
124
154
|
def _read_user_config():
|
|
125
155
|
config_data = {}
|
|
126
156
|
if not _is_remote() and os.path.exists(user_config_path):
|
|
@@ -348,7 +348,7 @@ class Function(
|
|
|
348
348
|
|
|
349
349
|
_call_generator: ___call_generator_spec
|
|
350
350
|
|
|
351
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
351
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
352
352
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
353
353
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
354
354
|
...
|
|
@@ -357,7 +357,7 @@ class Function(
|
|
|
357
357
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
358
358
|
...
|
|
359
359
|
|
|
360
|
-
remote: __remote_spec[modal._functions.
|
|
360
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
361
361
|
|
|
362
362
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
363
363
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -384,7 +384,7 @@ class Function(
|
|
|
384
384
|
"""
|
|
385
385
|
...
|
|
386
386
|
|
|
387
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
387
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
388
388
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
389
389
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
390
390
|
|
|
@@ -407,7 +407,7 @@ class Function(
|
|
|
407
407
|
"""
|
|
408
408
|
...
|
|
409
409
|
|
|
410
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
410
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
411
411
|
|
|
412
412
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
413
413
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -415,7 +415,7 @@ class Function(
|
|
|
415
415
|
|
|
416
416
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
417
417
|
|
|
418
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
418
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
419
419
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
420
420
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
421
421
|
|
|
@@ -436,7 +436,7 @@ class Function(
|
|
|
436
436
|
"""
|
|
437
437
|
...
|
|
438
438
|
|
|
439
|
-
spawn: __spawn_spec[modal._functions.
|
|
439
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
440
440
|
|
|
441
441
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
442
442
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -42,6 +42,7 @@ from .exception import (
|
|
|
42
42
|
InvalidError,
|
|
43
43
|
SandboxTerminatedError,
|
|
44
44
|
SandboxTimeoutError,
|
|
45
|
+
TimeoutError,
|
|
45
46
|
)
|
|
46
47
|
from .file_io import FileWatchEvent, FileWatchEventType, _FileIO, ls, mkdir, rm, watch
|
|
47
48
|
from .image import _Image
|
|
@@ -118,6 +119,73 @@ class SandboxConnectCredentials:
|
|
|
118
119
|
token: str
|
|
119
120
|
|
|
120
121
|
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class Probe:
|
|
124
|
+
"""Probe configuration for the Sandbox Readiness Probe.
|
|
125
|
+
|
|
126
|
+
Usage:
|
|
127
|
+
```py notest
|
|
128
|
+
# Wait until a file exists.
|
|
129
|
+
readiness_probe = modal.Probe.with_exec(
|
|
130
|
+
"sh", "-c", "test -f /tmp/ready",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Wait until a TCP port is accepting connections.
|
|
134
|
+
readiness_probe = modal.Probe.with_tcp(8080)
|
|
135
|
+
|
|
136
|
+
app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
|
|
137
|
+
sandbox = modal.Sandbox.create(
|
|
138
|
+
"python3", "-m", "http.server", "8080",
|
|
139
|
+
readiness_probe=readiness_probe,
|
|
140
|
+
app=app,
|
|
141
|
+
)
|
|
142
|
+
sandbox.wait_until_ready()
|
|
143
|
+
```
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
tcp_port: Optional[int] = None
|
|
147
|
+
exec_argv: Optional[tuple[str, ...]] = None
|
|
148
|
+
interval_ms: int = 100
|
|
149
|
+
|
|
150
|
+
def __post_init__(self):
|
|
151
|
+
if (self.tcp_port is None) == (self.exec_argv is None):
|
|
152
|
+
raise InvalidError("Probe must be created with Probe.with_tcp(...) or Probe.with_exec(...)")
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def with_tcp(cls, port: int, *, interval_ms: int = 100) -> "Probe":
|
|
156
|
+
if not isinstance(port, int):
|
|
157
|
+
raise InvalidError("Probe.with_tcp() expects an integer `port`")
|
|
158
|
+
if port <= 0 or port > 65535:
|
|
159
|
+
raise InvalidError(f"Probe.with_tcp() expects `port` in [1, 65535], got {port}")
|
|
160
|
+
if not isinstance(interval_ms, int):
|
|
161
|
+
raise InvalidError("Probe.with_tcp() expects an integer `interval_ms`")
|
|
162
|
+
if interval_ms <= 0:
|
|
163
|
+
raise InvalidError(f"Probe.with_tcp() expects `interval_ms` > 0, got {interval_ms}")
|
|
164
|
+
return cls(tcp_port=port, interval_ms=interval_ms)
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def with_exec(cls, *argv: str, interval_ms: int = 100) -> "Probe":
|
|
168
|
+
if len(argv) == 0:
|
|
169
|
+
raise InvalidError("Probe.with_exec() requires at least one argument")
|
|
170
|
+
if not all(isinstance(arg, str) for arg in argv):
|
|
171
|
+
raise InvalidError("Probe.with_exec() expects all arguments to be strings")
|
|
172
|
+
if not isinstance(interval_ms, int):
|
|
173
|
+
raise InvalidError("Probe.with_exec() expects an integer `interval_ms`")
|
|
174
|
+
if interval_ms <= 0:
|
|
175
|
+
raise InvalidError(f"Probe.with_exec() expects `interval_ms` > 0, got {interval_ms}")
|
|
176
|
+
return cls(exec_argv=tuple(argv), interval_ms=interval_ms)
|
|
177
|
+
|
|
178
|
+
def _to_proto(self) -> api_pb2.Probe:
|
|
179
|
+
if self.tcp_port is not None:
|
|
180
|
+
return api_pb2.Probe(tcp_port=self.tcp_port, interval_ms=self.interval_ms)
|
|
181
|
+
if self.exec_argv is not None:
|
|
182
|
+
return api_pb2.Probe(
|
|
183
|
+
exec_command=api_pb2.Probe.ExecCommand(argv=list(self.exec_argv)),
|
|
184
|
+
interval_ms=self.interval_ms,
|
|
185
|
+
)
|
|
186
|
+
raise InvalidError("Probe must be created with Probe.with_tcp(...) or Probe.with_exec(...)")
|
|
187
|
+
|
|
188
|
+
|
|
121
189
|
class _Sandbox(_Object, type_prefix="sb"):
|
|
122
190
|
"""A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
|
|
123
191
|
[asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
|
|
@@ -166,6 +234,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
166
234
|
h2_ports: Sequence[int] = [],
|
|
167
235
|
unencrypted_ports: Sequence[int] = [],
|
|
168
236
|
proxy: Optional[_Proxy] = None,
|
|
237
|
+
readiness_probe: Optional[Probe] = None,
|
|
169
238
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
170
239
|
enable_snapshot: bool = False,
|
|
171
240
|
verbose: bool = False,
|
|
@@ -279,6 +348,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
279
348
|
open_ports=api_pb2.PortSpecs(ports=open_ports),
|
|
280
349
|
network_access=network_access,
|
|
281
350
|
proxy_id=(proxy.object_id if proxy else None),
|
|
351
|
+
readiness_probe=(readiness_probe._to_proto() if readiness_probe else None),
|
|
282
352
|
enable_snapshot=enable_snapshot,
|
|
283
353
|
verbose=verbose,
|
|
284
354
|
name=name,
|
|
@@ -339,6 +409,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
339
409
|
proxy: Optional[_Proxy] = None,
|
|
340
410
|
# If True, the sandbox will receive a MODAL_IDENTITY_TOKEN env var for OIDC-based auth.
|
|
341
411
|
include_oidc_identity_token: bool = False,
|
|
412
|
+
# Probe used to determine when the sandbox has become ready.
|
|
413
|
+
readiness_probe: Optional[Probe] = None,
|
|
342
414
|
# Enable verbose logging for sandbox operations.
|
|
343
415
|
verbose: bool = False,
|
|
344
416
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
@@ -403,6 +475,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
403
475
|
h2_ports=h2_ports,
|
|
404
476
|
unencrypted_ports=unencrypted_ports,
|
|
405
477
|
proxy=proxy,
|
|
478
|
+
readiness_probe=readiness_probe,
|
|
406
479
|
experimental_options=experimental_options,
|
|
407
480
|
_experimental_enable_snapshot=_experimental_enable_snapshot,
|
|
408
481
|
include_oidc_identity_token=include_oidc_identity_token,
|
|
@@ -439,6 +512,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
439
512
|
unencrypted_ports: Sequence[int] = [],
|
|
440
513
|
proxy: Optional[_Proxy] = None,
|
|
441
514
|
include_oidc_identity_token: bool = False,
|
|
515
|
+
readiness_probe: Optional[Probe] = None,
|
|
442
516
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
443
517
|
_experimental_enable_snapshot: bool = False,
|
|
444
518
|
client: Optional[_Client] = None,
|
|
@@ -490,6 +564,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
490
564
|
h2_ports=h2_ports,
|
|
491
565
|
unencrypted_ports=unencrypted_ports,
|
|
492
566
|
proxy=proxy,
|
|
567
|
+
readiness_probe=readiness_probe,
|
|
493
568
|
experimental_options=experimental_options,
|
|
494
569
|
enable_snapshot=_experimental_enable_snapshot,
|
|
495
570
|
verbose=verbose,
|
|
@@ -958,6 +1033,39 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
958
1033
|
raise SandboxTerminatedError()
|
|
959
1034
|
break
|
|
960
1035
|
|
|
1036
|
+
async def wait_until_ready(self, *, timeout: int = 300) -> None:
|
|
1037
|
+
"""Wait for the Sandbox readiness probe to report that the Sandbox is ready.
|
|
1038
|
+
|
|
1039
|
+
The Sandbox must be configured with a `readiness_probe` in order to use this method.
|
|
1040
|
+
|
|
1041
|
+
Usage:
|
|
1042
|
+
```py notest
|
|
1043
|
+
app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
|
|
1044
|
+
sandbox = modal.Sandbox.create(
|
|
1045
|
+
"python3", "-m", "http.server", "8080",
|
|
1046
|
+
readiness_probe=modal.Probe.with_tcp(8080),
|
|
1047
|
+
app=app,
|
|
1048
|
+
)
|
|
1049
|
+
sandbox.wait_until_ready()
|
|
1050
|
+
```
|
|
1051
|
+
"""
|
|
1052
|
+
if timeout <= 0:
|
|
1053
|
+
raise InvalidError(f"`timeout` must be positive, got: {timeout}")
|
|
1054
|
+
|
|
1055
|
+
deadline = time.monotonic() + timeout
|
|
1056
|
+
remaining_timeout = deadline - time.monotonic()
|
|
1057
|
+
while remaining_timeout > 0:
|
|
1058
|
+
req = api_pb2.SandboxWaitUntilReadyRequest(
|
|
1059
|
+
sandbox_id=self.object_id,
|
|
1060
|
+
timeout=min(remaining_timeout, 50.0),
|
|
1061
|
+
)
|
|
1062
|
+
resp = await self._client.stub.SandboxWaitUntilReady(req)
|
|
1063
|
+
if resp.ready_at > 0:
|
|
1064
|
+
return
|
|
1065
|
+
|
|
1066
|
+
remaining_timeout = deadline - time.monotonic()
|
|
1067
|
+
raise TimeoutError()
|
|
1068
|
+
|
|
961
1069
|
async def tunnels(self, timeout: int = 50) -> dict[int, Tunnel]:
|
|
962
1070
|
"""Get Tunnel metadata for the sandbox.
|
|
963
1071
|
|
|
@@ -71,6 +71,68 @@ class SandboxConnectCredentials:
|
|
|
71
71
|
"""Return hash(self)."""
|
|
72
72
|
...
|
|
73
73
|
|
|
74
|
+
class Probe:
|
|
75
|
+
"""Probe configuration for the Sandbox Readiness Probe.
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
```py notest
|
|
79
|
+
# Wait until a file exists.
|
|
80
|
+
readiness_probe = modal.Probe.with_exec(
|
|
81
|
+
"sh", "-c", "test -f /tmp/ready",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Wait until a TCP port is accepting connections.
|
|
85
|
+
readiness_probe = modal.Probe.with_tcp(8080)
|
|
86
|
+
|
|
87
|
+
app = modal.App.lookup('sandbox-readiness-probe', create_if_missing=True)
|
|
88
|
+
sandbox = modal.Sandbox.create(
|
|
89
|
+
"python3", "-m", "http.server", "8080",
|
|
90
|
+
readiness_probe=readiness_probe,
|
|
91
|
+
app=app,
|
|
92
|
+
)
|
|
93
|
+
sandbox.wait_until_ready()
|
|
94
|
+
```
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
tcp_port: typing.Optional[int]
|
|
98
|
+
exec_argv: typing.Optional[tuple[str, ...]]
|
|
99
|
+
interval_ms: int
|
|
100
|
+
|
|
101
|
+
def __post_init__(self): ...
|
|
102
|
+
@classmethod
|
|
103
|
+
def with_tcp(cls, port: int, *, interval_ms: int = 100) -> Probe: ...
|
|
104
|
+
@classmethod
|
|
105
|
+
def with_exec(cls, *argv: str, interval_ms: int = 100) -> Probe: ...
|
|
106
|
+
def _to_proto(self) -> modal_proto.api_pb2.Probe: ...
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
tcp_port: typing.Optional[int] = None,
|
|
110
|
+
exec_argv: typing.Optional[tuple[str, ...]] = None,
|
|
111
|
+
interval_ms: int = 100,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
def __repr__(self):
|
|
117
|
+
"""Return repr(self)."""
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
def __eq__(self, other):
|
|
121
|
+
"""Return self==value."""
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
def __setattr__(self, name, value):
|
|
125
|
+
"""Implement setattr(self, name, value)."""
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
def __delattr__(self, name):
|
|
129
|
+
"""Implement delattr(self, name)."""
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
def __hash__(self):
|
|
133
|
+
"""Return hash(self)."""
|
|
134
|
+
...
|
|
135
|
+
|
|
74
136
|
class _Sandbox(modal._object._Object):
|
|
75
137
|
"""A `Sandbox` object lets you interact with a running sandbox. This API is similar to Python's
|
|
76
138
|
[asyncio.subprocess.Process](https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process).
|
|
@@ -120,6 +182,7 @@ class _Sandbox(modal._object._Object):
|
|
|
120
182
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
121
183
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
122
184
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
185
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
123
186
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
124
187
|
enable_snapshot: bool = False,
|
|
125
188
|
verbose: bool = False,
|
|
@@ -159,6 +222,7 @@ class _Sandbox(modal._object._Object):
|
|
|
159
222
|
custom_domain: typing.Optional[str] = None,
|
|
160
223
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
161
224
|
include_oidc_identity_token: bool = False,
|
|
225
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
162
226
|
verbose: bool = False,
|
|
163
227
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
164
228
|
_experimental_enable_snapshot: bool = False,
|
|
@@ -211,6 +275,7 @@ class _Sandbox(modal._object._Object):
|
|
|
211
275
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
212
276
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
213
277
|
include_oidc_identity_token: bool = False,
|
|
278
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
214
279
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
215
280
|
_experimental_enable_snapshot: bool = False,
|
|
216
281
|
client: typing.Optional[modal.client._Client] = None,
|
|
@@ -360,6 +425,24 @@ class _Sandbox(modal._object._Object):
|
|
|
360
425
|
"""Wait for the Sandbox to finish running."""
|
|
361
426
|
...
|
|
362
427
|
|
|
428
|
+
async def wait_until_ready(self, *, timeout: int = 300) -> None:
|
|
429
|
+
"""Wait for the Sandbox readiness probe to report that the Sandbox is ready.
|
|
430
|
+
|
|
431
|
+
The Sandbox must be configured with a `readiness_probe` in order to use this method.
|
|
432
|
+
|
|
433
|
+
Usage:
|
|
434
|
+
```py notest
|
|
435
|
+
app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
|
|
436
|
+
sandbox = modal.Sandbox.create(
|
|
437
|
+
"python3", "-m", "http.server", "8080",
|
|
438
|
+
readiness_probe=modal.Probe.with_tcp(8080),
|
|
439
|
+
app=app,
|
|
440
|
+
)
|
|
441
|
+
sandbox.wait_until_ready()
|
|
442
|
+
```
|
|
443
|
+
"""
|
|
444
|
+
...
|
|
445
|
+
|
|
363
446
|
async def tunnels(self, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]:
|
|
364
447
|
"""Get Tunnel metadata for the sandbox.
|
|
365
448
|
|
|
@@ -843,6 +926,7 @@ class Sandbox(modal.object.Object):
|
|
|
843
926
|
h2_ports: collections.abc.Sequence[int] = [],
|
|
844
927
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
845
928
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
929
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
846
930
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
847
931
|
enable_snapshot: bool = False,
|
|
848
932
|
verbose: bool = False,
|
|
@@ -886,6 +970,7 @@ class Sandbox(modal.object.Object):
|
|
|
886
970
|
custom_domain: typing.Optional[str] = None,
|
|
887
971
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
888
972
|
include_oidc_identity_token: bool = False,
|
|
973
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
889
974
|
verbose: bool = False,
|
|
890
975
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
891
976
|
_experimental_enable_snapshot: bool = False,
|
|
@@ -941,6 +1026,7 @@ class Sandbox(modal.object.Object):
|
|
|
941
1026
|
custom_domain: typing.Optional[str] = None,
|
|
942
1027
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
943
1028
|
include_oidc_identity_token: bool = False,
|
|
1029
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
944
1030
|
verbose: bool = False,
|
|
945
1031
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
946
1032
|
_experimental_enable_snapshot: bool = False,
|
|
@@ -999,6 +1085,7 @@ class Sandbox(modal.object.Object):
|
|
|
999
1085
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
1000
1086
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
1001
1087
|
include_oidc_identity_token: bool = False,
|
|
1088
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
1002
1089
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
1003
1090
|
_experimental_enable_snapshot: bool = False,
|
|
1004
1091
|
client: typing.Optional[modal.client.Client] = None,
|
|
@@ -1047,6 +1134,7 @@ class Sandbox(modal.object.Object):
|
|
|
1047
1134
|
unencrypted_ports: collections.abc.Sequence[int] = [],
|
|
1048
1135
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
1049
1136
|
include_oidc_identity_token: bool = False,
|
|
1137
|
+
readiness_probe: typing.Optional[Probe] = None,
|
|
1050
1138
|
experimental_options: typing.Optional[dict[str, bool]] = None,
|
|
1051
1139
|
_experimental_enable_snapshot: bool = False,
|
|
1052
1140
|
client: typing.Optional[modal.client.Client] = None,
|
|
@@ -1357,6 +1445,45 @@ class Sandbox(modal.object.Object):
|
|
|
1357
1445
|
|
|
1358
1446
|
wait: __wait_spec
|
|
1359
1447
|
|
|
1448
|
+
class __wait_until_ready_spec(typing_extensions.Protocol):
|
|
1449
|
+
def __call__(self, /, *, timeout: int = 300) -> None:
|
|
1450
|
+
"""Wait for the Sandbox readiness probe to report that the Sandbox is ready.
|
|
1451
|
+
|
|
1452
|
+
The Sandbox must be configured with a `readiness_probe` in order to use this method.
|
|
1453
|
+
|
|
1454
|
+
Usage:
|
|
1455
|
+
```py notest
|
|
1456
|
+
app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
|
|
1457
|
+
sandbox = modal.Sandbox.create(
|
|
1458
|
+
"python3", "-m", "http.server", "8080",
|
|
1459
|
+
readiness_probe=modal.Probe.with_tcp(8080),
|
|
1460
|
+
app=app,
|
|
1461
|
+
)
|
|
1462
|
+
sandbox.wait_until_ready()
|
|
1463
|
+
```
|
|
1464
|
+
"""
|
|
1465
|
+
...
|
|
1466
|
+
|
|
1467
|
+
async def aio(self, /, *, timeout: int = 300) -> None:
|
|
1468
|
+
"""Wait for the Sandbox readiness probe to report that the Sandbox is ready.
|
|
1469
|
+
|
|
1470
|
+
The Sandbox must be configured with a `readiness_probe` in order to use this method.
|
|
1471
|
+
|
|
1472
|
+
Usage:
|
|
1473
|
+
```py notest
|
|
1474
|
+
app = modal.App.lookup('sandbox-wait-until-ready', create_if_missing=True)
|
|
1475
|
+
sandbox = modal.Sandbox.create(
|
|
1476
|
+
"python3", "-m", "http.server", "8080",
|
|
1477
|
+
readiness_probe=modal.Probe.with_tcp(8080),
|
|
1478
|
+
app=app,
|
|
1479
|
+
)
|
|
1480
|
+
sandbox.wait_until_ready()
|
|
1481
|
+
```
|
|
1482
|
+
"""
|
|
1483
|
+
...
|
|
1484
|
+
|
|
1485
|
+
wait_until_ready: __wait_until_ready_spec
|
|
1486
|
+
|
|
1360
1487
|
class __tunnels_spec(typing_extensions.Protocol):
|
|
1361
1488
|
def __call__(self, /, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]:
|
|
1362
1489
|
"""Get Tunnel metadata for the sandbox.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|