modal 1.5.3.dev9__tar.gz → 1.5.3.dev10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/PKG-INFO +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_function_variants.py +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_functions.py +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/task_command_router_client.py +5 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/client.pyi +2 -2
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cloud_bucket_mount.py +15 -3
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cloud_bucket_mount.pyi +2 -2
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/functions.pyi +6 -6
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox.py +89 -30
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox.pyi +65 -9
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/secret.py +9 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/secret.pyi +18 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/snapshot.py +17 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/snapshot.pyi +22 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2.py +396 -386
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2.pyi +20 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_version/__init__.py +1 -1
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/LICENSE +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/README.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/__main__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_billing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_clustered_functions.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_environments.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_grpc_client.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_image.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_ipython.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_load_context.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_location.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_logs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_object.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/pty.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/rich.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_output/status.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_partial_function.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_resolver.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_resources.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_serialization.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_server.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_traceback.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_tunnel.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_tunnel.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_type_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/logger.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_vendor/version.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_watcher.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/_workspace.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/app.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/app.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/billing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/billing.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/README.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/builder/base-images.json +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/call_graph.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/_download.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/_help.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/_traceback.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/app.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/billing.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/changelog.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/cluster.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/config.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/container.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/curl.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/dashboard.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/dict.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/endpoint.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/entry_point.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/environment.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/image.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/import_refs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/launch.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/logo.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/profile.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/queues.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/run.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/secret.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/selector.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/shell.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/skills.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/token.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/utils.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/volume.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cli/workspace.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/client.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cls.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/cls.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/config.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/container_process.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/container_process.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/dict.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/dict.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/environments.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/environments.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/exception.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/flash.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/experimental/ipython.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/file_io.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/file_io.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/functions.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/image.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/image.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/io_streams.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/io_streams.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/mount.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/mount.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/network_file_system.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/network_file_system.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/object.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/object.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/output.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/parallel_map.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/parallel_map.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/partial_function.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/partial_function.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/proxy.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/proxy.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/py.typed +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/queue.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/queue.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/retries.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/runner.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/runner.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/running_app.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox_fs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/schedule.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/scheduler_placement.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/server.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/server.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/serving.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/serving.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/stream_type.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/token_flow.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/token_flow.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/types.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/volume.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/volume.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/workspace.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal/workspace.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/__init__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/py.typed +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/modal_version/__main__.py +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/pyproject.toml +0 -0
- {modal-1.5.3.dev9 → modal-1.5.3.dev10}/setup.cfg +0 -0
|
@@ -171,7 +171,7 @@ class _FunctionOptions:
|
|
|
171
171
|
replace_secret_ids=bool(self.secrets),
|
|
172
172
|
replace_volume_mounts=len(volume_mounts) > 0,
|
|
173
173
|
volume_mounts=volume_mounts,
|
|
174
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts),
|
|
174
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(self.cloud_bucket_mounts)[0],
|
|
175
175
|
replace_cloud_bucket_mounts=bool(self.cloud_bucket_mounts),
|
|
176
176
|
resources=self.resources,
|
|
177
177
|
retry_policy=self.retry_policy,
|
|
@@ -1000,7 +1000,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1000
1000
|
untrusted=restrict_modal_access,
|
|
1001
1001
|
single_use_containers=single_use_containers,
|
|
1002
1002
|
max_inputs=int(single_use_containers), # TODO(michael) remove after worker rollover
|
|
1003
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
1003
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts)[0],
|
|
1004
1004
|
scheduler_placement=scheduler_placement,
|
|
1005
1005
|
is_class=info.is_service_class(),
|
|
1006
1006
|
class_parameter_info=info.class_parameter_info(),
|
|
@@ -974,3 +974,8 @@ class TaskCommandRouterClient:
|
|
|
974
974
|
return await self._unary_call_with_deadline(
|
|
975
975
|
self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
|
|
976
976
|
)
|
|
977
|
+
|
|
978
|
+
async def snapshot_memory(
|
|
979
|
+
self, request: sr_pb2.TaskSnapshotMemoryRequest, *, timeout: float, **kwargs
|
|
980
|
+
) -> sr_pb2.TaskSnapshotMemoryResponse:
|
|
981
|
+
return await self._unary_call_with_deadline(self._stub.TaskSnapshotMemory, request, timeout=timeout, **kwargs)
|
|
@@ -37,7 +37,7 @@ class _Client:
|
|
|
37
37
|
server_url: str,
|
|
38
38
|
client_type: int,
|
|
39
39
|
credentials: typing.Optional[tuple[str, str]],
|
|
40
|
-
version: str = "1.5.3.
|
|
40
|
+
version: str = "1.5.3.dev10",
|
|
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.dev10",
|
|
211
211
|
):
|
|
212
212
|
"""mdmd:hidden
|
|
213
213
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -121,11 +121,14 @@ class _CloudBucketMount:
|
|
|
121
121
|
force_path_style: bool = False
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def cloud_bucket_mounts_to_proto(
|
|
124
|
+
def cloud_bucket_mounts_to_proto(
|
|
125
|
+
mounts: Sequence[tuple[str, _CloudBucketMount]], split_ephemeral_credentials: bool = False
|
|
126
|
+
) -> tuple[list[api_pb2.CloudBucketMount], dict[str, api_pb2.StringMap]]:
|
|
125
127
|
"""mdmd:hidden
|
|
126
128
|
Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server.
|
|
127
129
|
"""
|
|
128
130
|
cloud_bucket_mounts: list[api_pb2.CloudBucketMount] = []
|
|
131
|
+
cloud_bucket_credentials: dict[str, api_pb2.StringMap] = {}
|
|
129
132
|
|
|
130
133
|
for path, mount in mounts:
|
|
131
134
|
# crude mapping from mount arguments to type.
|
|
@@ -153,11 +156,20 @@ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]
|
|
|
153
156
|
else:
|
|
154
157
|
key_prefix = mount.key_prefix
|
|
155
158
|
|
|
159
|
+
credentials_secret_id = ""
|
|
160
|
+
if secret := mount.secret:
|
|
161
|
+
if split_ephemeral_credentials and secret._load_env_dict:
|
|
162
|
+
env_dict = secret._load_env_dict()
|
|
163
|
+
credientials = api_pb2.StringMap(contents=env_dict)
|
|
164
|
+
cloud_bucket_credentials[path] = credientials
|
|
165
|
+
else:
|
|
166
|
+
credentials_secret_id = secret.object_id
|
|
167
|
+
|
|
156
168
|
cloud_bucket_mount = api_pb2.CloudBucketMount(
|
|
157
169
|
bucket_name=mount.bucket_name,
|
|
158
170
|
bucket_endpoint_url=mount.bucket_endpoint_url,
|
|
159
171
|
mount_path=path,
|
|
160
|
-
credentials_secret_id=
|
|
172
|
+
credentials_secret_id=credentials_secret_id,
|
|
161
173
|
read_only=mount.read_only,
|
|
162
174
|
bucket_type=bucket_type,
|
|
163
175
|
requester_pays=mount.requester_pays,
|
|
@@ -167,7 +179,7 @@ def cloud_bucket_mounts_to_proto(mounts: Sequence[tuple[str, _CloudBucketMount]]
|
|
|
167
179
|
)
|
|
168
180
|
cloud_bucket_mounts.append(cloud_bucket_mount)
|
|
169
181
|
|
|
170
|
-
return cloud_bucket_mounts
|
|
182
|
+
return cloud_bucket_mounts, cloud_bucket_credentials
|
|
171
183
|
|
|
172
184
|
|
|
173
185
|
CloudBucketMount = synchronize_api(_CloudBucketMount)
|
|
@@ -126,8 +126,8 @@ class _CloudBucketMount:
|
|
|
126
126
|
...
|
|
127
127
|
|
|
128
128
|
def cloud_bucket_mounts_to_proto(
|
|
129
|
-
mounts: collections.abc.Sequence[tuple[str, _CloudBucketMount]],
|
|
130
|
-
) -> list[modal_proto.api_pb2.CloudBucketMount]:
|
|
129
|
+
mounts: collections.abc.Sequence[tuple[str, _CloudBucketMount]], split_ephemeral_credentials: bool = False
|
|
130
|
+
) -> tuple[list[modal_proto.api_pb2.CloudBucketMount], dict[str, modal_proto.api_pb2.StringMap]]:
|
|
131
131
|
"""mdmd:hidden
|
|
132
132
|
Helper function to convert `CloudBucketMount` to a list of protobufs that can be passed to the server.
|
|
133
133
|
"""
|
|
@@ -493,7 +493,7 @@ class Function(
|
|
|
493
493
|
|
|
494
494
|
_call_generator: ___call_generator_spec
|
|
495
495
|
|
|
496
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
496
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
497
497
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
498
498
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
499
499
|
|
|
@@ -518,7 +518,7 @@ class Function(
|
|
|
518
518
|
"""
|
|
519
519
|
...
|
|
520
520
|
|
|
521
|
-
remote: __remote_spec[modal._functions.
|
|
521
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
522
522
|
|
|
523
523
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
524
524
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -568,7 +568,7 @@ class Function(
|
|
|
568
568
|
"""
|
|
569
569
|
...
|
|
570
570
|
|
|
571
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
571
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
572
572
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
573
573
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
574
574
|
|
|
@@ -601,7 +601,7 @@ class Function(
|
|
|
601
601
|
"""
|
|
602
602
|
...
|
|
603
603
|
|
|
604
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
604
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
605
605
|
|
|
606
606
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
607
607
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -609,7 +609,7 @@ class Function(
|
|
|
609
609
|
|
|
610
610
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
611
611
|
|
|
612
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
612
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
613
613
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
614
614
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
615
615
|
|
|
@@ -642,7 +642,7 @@ class Function(
|
|
|
642
642
|
"""
|
|
643
643
|
...
|
|
644
644
|
|
|
645
|
-
spawn: __spawn_spec[modal._functions.
|
|
645
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
646
646
|
|
|
647
647
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
648
648
|
"""Return the inner Python object wrapped by this Modal Function.
|
|
@@ -472,7 +472,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
472
472
|
nfs_mounts=network_file_system_mount_protos(validated_network_file_systems),
|
|
473
473
|
runtime=config.get("function_runtime"),
|
|
474
474
|
runtime_debug=config.get("function_runtime_debug"),
|
|
475
|
-
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
475
|
+
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts)[0],
|
|
476
476
|
volume_mounts=volume_mounts,
|
|
477
477
|
pty_info=pty_info,
|
|
478
478
|
scheduler_placement=scheduler_placement,
|
|
@@ -845,6 +845,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
845
845
|
verbose: bool = False,
|
|
846
846
|
custom_domain: str | None = None,
|
|
847
847
|
client: _Client | None = None,
|
|
848
|
+
_experimental_enable_snapshot: bool = False,
|
|
848
849
|
) -> "_Sandbox":
|
|
849
850
|
"""Create a sandbox using the V2 backend.
|
|
850
851
|
|
|
@@ -860,8 +861,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
860
861
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
861
862
|
in MiB), matching `Sandbox.create()`.
|
|
862
863
|
|
|
863
|
-
|
|
864
|
-
|
|
864
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
865
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
866
|
+
file systems and GPUs are not supported.
|
|
865
867
|
|
|
866
868
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
867
869
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -961,8 +963,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
961
963
|
for _, vol in validated_volumes:
|
|
962
964
|
dep_tasks.append(resolver.load(vol, load_context))
|
|
963
965
|
for _, cloud_bucket_mount in cloud_bucket_mounts:
|
|
964
|
-
if cloud_bucket_mount.secret:
|
|
965
|
-
|
|
966
|
+
if secret := cloud_bucket_mount.secret:
|
|
967
|
+
if not secret._is_ephemeral:
|
|
968
|
+
dep_tasks.append(resolver.load(cloud_bucket_mount.secret, load_context))
|
|
966
969
|
if proxy:
|
|
967
970
|
dep_tasks.append(resolver.load(proxy, load_context))
|
|
968
971
|
dep_timings = await _gather_load_with_timings(dep_tasks) if dep_tasks else []
|
|
@@ -971,6 +974,10 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
971
974
|
|
|
972
975
|
volume_mounts = [_volume_to_mount_proto(path, volume) for path, volume in validated_volumes]
|
|
973
976
|
|
|
977
|
+
cloud_bucket_mount_protos, cloud_bucket_credientials = cloud_bucket_mounts_to_proto(
|
|
978
|
+
cloud_bucket_mounts, split_ephemeral_credentials=True
|
|
979
|
+
)
|
|
980
|
+
|
|
974
981
|
definition = api_pb2.Sandbox(
|
|
975
982
|
entrypoint_args=args,
|
|
976
983
|
image_id=image.object_id,
|
|
@@ -995,12 +1002,13 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
995
1002
|
inbound_cidr_allowlist=list(inbound_cidr_allowlist) if inbound_cidr_allowlist is not None else [],
|
|
996
1003
|
i6pn_enabled=i6pn,
|
|
997
1004
|
volume_mounts=volume_mounts,
|
|
998
|
-
cloud_bucket_mounts=
|
|
1005
|
+
cloud_bucket_mounts=cloud_bucket_mount_protos,
|
|
999
1006
|
readiness_probe=(readiness_probe._to_proto() if readiness_probe else None),
|
|
1000
1007
|
experimental_options_v2=(
|
|
1001
1008
|
{k: str(v) for k, v in experimental_options.items()} if experimental_options else None
|
|
1002
1009
|
),
|
|
1003
1010
|
custom_domain=custom_domain,
|
|
1011
|
+
enable_snapshot=_experimental_enable_snapshot,
|
|
1004
1012
|
)
|
|
1005
1013
|
|
|
1006
1014
|
tag_protos = [api_pb2.SandboxTag(tag_name=k, tag_value=v) for k, v in tags.items()] if tags else []
|
|
@@ -1009,6 +1017,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1009
1017
|
definition=definition,
|
|
1010
1018
|
ephemeral_secrets=api_pb2.StringMap(contents=env_dict) if env_dict else None,
|
|
1011
1019
|
tags=tag_protos,
|
|
1020
|
+
cloud_bucket_mount_credentials=cloud_bucket_credientials,
|
|
1012
1021
|
)
|
|
1013
1022
|
assert load_context.client._auth_token_manager
|
|
1014
1023
|
auth_token = await load_context.client._auth_token_manager.get_token()
|
|
@@ -2079,19 +2088,27 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2079
2088
|
)
|
|
2080
2089
|
|
|
2081
2090
|
async def _experimental_snapshot(self) -> _SandboxSnapshot:
|
|
2082
|
-
self.
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2091
|
+
if self._is_v2:
|
|
2092
|
+
task_id = await self._get_task_id()
|
|
2093
|
+
command_router_client = await self._get_command_router_client(task_id)
|
|
2094
|
+
snap_v2_resp = await command_router_client.snapshot_memory(
|
|
2095
|
+
sr_pb2.TaskSnapshotMemoryRequest(task_id=task_id, idempotency_key=str(uuid.uuid4())),
|
|
2096
|
+
timeout=55.0,
|
|
2097
|
+
)
|
|
2098
|
+
snapshot_id = snap_v2_resp.snapshot_id
|
|
2099
|
+
else:
|
|
2100
|
+
await self._get_task_id()
|
|
2101
|
+
snap_req = api_pb2.SandboxSnapshotRequest(sandbox_id=self.object_id)
|
|
2102
|
+
snap_resp = await self._client.stub.SandboxSnapshot(snap_req)
|
|
2086
2103
|
|
|
2087
|
-
|
|
2104
|
+
snapshot_id = snap_resp.snapshot_id
|
|
2088
2105
|
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2106
|
+
# wait for the snapshot to succeed. this is implemented as a second idempotent rpc
|
|
2107
|
+
# because the snapshot itself may take a while to complete.
|
|
2108
|
+
wait_req = api_pb2.SandboxSnapshotWaitRequest(snapshot_id=snapshot_id, timeout=55.0)
|
|
2109
|
+
wait_resp = await self._client.stub.SandboxSnapshotWait(wait_req)
|
|
2110
|
+
if wait_resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
|
|
2111
|
+
raise ExecutionError(wait_resp.result.exception)
|
|
2095
2112
|
|
|
2096
2113
|
async def _load(
|
|
2097
2114
|
self: _SandboxSnapshot, resolver: Resolver, load_context: LoadContext, existing_object_id: str | None
|
|
@@ -2102,7 +2119,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2102
2119
|
rep = "SandboxSnapshot()"
|
|
2103
2120
|
# TODO: use ._new_hydrated instead
|
|
2104
2121
|
obj = _SandboxSnapshot._from_loader(_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext.empty())
|
|
2105
|
-
|
|
2122
|
+
metadata = api_pb2.SandboxSnapshotHandleMetadata(is_v2=self._is_v2)
|
|
2123
|
+
obj._hydrate(snapshot_id, self._client, metadata)
|
|
2106
2124
|
|
|
2107
2125
|
return obj
|
|
2108
2126
|
|
|
@@ -2113,27 +2131,41 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2113
2131
|
*,
|
|
2114
2132
|
name: str | None = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
2115
2133
|
):
|
|
2134
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
2135
|
+
|
|
2136
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
2137
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
2138
|
+
"""
|
|
2116
2139
|
client = client or await _Client.from_env()
|
|
2117
2140
|
|
|
2141
|
+
if snapshot._is_v2 is None:
|
|
2142
|
+
await snapshot.hydrate(client=client)
|
|
2143
|
+
use_v2 = bool(snapshot._is_v2)
|
|
2144
|
+
|
|
2118
2145
|
if name is not None and name != _DEFAULT_SANDBOX_NAME_OVERRIDE:
|
|
2119
2146
|
check_object_name(name, "Sandbox")
|
|
2120
2147
|
|
|
2121
2148
|
if name is _DEFAULT_SANDBOX_NAME_OVERRIDE:
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_UNSPECIFIED,
|
|
2125
|
-
)
|
|
2149
|
+
override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_UNSPECIFIED
|
|
2150
|
+
name_override = None
|
|
2126
2151
|
elif name is None:
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
sandbox_name_override_type=api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_NONE,
|
|
2130
|
-
)
|
|
2152
|
+
override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_NONE
|
|
2153
|
+
name_override = None
|
|
2131
2154
|
else:
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2155
|
+
override_type = api_pb2.SandboxRestoreRequest.SANDBOX_NAME_OVERRIDE_TYPE_STRING
|
|
2156
|
+
name_override = name
|
|
2157
|
+
|
|
2158
|
+
if use_v2:
|
|
2159
|
+
return await _Sandbox._experimental_from_snapshot_v2(
|
|
2160
|
+
snapshot, client, override_type=override_type, name_override=name_override
|
|
2136
2161
|
)
|
|
2162
|
+
|
|
2163
|
+
restore_req = api_pb2.SandboxRestoreRequest(
|
|
2164
|
+
snapshot_id=snapshot.object_id,
|
|
2165
|
+
sandbox_name_override_type=override_type,
|
|
2166
|
+
)
|
|
2167
|
+
if name_override is not None:
|
|
2168
|
+
restore_req.sandbox_name_override = name_override
|
|
2137
2169
|
# Pin the restored Sandbox to a specific worker when MODAL_WORKER_ID is
|
|
2138
2170
|
# set.
|
|
2139
2171
|
if worker_id := config.get("worker_id"):
|
|
@@ -2153,6 +2185,33 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
2153
2185
|
raise ExecutionError(resp.task_result.exception)
|
|
2154
2186
|
return sandbox
|
|
2155
2187
|
|
|
2188
|
+
@staticmethod
|
|
2189
|
+
async def _experimental_from_snapshot_v2(
|
|
2190
|
+
snapshot: _SandboxSnapshot,
|
|
2191
|
+
client: _Client,
|
|
2192
|
+
*,
|
|
2193
|
+
override_type: "api_pb2.SandboxRestoreRequest.SandboxNameOverrideType.ValueType",
|
|
2194
|
+
name_override: str | None,
|
|
2195
|
+
) -> "_Sandbox":
|
|
2196
|
+
restore_req = api_pb2.SandboxRestoreV2Request(
|
|
2197
|
+
snapshot_id=snapshot.object_id,
|
|
2198
|
+
sandbox_name_override_type=override_type,
|
|
2199
|
+
)
|
|
2200
|
+
if name_override is not None:
|
|
2201
|
+
restore_req.sandbox_name_override = name_override
|
|
2202
|
+
if worker_id := config.get("worker_id"):
|
|
2203
|
+
restore_req.worker_id = worker_id
|
|
2204
|
+
|
|
2205
|
+
assert client._auth_token_manager
|
|
2206
|
+
auth_token = await client._auth_token_manager.get_token()
|
|
2207
|
+
restore_resp: api_pb2.SandboxRestoreV2Response = await client.stub.SandboxRestoreV2(
|
|
2208
|
+
restore_req, metadata=[("x-modal-auth-token", auth_token)]
|
|
2209
|
+
)
|
|
2210
|
+
|
|
2211
|
+
sandbox = await _Sandbox.from_id(restore_resp.sandbox_id, client)
|
|
2212
|
+
sandbox._task_id = restore_resp.task_id
|
|
2213
|
+
return sandbox
|
|
2214
|
+
|
|
2156
2215
|
@property
|
|
2157
2216
|
def filesystem(self) -> _SandboxFilesystem:
|
|
2158
2217
|
"""Namespace for Sandbox filesystem APIs."""
|
|
@@ -393,6 +393,7 @@ class _Sandbox(modal._object._Object):
|
|
|
393
393
|
verbose: bool = False,
|
|
394
394
|
custom_domain: typing.Optional[str] = None,
|
|
395
395
|
client: typing.Optional[modal.client._Client] = None,
|
|
396
|
+
_experimental_enable_snapshot: bool = False,
|
|
396
397
|
) -> _Sandbox:
|
|
397
398
|
"""Create a sandbox using the V2 backend.
|
|
398
399
|
|
|
@@ -408,8 +409,9 @@ class _Sandbox(modal._object._Object):
|
|
|
408
409
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
409
410
|
in MiB), matching `Sandbox.create()`.
|
|
410
411
|
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
413
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
414
|
+
file systems and GPUs are not supported.
|
|
413
415
|
|
|
414
416
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
415
417
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -852,7 +854,22 @@ class _Sandbox(modal._object._Object):
|
|
|
852
854
|
client: typing.Optional[modal.client._Client] = None,
|
|
853
855
|
*,
|
|
854
856
|
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
855
|
-
):
|
|
857
|
+
):
|
|
858
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
859
|
+
|
|
860
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
861
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
862
|
+
"""
|
|
863
|
+
...
|
|
864
|
+
|
|
865
|
+
@staticmethod
|
|
866
|
+
async def _experimental_from_snapshot_v2(
|
|
867
|
+
snapshot: modal.snapshot._SandboxSnapshot,
|
|
868
|
+
client: modal.client._Client,
|
|
869
|
+
*,
|
|
870
|
+
override_type: int,
|
|
871
|
+
name_override: typing.Optional[str],
|
|
872
|
+
) -> _Sandbox: ...
|
|
856
873
|
@property
|
|
857
874
|
def filesystem(self) -> modal.sandbox_fs._SandboxFilesystem:
|
|
858
875
|
"""Namespace for Sandbox filesystem APIs."""
|
|
@@ -1768,6 +1785,7 @@ class Sandbox(modal.object.Object):
|
|
|
1768
1785
|
verbose: bool = False,
|
|
1769
1786
|
custom_domain: typing.Optional[str] = None,
|
|
1770
1787
|
client: typing.Optional[modal.client.Client] = None,
|
|
1788
|
+
_experimental_enable_snapshot: bool = False,
|
|
1771
1789
|
) -> Sandbox:
|
|
1772
1790
|
"""Create a sandbox using the V2 backend.
|
|
1773
1791
|
|
|
@@ -1783,8 +1801,9 @@ class Sandbox(modal.object.Object):
|
|
|
1783
1801
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
1784
1802
|
in MiB), matching `Sandbox.create()`.
|
|
1785
1803
|
|
|
1786
|
-
|
|
1787
|
-
|
|
1804
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
1805
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
1806
|
+
file systems and GPUs are not supported.
|
|
1788
1807
|
|
|
1789
1808
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
1790
1809
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -1837,6 +1856,7 @@ class Sandbox(modal.object.Object):
|
|
|
1837
1856
|
verbose: bool = False,
|
|
1838
1857
|
custom_domain: typing.Optional[str] = None,
|
|
1839
1858
|
client: typing.Optional[modal.client.Client] = None,
|
|
1859
|
+
_experimental_enable_snapshot: bool = False,
|
|
1840
1860
|
) -> Sandbox:
|
|
1841
1861
|
"""Create a sandbox using the V2 backend.
|
|
1842
1862
|
|
|
@@ -1852,8 +1872,9 @@ class Sandbox(modal.object.Object):
|
|
|
1852
1872
|
tuple that additionally sets a hard limit (fractional CPU cores; memory
|
|
1853
1873
|
in MiB), matching `Sandbox.create()`.
|
|
1854
1874
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1875
|
+
Pass `_experimental_enable_snapshot=True` to create a sandbox that can be
|
|
1876
|
+
snapshotted with `._experimental_snapshot()`. Features like network
|
|
1877
|
+
file systems and GPUs are not supported.
|
|
1857
1878
|
|
|
1858
1879
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
1859
1880
|
workspace can address each other directly at their `i6pn.modal.local`
|
|
@@ -2802,7 +2823,14 @@ class Sandbox(modal.object.Object):
|
|
|
2802
2823
|
client: typing.Optional[modal.client.Client] = None,
|
|
2803
2824
|
*,
|
|
2804
2825
|
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
2805
|
-
):
|
|
2826
|
+
):
|
|
2827
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
2828
|
+
|
|
2829
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
2830
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
2831
|
+
"""
|
|
2832
|
+
...
|
|
2833
|
+
|
|
2806
2834
|
async def aio(
|
|
2807
2835
|
self,
|
|
2808
2836
|
/,
|
|
@@ -2810,10 +2838,38 @@ class Sandbox(modal.object.Object):
|
|
|
2810
2838
|
client: typing.Optional[modal.client.Client] = None,
|
|
2811
2839
|
*,
|
|
2812
2840
|
name: typing.Optional[str] = _DEFAULT_SANDBOX_NAME_OVERRIDE,
|
|
2813
|
-
):
|
|
2841
|
+
):
|
|
2842
|
+
"""Restore a Sandbox from a memory snapshot.
|
|
2843
|
+
|
|
2844
|
+
The restore targets the same backend the snapshot was taken from. A V1
|
|
2845
|
+
snapshot restores as a V1 sandbox, a V2 snapshot as a V2 sandbox.
|
|
2846
|
+
"""
|
|
2847
|
+
...
|
|
2814
2848
|
|
|
2815
2849
|
_experimental_from_snapshot: typing.ClassVar[___experimental_from_snapshot_spec]
|
|
2816
2850
|
|
|
2851
|
+
class ___experimental_from_snapshot_v2_spec(typing_extensions.Protocol):
|
|
2852
|
+
def __call__(
|
|
2853
|
+
self,
|
|
2854
|
+
/,
|
|
2855
|
+
snapshot: modal.snapshot.SandboxSnapshot,
|
|
2856
|
+
client: modal.client.Client,
|
|
2857
|
+
*,
|
|
2858
|
+
override_type: int,
|
|
2859
|
+
name_override: typing.Optional[str],
|
|
2860
|
+
) -> Sandbox: ...
|
|
2861
|
+
async def aio(
|
|
2862
|
+
self,
|
|
2863
|
+
/,
|
|
2864
|
+
snapshot: modal.snapshot.SandboxSnapshot,
|
|
2865
|
+
client: modal.client.Client,
|
|
2866
|
+
*,
|
|
2867
|
+
override_type: int,
|
|
2868
|
+
name_override: typing.Optional[str],
|
|
2869
|
+
) -> Sandbox: ...
|
|
2870
|
+
|
|
2871
|
+
_experimental_from_snapshot_v2: typing.ClassVar[___experimental_from_snapshot_v2_spec]
|
|
2872
|
+
|
|
2817
2873
|
@property
|
|
2818
2874
|
def filesystem(self) -> modal.sandbox_fs.SandboxFilesystem:
|
|
2819
2875
|
"""Namespace for Sandbox filesystem APIs."""
|
|
@@ -263,6 +263,15 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
263
263
|
assert self._metadata
|
|
264
264
|
return self._metadata
|
|
265
265
|
|
|
266
|
+
@property
|
|
267
|
+
def _is_ephemeral(self) -> bool:
|
|
268
|
+
"""Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
|
|
269
|
+
|
|
270
|
+
True for Secrets created via `from_dict`, `from_dotenv`, or `from_local_environ`, whose contents
|
|
271
|
+
can be resolved locally and inlined; False for `from_name` references that must be resolved server-side.
|
|
272
|
+
"""
|
|
273
|
+
return self._load_env_dict is not None
|
|
274
|
+
|
|
266
275
|
@staticmethod
|
|
267
276
|
def from_dict(
|
|
268
277
|
env_dict: dict[str, str | None] = {},
|
|
@@ -432,6 +432,15 @@ class _Secret(modal._object._Object):
|
|
|
432
432
|
def name(self) -> typing.Optional[str]: ...
|
|
433
433
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
434
434
|
def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
|
|
435
|
+
@property
|
|
436
|
+
def _is_ephemeral(self) -> bool:
|
|
437
|
+
"""Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
|
|
438
|
+
|
|
439
|
+
True for Secrets created via `from_dict`, `from_dotenv`, or `from_local_environ`, whose contents
|
|
440
|
+
can be resolved locally and inlined; False for `from_name` references that must be resolved server-side.
|
|
441
|
+
"""
|
|
442
|
+
...
|
|
443
|
+
|
|
435
444
|
@staticmethod
|
|
436
445
|
def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> _Secret:
|
|
437
446
|
"""Create a Secret from a dictionary of environment variable names to string values.
|
|
@@ -588,6 +597,15 @@ class Secret(modal.object.Object):
|
|
|
588
597
|
def name(self) -> typing.Optional[str]: ...
|
|
589
598
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
590
599
|
def _get_metadata(self) -> modal_proto.api_pb2.SecretMetadata: ...
|
|
600
|
+
@property
|
|
601
|
+
def _is_ephemeral(self) -> bool:
|
|
602
|
+
"""Whether this Secret is backed by a locally-resolvable env dict rather than a named deployment.
|
|
603
|
+
|
|
604
|
+
True for Secrets created via `from_dict`, `from_dotenv`, or `from_local_environ`, whose contents
|
|
605
|
+
can be resolved locally and inlined; False for `from_name` references that must be resolved server-side.
|
|
606
|
+
"""
|
|
607
|
+
...
|
|
608
|
+
|
|
591
609
|
@staticmethod
|
|
592
610
|
def from_dict(env_dict: dict[str, typing.Optional[str]] = {}) -> Secret:
|
|
593
611
|
"""Create a Secret from a dictionary of environment variable names to string values.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from typing import cast
|
|
3
3
|
|
|
4
4
|
import typing_extensions
|
|
5
|
+
from google.protobuf.message import Message
|
|
5
6
|
|
|
6
7
|
import modal.client
|
|
7
8
|
from modal_proto import api_pb2
|
|
@@ -22,6 +23,21 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
|
|
|
22
23
|
the original Sandbox at the time the snapshot was taken.
|
|
23
24
|
"""
|
|
24
25
|
|
|
26
|
+
_metadata: "api_pb2.SandboxSnapshotHandleMetadata | None" = None
|
|
27
|
+
|
|
28
|
+
def _hydrate_metadata(self, metadata: Message | None):
|
|
29
|
+
if metadata:
|
|
30
|
+
assert isinstance(metadata, api_pb2.SandboxSnapshotHandleMetadata)
|
|
31
|
+
self._metadata = metadata
|
|
32
|
+
|
|
33
|
+
def _get_metadata(self) -> "api_pb2.SandboxSnapshotHandleMetadata | None":
|
|
34
|
+
return self._metadata
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def _is_v2(self) -> bool | None:
|
|
38
|
+
"""Whether the snapshot came from a V2 sandbox."""
|
|
39
|
+
return self._metadata.is_v2 if self._metadata is not None else None
|
|
40
|
+
|
|
25
41
|
@deprecate_aio_usage((2025, 12, 5), "SandboxSnapshot.from_id")
|
|
26
42
|
@classmethod
|
|
27
43
|
def from_id(cls, sandbox_snapshot_id: str, client: "modal.client.Client | None" = None) -> typing_extensions.Self:
|
|
@@ -45,7 +61,7 @@ class _SandboxSnapshot(_Object, type_prefix="sn"):
|
|
|
45
61
|
resp: api_pb2.SandboxSnapshotGetResponse = await load_context.client.stub.SandboxSnapshotGet(
|
|
46
62
|
api_pb2.SandboxSnapshotGetRequest(snapshot_id=sandbox_snapshot_id)
|
|
47
63
|
)
|
|
48
|
-
self._hydrate(resp.snapshot_id, load_context.client,
|
|
64
|
+
self._hydrate(resp.snapshot_id, load_context.client, resp.handle_metadata)
|
|
49
65
|
|
|
50
66
|
rep = "SandboxSnapshot()"
|
|
51
67
|
obj = _SandboxSnapshot._from_loader(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import google.protobuf.message
|
|
1
2
|
import modal._object
|
|
2
3
|
import modal.client
|
|
3
4
|
import modal.object
|
|
5
|
+
import modal_proto.api_pb2
|
|
4
6
|
import typing
|
|
5
7
|
import typing_extensions
|
|
6
8
|
|
|
@@ -13,6 +15,16 @@ class _SandboxSnapshot(modal._object._Object):
|
|
|
13
15
|
`._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
|
|
14
16
|
the original Sandbox at the time the snapshot was taken.
|
|
15
17
|
"""
|
|
18
|
+
|
|
19
|
+
_metadata: typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]
|
|
20
|
+
|
|
21
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
22
|
+
def _get_metadata(self) -> typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]: ...
|
|
23
|
+
@property
|
|
24
|
+
def _is_v2(self) -> typing.Optional[bool]:
|
|
25
|
+
"""Whether the snapshot came from a V2 sandbox."""
|
|
26
|
+
...
|
|
27
|
+
|
|
16
28
|
class __from_id_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
17
29
|
def __call__(
|
|
18
30
|
self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None
|
|
@@ -39,10 +51,20 @@ class SandboxSnapshot(modal.object.Object):
|
|
|
39
51
|
`._experimental_snapshot()` on a Sandbox instance. This includes both the filesystem and memory state of
|
|
40
52
|
the original Sandbox at the time the snapshot was taken.
|
|
41
53
|
"""
|
|
54
|
+
|
|
55
|
+
_metadata: typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]
|
|
56
|
+
|
|
42
57
|
def __init__(self, *args, **kwargs):
|
|
43
58
|
"""mdmd:hidden"""
|
|
44
59
|
...
|
|
45
60
|
|
|
61
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
62
|
+
def _get_metadata(self) -> typing.Optional[modal_proto.api_pb2.SandboxSnapshotHandleMetadata]: ...
|
|
63
|
+
@property
|
|
64
|
+
def _is_v2(self) -> typing.Optional[bool]:
|
|
65
|
+
"""Whether the snapshot came from a V2 sandbox."""
|
|
66
|
+
...
|
|
67
|
+
|
|
46
68
|
class __from_id_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
47
69
|
def __call__(
|
|
48
70
|
self, /, sandbox_snapshot_id: str, client: typing.Optional[modal.client.Client] = None
|