modal 1.5.3.dev5__tar.gz → 1.5.3.dev6__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.dev5 → modal-1.5.3.dev6}/PKG-INFO +1 -1
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_functions.py +11 -15
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/task_command_router_client.py +127 -3
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/client.pyi +2 -2
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/container_process.py +13 -1
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/container_process.pyi +27 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/functions.pyi +20 -20
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox.py +29 -10
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox.pyi +6 -2
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox_fs.py +32 -80
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/sandbox_fs.pyi +24 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_reference_docs.py +0 -1
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/mdmd.py +9 -2
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/types.py +1 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_version/__init__.py +1 -1
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/LICENSE +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/README.md +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/__main__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_billing.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_clustered_functions.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_environments.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_function_variants.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_grpc_client.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_image.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_ipython.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_load_context.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_location.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_logs.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_object.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/manager.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/pty.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/rich.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_output/status.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_partial_function.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_resolver.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_resources.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_serialization.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_server.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_traceback.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_tunnel.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_tunnel.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_type_manager.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/logger.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_vendor/version.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_watcher.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/_workspace.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/app.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/app.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/billing.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/billing.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/README.md +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/builder/base-images.json +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/call_graph.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/_download.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/_help.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/_traceback.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/app.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/billing.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/changelog.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/cluster.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/config.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/container.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/curl.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/dashboard.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/dict.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/endpoint.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/entry_point.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/environment.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/image.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/import_refs.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/launch.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/logo.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/profile.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/queues.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/run.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/secret.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/selector.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/shell.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/skills.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/token.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/utils.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/volume.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cli/workspace.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/client.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cls.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/cls.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/config.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/dict.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/dict.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/environments.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/environments.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/exception.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/flash.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/experimental/ipython.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/file_io.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/file_io.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/functions.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/image.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/image.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/io_streams.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/io_streams.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/mount.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/mount.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/network_file_system.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/network_file_system.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/object.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/object.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/output.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/parallel_map.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/parallel_map.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/partial_function.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/partial_function.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/proxy.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/proxy.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/py.typed +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/queue.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/queue.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/retries.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/runner.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/runner.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/running_app.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/schedule.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/scheduler_placement.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/secret.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/secret.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/server.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/server.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/serving.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/serving.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/snapshot.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/snapshot.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/stream_type.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/token_flow.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/token_flow.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/types.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/volume.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/volume.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/workspace.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal/workspace.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/__init__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/py.typed +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/modal_version/__main__.py +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/pyproject.toml +0 -0
- {modal-1.5.3.dev5 → modal-1.5.3.dev6}/setup.cfg +0 -0
|
@@ -2073,11 +2073,13 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2073
2073
|
|
|
2074
2074
|
@live_method
|
|
2075
2075
|
async def get_call_graph(self) -> list[InputInfo]:
|
|
2076
|
-
"""
|
|
2077
|
-
call ID, along with the status of execution for each node.
|
|
2076
|
+
"""Fetch information about the graph of Inputs this FunctionCall is part of.
|
|
2078
2077
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2078
|
+
Note: the call graph data is not populated in real-time, and its capture is best-effort.
|
|
2079
|
+
We do not recommend relying on this method for critical use cases.
|
|
2080
|
+
|
|
2081
|
+
See the [`modal.types`](/docs/sdk/py/latest/modal.types) reference for information
|
|
2082
|
+
on the return values.
|
|
2081
2083
|
|
|
2082
2084
|
Returns:
|
|
2083
2085
|
A list of `InputInfo` nodes describing the call graph.
|
|
@@ -2088,19 +2090,13 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2088
2090
|
return _reconstruct_call_graph(response)
|
|
2089
2091
|
|
|
2090
2092
|
@live_method
|
|
2091
|
-
async def cancel(
|
|
2092
|
-
|
|
2093
|
-
# if true, containers running the inputs are forcibly terminated
|
|
2094
|
-
terminate_containers: bool = False,
|
|
2095
|
-
):
|
|
2096
|
-
"""Cancels the function call, which will stop its execution and mark its inputs as
|
|
2097
|
-
[`TERMINATED`](https://modal.com/docs/sdk/py/latest/modal.call_graph#modalcall_graphinputstatus).
|
|
2098
|
-
|
|
2099
|
-
If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
|
|
2100
|
-
causing any non-cancelled inputs on those containers to be rescheduled in new containers.
|
|
2093
|
+
async def cancel(self, terminate_containers: bool = False):
|
|
2094
|
+
"""Cancel the FunctionCall and terminate its inputs without retrying.
|
|
2101
2095
|
|
|
2102
2096
|
Args:
|
|
2103
|
-
terminate_containers: If True,
|
|
2097
|
+
terminate_containers: If True, terminate the containers running the cancelled
|
|
2098
|
+
inputs. Any other inputs running concurrently on those containers will be
|
|
2099
|
+
rescheduled.
|
|
2104
2100
|
"""
|
|
2105
2101
|
request = api_pb2.FunctionCallCancelRequest(
|
|
2106
2102
|
function_call_id=self.object_id, terminate_containers=terminate_containers
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
import asyncio
|
|
3
3
|
import base64
|
|
4
|
+
import io
|
|
4
5
|
import json
|
|
5
6
|
import socket
|
|
6
7
|
import ssl
|
|
@@ -8,16 +9,16 @@ import time
|
|
|
8
9
|
import typing
|
|
9
10
|
import urllib.parse
|
|
10
11
|
import weakref
|
|
11
|
-
from collections.abc import AsyncGenerator, Callable
|
|
12
|
+
from collections.abc import AsyncGenerator, AsyncIterable, Callable
|
|
12
13
|
from contextlib import suppress
|
|
13
|
-
from typing import TypeVar
|
|
14
|
+
from typing import BinaryIO, TypeVar
|
|
14
15
|
|
|
15
16
|
import grpclib.client
|
|
16
17
|
from grpclib import GRPCError, Status
|
|
17
18
|
from grpclib.exceptions import StreamTerminatedError
|
|
18
19
|
|
|
19
20
|
from modal.config import logger
|
|
20
|
-
from modal.exception import ExecTimeoutError, TimeoutError as ModalTimeoutError
|
|
21
|
+
from modal.exception import AuthError, ExecTimeoutError, InternalError, ServiceError, TimeoutError as ModalTimeoutError
|
|
21
22
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
22
23
|
from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
23
24
|
|
|
@@ -26,6 +27,17 @@ from .._utils.grpc_utils import ModalChannel, create_channel_config
|
|
|
26
27
|
from .async_utils import aclosing, retry
|
|
27
28
|
from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
28
29
|
|
|
30
|
+
STREAMING_STDIN_CHUNK_SIZE = 256 * 1024
|
|
31
|
+
|
|
32
|
+
_STREAMING_STDIN_RESUMABLE_EXCEPTIONS = (
|
|
33
|
+
AuthError,
|
|
34
|
+
InternalError,
|
|
35
|
+
ServiceError,
|
|
36
|
+
StreamTerminatedError,
|
|
37
|
+
OSError,
|
|
38
|
+
asyncio.TimeoutError,
|
|
39
|
+
)
|
|
40
|
+
|
|
29
41
|
|
|
30
42
|
@retry(n_attempts=34, base_delay=1, max_delay=10, attempt_timeout=10, total_timeout=310)
|
|
31
43
|
async def _connect_channel(channel: grpclib.client.Channel):
|
|
@@ -468,6 +480,118 @@ class TaskCommandRouterClient:
|
|
|
468
480
|
lambda: self._call_with_auth_retry(self._stub.TaskExecStdinWrite, request)
|
|
469
481
|
)
|
|
470
482
|
|
|
483
|
+
async def exec_stdin_write_stream(
|
|
484
|
+
self,
|
|
485
|
+
task_id: str,
|
|
486
|
+
exec_id: str,
|
|
487
|
+
source: BinaryIO,
|
|
488
|
+
chunk_size: int = STREAMING_STDIN_CHUNK_SIZE,
|
|
489
|
+
max_resume_attempts: int = 9,
|
|
490
|
+
) -> int:
|
|
491
|
+
"""Stream `source` into the exec's stdin, with bounded resume on transient failures.
|
|
492
|
+
|
|
493
|
+
Streams the full contents of `source` in one client-streaming RPC and
|
|
494
|
+
closes stdin (EOF) on success. On a resumable error, queries
|
|
495
|
+
`exec_stdin_status` for the server's canonical offset, seeks `source`
|
|
496
|
+
to that point, and reopens the stream.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
task_id: The task ID of the task running the exec'd command.
|
|
500
|
+
exec_id: The execution ID of the command to write to.
|
|
501
|
+
source: A seekable byte source (file, BytesIO, etc.).
|
|
502
|
+
chunk_size: Bytes per outbound message.
|
|
503
|
+
max_resume_attempts: Upper bound on resume retries before giving up.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
The total number of bytes streamed into the exec's stdin.
|
|
507
|
+
"""
|
|
508
|
+
offset = 0
|
|
509
|
+
attempt = 0
|
|
510
|
+
while True:
|
|
511
|
+
|
|
512
|
+
async def _chunks():
|
|
513
|
+
while True:
|
|
514
|
+
chunk = source.read(chunk_size)
|
|
515
|
+
if not chunk:
|
|
516
|
+
return
|
|
517
|
+
yield chunk
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
source.seek(offset)
|
|
521
|
+
await self._exec_stdin_write_stream(task_id, exec_id, offset, _chunks())
|
|
522
|
+
return source.tell()
|
|
523
|
+
except (*_STREAMING_STDIN_RESUMABLE_EXCEPTIONS, AttributeError) as e:
|
|
524
|
+
# StreamTerminatedError surfaces as AttributeError in grpclib<=0.4.7;
|
|
525
|
+
# see call_with_retries_on_transient_errors.
|
|
526
|
+
if isinstance(e, AttributeError) and "_write_appdata" not in str(e):
|
|
527
|
+
raise
|
|
528
|
+
attempt += 1
|
|
529
|
+
if attempt > max_resume_attempts:
|
|
530
|
+
raise
|
|
531
|
+
status = await self.exec_stdin_status(task_id=task_id, exec_id=exec_id)
|
|
532
|
+
if status.closed:
|
|
533
|
+
pos = source.tell()
|
|
534
|
+
if status.num_bytes_written == pos and source.seek(0, io.SEEK_END) == pos:
|
|
535
|
+
logger.debug(f"exec_stdin_write_stream completed but response was lost: {e}")
|
|
536
|
+
return pos
|
|
537
|
+
raise
|
|
538
|
+
offset = status.num_bytes_written
|
|
539
|
+
logger.debug(f"exec_stdin_write_stream resuming from offset {offset} after error: {e}")
|
|
540
|
+
|
|
541
|
+
async def _exec_stdin_write_stream(
|
|
542
|
+
self,
|
|
543
|
+
task_id: str,
|
|
544
|
+
exec_id: str,
|
|
545
|
+
start_offset: int,
|
|
546
|
+
chunks: AsyncIterable[bytes],
|
|
547
|
+
) -> sr_pb2.TaskExecStdinWriteStreamResponse | None:
|
|
548
|
+
"""Single client-streaming attempt: Start, Data chunks, then End (EOF).
|
|
549
|
+
|
|
550
|
+
Does not retry; `exec_stdin_write_stream` owns resume.
|
|
551
|
+
"""
|
|
552
|
+
with grpc_error_converter():
|
|
553
|
+
stream = self._stub.TaskExecStdinWriteStream.open(metadata=self._get_metadata())
|
|
554
|
+
async with stream as s:
|
|
555
|
+
start = sr_pb2.TaskExecStdinWriteStreamRequest(
|
|
556
|
+
start=sr_pb2.TaskExecStdinWriteStreamStart(
|
|
557
|
+
task_id=task_id,
|
|
558
|
+
exec_id=exec_id,
|
|
559
|
+
offset=start_offset,
|
|
560
|
+
),
|
|
561
|
+
)
|
|
562
|
+
await s.send_message(start)
|
|
563
|
+
async for data in chunks:
|
|
564
|
+
if not data:
|
|
565
|
+
continue
|
|
566
|
+
await s.send_message(sr_pb2.TaskExecStdinWriteStreamRequest(data=data))
|
|
567
|
+
# The server closes stdin only on this explicit End message. A
|
|
568
|
+
# stream that breaks before it leaves stdin open for resume.
|
|
569
|
+
await s.send_message(
|
|
570
|
+
sr_pb2.TaskExecStdinWriteStreamRequest(end=sr_pb2.TaskExecStdinWriteStreamEnd()),
|
|
571
|
+
end=True,
|
|
572
|
+
)
|
|
573
|
+
return await s.recv_message()
|
|
574
|
+
|
|
575
|
+
async def exec_stdin_status(self, task_id: str, exec_id: str) -> sr_pb2.TaskExecStdinStatusResponse:
|
|
576
|
+
"""Read the current stdin write status for an exec'd command, to support retries from the right offset.
|
|
577
|
+
|
|
578
|
+
Used by streaming clients to find the resume offset after a stream
|
|
579
|
+
failure. Evicts any in-flight stdin stream for the exec, so it is not
|
|
580
|
+
safe to call for read-only observability.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
task_id: The task ID of the task running the exec'd command.
|
|
584
|
+
exec_id: The execution ID of the command to query.
|
|
585
|
+
Raises:
|
|
586
|
+
Other errors: If retries are exhausted on transient errors or if there's an error
|
|
587
|
+
from the RPC itself.
|
|
588
|
+
"""
|
|
589
|
+
request = sr_pb2.TaskExecStdinStatusRequest(task_id=task_id, exec_id=exec_id)
|
|
590
|
+
with grpc_error_converter():
|
|
591
|
+
return await call_with_retries_on_transient_errors(
|
|
592
|
+
lambda: self._call_with_auth_retry(self._stub.TaskExecStdinStatus, request)
|
|
593
|
+
)
|
|
594
|
+
|
|
471
595
|
async def sandbox_stdin_write_v2(
|
|
472
596
|
self, task_id: str, offset: int, data: bytes, eof: bool
|
|
473
597
|
) -> sr_pb2.SandboxStdinWriteV2Response:
|
|
@@ -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.dev6",
|
|
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.dev6",
|
|
211
211
|
):
|
|
212
212
|
"""mdmd:hidden
|
|
213
213
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2024
|
|
2
2
|
import asyncio
|
|
3
3
|
import platform
|
|
4
|
-
from typing import Generic, TypeVar
|
|
4
|
+
from typing import BinaryIO, Generic, TypeVar
|
|
5
5
|
|
|
6
6
|
from modal_proto import api_pb2
|
|
7
7
|
|
|
@@ -117,6 +117,18 @@ class _ContainerProcess(Generic[T]):
|
|
|
117
117
|
)
|
|
118
118
|
return self._returncode
|
|
119
119
|
|
|
120
|
+
async def _stdin_write_stream(self, source: BinaryIO) -> int:
|
|
121
|
+
"""mdmd:hidden
|
|
122
|
+
Stream `source` into the process's stdin and close it (EOF) on success.
|
|
123
|
+
|
|
124
|
+
Returns the total bytes streamed.
|
|
125
|
+
"""
|
|
126
|
+
return await self._command_router_client.exec_stdin_write_stream(
|
|
127
|
+
task_id=self._task_id,
|
|
128
|
+
exec_id=self._process_id,
|
|
129
|
+
source=source,
|
|
130
|
+
)
|
|
131
|
+
|
|
120
132
|
async def poll(self) -> int | None:
|
|
121
133
|
"""Check if the container process has finished running.
|
|
122
134
|
|
|
@@ -53,6 +53,14 @@ class _ContainerProcess(typing.Generic[T]):
|
|
|
53
53
|
|
|
54
54
|
@property
|
|
55
55
|
def returncode(self) -> int: ...
|
|
56
|
+
async def _stdin_write_stream(self, source: typing.BinaryIO) -> int:
|
|
57
|
+
"""mdmd:hidden
|
|
58
|
+
Stream `source` into the process's stdin and close it (EOF) on success.
|
|
59
|
+
|
|
60
|
+
Returns the total bytes streamed.
|
|
61
|
+
"""
|
|
62
|
+
...
|
|
63
|
+
|
|
56
64
|
async def poll(self) -> typing.Optional[int]:
|
|
57
65
|
"""Check if the container process has finished running.
|
|
58
66
|
|
|
@@ -105,6 +113,25 @@ class ContainerProcess(typing.Generic[T]):
|
|
|
105
113
|
@property
|
|
106
114
|
def returncode(self) -> int: ...
|
|
107
115
|
|
|
116
|
+
class ___stdin_write_stream_spec(typing_extensions.Protocol):
|
|
117
|
+
def __call__(self, /, source: typing.BinaryIO) -> int:
|
|
118
|
+
"""mdmd:hidden
|
|
119
|
+
Stream `source` into the process's stdin and close it (EOF) on success.
|
|
120
|
+
|
|
121
|
+
Returns the total bytes streamed.
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
async def aio(self, /, source: typing.BinaryIO) -> int:
|
|
126
|
+
"""mdmd:hidden
|
|
127
|
+
Stream `source` into the process's stdin and close it (EOF) on success.
|
|
128
|
+
|
|
129
|
+
Returns the total bytes streamed.
|
|
130
|
+
"""
|
|
131
|
+
...
|
|
132
|
+
|
|
133
|
+
_stdin_write_stream: ___stdin_write_stream_spec
|
|
134
|
+
|
|
108
135
|
class __poll_spec(typing_extensions.Protocol):
|
|
109
136
|
def __call__(self, /) -> typing.Optional[int]:
|
|
110
137
|
"""Check if the container process has finished running.
|
|
@@ -1044,11 +1044,13 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
|
|
|
1044
1044
|
|
|
1045
1045
|
class __get_call_graph_spec(typing_extensions.Protocol):
|
|
1046
1046
|
def __call__(self, /) -> list[modal.types.InputInfo]:
|
|
1047
|
-
"""
|
|
1048
|
-
call ID, along with the status of execution for each node.
|
|
1047
|
+
"""Fetch information about the graph of Inputs this FunctionCall is part of.
|
|
1049
1048
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1049
|
+
Note: the call graph data is not populated in real-time, and its capture is best-effort.
|
|
1050
|
+
We do not recommend relying on this method for critical use cases.
|
|
1051
|
+
|
|
1052
|
+
See the [`modal.types`](/docs/sdk/py/latest/modal.types) reference for information
|
|
1053
|
+
on the return values.
|
|
1052
1054
|
|
|
1053
1055
|
Returns:
|
|
1054
1056
|
A list of `InputInfo` nodes describing the call graph.
|
|
@@ -1056,11 +1058,13 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
|
|
|
1056
1058
|
...
|
|
1057
1059
|
|
|
1058
1060
|
async def aio(self, /) -> list[modal.types.InputInfo]:
|
|
1059
|
-
"""
|
|
1060
|
-
|
|
1061
|
+
"""Fetch information about the graph of Inputs this FunctionCall is part of.
|
|
1062
|
+
|
|
1063
|
+
Note: the call graph data is not populated in real-time, and its capture is best-effort.
|
|
1064
|
+
We do not recommend relying on this method for critical use cases.
|
|
1061
1065
|
|
|
1062
|
-
See [`modal.
|
|
1063
|
-
|
|
1066
|
+
See the [`modal.types`](/docs/sdk/py/latest/modal.types) reference for information
|
|
1067
|
+
on the return values.
|
|
1064
1068
|
|
|
1065
1069
|
Returns:
|
|
1066
1070
|
A list of `InputInfo` nodes describing the call graph.
|
|
@@ -1071,26 +1075,22 @@ class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Obj
|
|
|
1071
1075
|
|
|
1072
1076
|
class __cancel_spec(typing_extensions.Protocol):
|
|
1073
1077
|
def __call__(self, /, terminate_containers: bool = False):
|
|
1074
|
-
"""
|
|
1075
|
-
[`TERMINATED`](https://modal.com/docs/sdk/py/latest/modal.call_graph#modalcall_graphinputstatus).
|
|
1076
|
-
|
|
1077
|
-
If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
|
|
1078
|
-
causing any non-cancelled inputs on those containers to be rescheduled in new containers.
|
|
1078
|
+
"""Cancel the FunctionCall and terminate its inputs without retrying.
|
|
1079
1079
|
|
|
1080
1080
|
Args:
|
|
1081
|
-
terminate_containers: If True,
|
|
1081
|
+
terminate_containers: If True, terminate the containers running the cancelled
|
|
1082
|
+
inputs. Any other inputs running concurrently on those containers will be
|
|
1083
|
+
rescheduled.
|
|
1082
1084
|
"""
|
|
1083
1085
|
...
|
|
1084
1086
|
|
|
1085
1087
|
async def aio(self, /, terminate_containers: bool = False):
|
|
1086
|
-
"""
|
|
1087
|
-
[`TERMINATED`](https://modal.com/docs/sdk/py/latest/modal.call_graph#modalcall_graphinputstatus).
|
|
1088
|
-
|
|
1089
|
-
If `terminate_containers=True` - the containers running the cancelled inputs are all terminated
|
|
1090
|
-
causing any non-cancelled inputs on those containers to be rescheduled in new containers.
|
|
1088
|
+
"""Cancel the FunctionCall and terminate its inputs without retrying.
|
|
1091
1089
|
|
|
1092
1090
|
Args:
|
|
1093
|
-
terminate_containers: If True,
|
|
1091
|
+
terminate_containers: If True, terminate the containers running the cancelled
|
|
1092
|
+
inputs. Any other inputs running concurrently on those containers will be
|
|
1093
|
+
rescheduled.
|
|
1094
1094
|
"""
|
|
1095
1095
|
...
|
|
1096
1096
|
|
|
@@ -332,6 +332,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
332
332
|
_attached: bool
|
|
333
333
|
_filesystem: _SandboxFilesystem | None
|
|
334
334
|
_is_v2: bool = False
|
|
335
|
+
_app_id: str | None
|
|
335
336
|
|
|
336
337
|
@staticmethod
|
|
337
338
|
def _default_pty_info() -> api_pb2.PTYInfo:
|
|
@@ -500,7 +501,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
500
501
|
create_resp = await load_context.client.stub.SandboxCreate(create_req)
|
|
501
502
|
rpc_elapsed = time.monotonic() - rpc_start
|
|
502
503
|
sandbox_id = create_resp.sandbox_id
|
|
503
|
-
self._hydrate(sandbox_id, load_context.client,
|
|
504
|
+
self._hydrate(sandbox_id, load_context.client, create_resp.metadata)
|
|
504
505
|
|
|
505
506
|
if logger.isEnabledFor(logging.DEBUG):
|
|
506
507
|
total_elapsed = time.monotonic() - load_start
|
|
@@ -1017,7 +1018,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1017
1018
|
)
|
|
1018
1019
|
rpc_elapsed = time.monotonic() - rpc_start
|
|
1019
1020
|
sandbox_id = create_resp.sandbox_id
|
|
1020
|
-
self._hydrate(sandbox_id, load_context.client,
|
|
1021
|
+
self._hydrate(sandbox_id, load_context.client, create_resp.metadata)
|
|
1021
1022
|
self._is_v2 = True
|
|
1022
1023
|
self._task_id = create_resp.task_id
|
|
1023
1024
|
self._hydrate_metadata_v2()
|
|
@@ -1070,7 +1071,22 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1070
1071
|
await resolver.load(obj, load_context)
|
|
1071
1072
|
return obj
|
|
1072
1073
|
|
|
1073
|
-
def
|
|
1074
|
+
def _get_metadata(self) -> api_pb2.SandboxHandleMetadata:
|
|
1075
|
+
metadata = api_pb2.SandboxHandleMetadata(app_id=self._app_id or "")
|
|
1076
|
+
if hasattr(self, "_result") and self._result is not None:
|
|
1077
|
+
metadata.result.CopyFrom(self._result)
|
|
1078
|
+
return metadata
|
|
1079
|
+
|
|
1080
|
+
def _hydrate_metadata(self, handle_metadata: Message | None) -> None:
|
|
1081
|
+
self._app_id = None
|
|
1082
|
+
self._result = None
|
|
1083
|
+
if handle_metadata is not None:
|
|
1084
|
+
assert isinstance(handle_metadata, api_pb2.SandboxHandleMetadata), (
|
|
1085
|
+
f"{type(handle_metadata)} is not SandboxHandleMetadata"
|
|
1086
|
+
)
|
|
1087
|
+
self._app_id = handle_metadata.app_id
|
|
1088
|
+
if handle_metadata.HasField("result"):
|
|
1089
|
+
self._result = handle_metadata.result
|
|
1074
1090
|
self._stdout = StreamReader(
|
|
1075
1091
|
_StreamReaderThroughServerParams(
|
|
1076
1092
|
file_descriptor=api_pb2.FILE_DESCRIPTOR_STDOUT,
|
|
@@ -1088,13 +1104,14 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1088
1104
|
by_line=True,
|
|
1089
1105
|
)
|
|
1090
1106
|
self._stdin = StreamWriter(_StreamWriterThroughServerParams(object_id=self.object_id, client=self._client))
|
|
1091
|
-
self._result = None
|
|
1092
1107
|
self._task_id = None
|
|
1093
1108
|
self._tunnels = None
|
|
1094
1109
|
self._enable_snapshot = False
|
|
1095
1110
|
self._command_router_client = None
|
|
1096
1111
|
self._filesystem = None
|
|
1097
|
-
self._is_v2 =
|
|
1112
|
+
self._is_v2 = _is_v2_sandbox_id(self.object_id)
|
|
1113
|
+
if self._is_v2:
|
|
1114
|
+
self._hydrate_metadata_v2()
|
|
1098
1115
|
|
|
1099
1116
|
def _hydrate_metadata_v2(self) -> None:
|
|
1100
1117
|
"""Wire up V2 stdio readers that read directly from the worker. Cheap
|
|
@@ -1133,11 +1150,13 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1133
1150
|
super()._initialize_from_other(other)
|
|
1134
1151
|
self._attached = other._attached
|
|
1135
1152
|
self._is_v2 = other._is_v2
|
|
1153
|
+
self._app_id = other._app_id
|
|
1136
1154
|
|
|
1137
1155
|
def _initialize_from_empty(self):
|
|
1138
1156
|
super()._initialize_from_empty()
|
|
1139
1157
|
self._attached = True
|
|
1140
1158
|
self._is_v2 = False
|
|
1159
|
+
self._app_id = None
|
|
1141
1160
|
|
|
1142
1161
|
async def detach(self):
|
|
1143
1162
|
"""Disconnects your client from the sandbox and cleans up resources assoicated with the connection.
|
|
@@ -1200,7 +1219,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1200
1219
|
|
|
1201
1220
|
req = api_pb2.SandboxGetFromNameRequest(sandbox_name=name, app_name=app_name, environment_name=env_name)
|
|
1202
1221
|
resp = await client.stub.SandboxGetFromName(req)
|
|
1203
|
-
return _Sandbox._new_hydrated(resp.sandbox_id, client,
|
|
1222
|
+
return _Sandbox._new_hydrated(resp.sandbox_id, client, resp.metadata)
|
|
1204
1223
|
|
|
1205
1224
|
@staticmethod
|
|
1206
1225
|
async def _experimental_from_name(
|
|
@@ -1235,7 +1254,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1235
1254
|
auth_token = await client._auth_token_manager.get_token()
|
|
1236
1255
|
resp = await client.stub.SandboxGetFromNameV2(req, metadata=[("x-modal-auth-token", auth_token)])
|
|
1237
1256
|
|
|
1238
|
-
obj = _Sandbox._new_hydrated(resp.sandbox_id, client,
|
|
1257
|
+
obj = _Sandbox._new_hydrated(resp.sandbox_id, client, resp.metadata)
|
|
1239
1258
|
obj._is_v2 = True
|
|
1240
1259
|
obj._hydrate_metadata_v2()
|
|
1241
1260
|
return obj
|
|
@@ -1266,7 +1285,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1266
1285
|
else:
|
|
1267
1286
|
resp = await client.stub.SandboxWait(req)
|
|
1268
1287
|
|
|
1269
|
-
obj = _Sandbox._new_hydrated(sandbox_id, client,
|
|
1288
|
+
obj = _Sandbox._new_hydrated(sandbox_id, client, resp.metadata)
|
|
1270
1289
|
obj._is_v2 = is_v2
|
|
1271
1290
|
if is_v2:
|
|
1272
1291
|
obj._hydrate_metadata_v2()
|
|
@@ -2360,7 +2379,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2360
2379
|
|
|
2361
2380
|
for sandbox_info in resp.sandboxes:
|
|
2362
2381
|
sandbox_info: api_pb2.SandboxInfo
|
|
2363
|
-
obj = _Sandbox._new_hydrated(sandbox_info.id, client,
|
|
2382
|
+
obj = _Sandbox._new_hydrated(sandbox_info.id, client, sandbox_info.metadata)
|
|
2364
2383
|
obj._result = sandbox_info.task_info.result # TODO: send SandboxInfo as metadata to _new_hydrated?
|
|
2365
2384
|
yield obj
|
|
2366
2385
|
|
|
@@ -2415,7 +2434,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2415
2434
|
|
|
2416
2435
|
for sandbox_info in resp.sandboxes:
|
|
2417
2436
|
sandbox_info: api_pb2.SandboxInfo
|
|
2418
|
-
obj = _Sandbox._new_hydrated(sandbox_info.id, client,
|
|
2437
|
+
obj = _Sandbox._new_hydrated(sandbox_info.id, client, sandbox_info.metadata)
|
|
2419
2438
|
# SandboxListV2 only returns V2 sandboxes; mark them as such so
|
|
2420
2439
|
# operations like wait/terminate/exec use the V2 RPCs and stdio.
|
|
2421
2440
|
obj._is_v2 = True
|
|
@@ -150,6 +150,7 @@ class _Sandbox(modal._object._Object):
|
|
|
150
150
|
_attached: bool
|
|
151
151
|
_filesystem: typing.Optional[modal.sandbox_fs._SandboxFilesystem]
|
|
152
152
|
_is_v2: bool
|
|
153
|
+
_app_id: typing.Optional[str]
|
|
153
154
|
|
|
154
155
|
@staticmethod
|
|
155
156
|
def _default_pty_info() -> modal_proto.api_pb2.PTYInfo: ...
|
|
@@ -424,7 +425,8 @@ class _Sandbox(modal._object._Object):
|
|
|
424
425
|
"""
|
|
425
426
|
...
|
|
426
427
|
|
|
427
|
-
def
|
|
428
|
+
def _get_metadata(self) -> modal_proto.api_pb2.SandboxHandleMetadata: ...
|
|
429
|
+
def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]) -> None: ...
|
|
428
430
|
def _hydrate_metadata_v2(self) -> None:
|
|
429
431
|
"""Wire up V2 stdio readers that read directly from the worker. Cheap
|
|
430
432
|
to call eagerly: the router connection is opened lazily on first read.
|
|
@@ -1342,6 +1344,7 @@ class Sandbox(modal.object.Object):
|
|
|
1342
1344
|
_attached: bool
|
|
1343
1345
|
_filesystem: typing.Optional[modal.sandbox_fs.SandboxFilesystem]
|
|
1344
1346
|
_is_v2: bool
|
|
1347
|
+
_app_id: typing.Optional[str]
|
|
1345
1348
|
|
|
1346
1349
|
def __init__(self, *args, **kwargs):
|
|
1347
1350
|
"""mdmd:hidden"""
|
|
@@ -1872,7 +1875,8 @@ class Sandbox(modal.object.Object):
|
|
|
1872
1875
|
|
|
1873
1876
|
_experimental_create: typing.ClassVar[___experimental_create_spec]
|
|
1874
1877
|
|
|
1875
|
-
def
|
|
1878
|
+
def _get_metadata(self) -> modal_proto.api_pb2.SandboxHandleMetadata: ...
|
|
1879
|
+
def _hydrate_metadata(self, handle_metadata: typing.Optional[google.protobuf.message.Message]) -> None: ...
|
|
1876
1880
|
def _hydrate_metadata_v2(self) -> None:
|
|
1877
1881
|
"""Wire up V2 stdio readers that read directly from the worker. Cheap
|
|
1878
1882
|
to call eagerly: the router connection is opened lazily on first read.
|