modal 1.4.4.dev0__tar.gz → 1.4.4.dev2__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.4.dev0 → modal-1.4.4.dev2}/PKG-INFO +1 -1
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/user_code_imports.py +1 -1
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_server.py +5 -2
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/app.py +5 -4
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/app.pyi +4 -4
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/client.pyi +2 -2
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/experimental/flash.py +26 -8
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/experimental/flash.pyi +19 -1
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/sandbox.py +60 -8
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/sandbox.pyi +47 -12
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/server.pyi +1 -1
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/api_grpc.py +16 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/api_pb2.py +825 -785
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/api_pb2.pyi +87 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_version/__init__.py +1 -1
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/LICENSE +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/README.md +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/__main__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_billing.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_clustered_functions.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_environments.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_function_variants.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_functions.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_grpc_client.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_ipython.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_load_context.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_location.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_logs.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_object.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_output/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_output/manager.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_output/pty.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_output/rich.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_output/status.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_partial_function.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_resolver.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_resources.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_serialization.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_traceback.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_tunnel.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_tunnel.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_type_manager.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/logger.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_vendor/version.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/_watcher.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/billing.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/README.md +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/builder/base-images.json +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/call_graph.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/_download.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/_help.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/_traceback.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/app.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/billing.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/bootstrap.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/changelog.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/cluster.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/config.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/container.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/dashboard.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/dict.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/entry_point.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/environment.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/import_refs.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/launch.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/logo.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/profile.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/queues.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/run.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/secret.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/selector.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/shell.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/token.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/utils.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cli/volume.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/client.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cls.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/cls.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/config.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/container_process.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/container_process.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/dict.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/dict.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/environments.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/environments.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/exception.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/experimental/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/experimental/ipython.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/file_io.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/file_io.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/functions.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/functions.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/image.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/image.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/io_streams.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/io_streams.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/mount.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/mount.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/network_file_system.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/network_file_system.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/object.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/object.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/output.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/parallel_map.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/parallel_map.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/partial_function.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/partial_function.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/proxy.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/proxy.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/py.typed +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/queue.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/queue.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/retries.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/runner.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/runner.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/running_app.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/sandbox_fs.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/sandbox_fs.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/schedule.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/scheduler_placement.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/secret.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/secret.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/server.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/serving.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/serving.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/snapshot.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/snapshot.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/stream_type.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/token_flow.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/token_flow.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/volume.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal/volume.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/__init__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/py.typed +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/modal_version/__main__.py +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/pyproject.toml +0 -0
- {modal-1.4.4.dev0 → modal-1.4.4.dev2}/setup.cfg +0 -0
|
@@ -337,7 +337,7 @@ class _LifecycleManager:
|
|
|
337
337
|
container_io_manager: "modal._runtime.container_io_manager.ContainerIOManager",
|
|
338
338
|
):
|
|
339
339
|
# Identify the "enter" methods to run after resuming from a snapshot.
|
|
340
|
-
flash_entry = _FlashContainerEntry(self.function_def.http_config)
|
|
340
|
+
flash_entry = _FlashContainerEntry(self.function_def.http_config, is_server=self.function_def.is_server)
|
|
341
341
|
if not self.function_def.is_auto_snapshot:
|
|
342
342
|
post_snapshot_methods = _find_callables_for_obj(
|
|
343
343
|
self.user_cls_instance, _PartialFunctionFlags.ENTER_POST_SNAPSHOT
|
|
@@ -24,6 +24,7 @@ def validate_http_server_config(
|
|
|
24
24
|
proxy_regions: list[str], # The regions to proxy the HTTP server to.
|
|
25
25
|
startup_timeout: int, # Maximum number of seconds to wait for the HTTP server to start.
|
|
26
26
|
exit_grace_period: Optional[int], # The time to wait for the HTTP server to exit gracefully.
|
|
27
|
+
is_server: bool = False, # Whether this validates a `_experimental_server` config.
|
|
27
28
|
):
|
|
28
29
|
if not isinstance(port, int) or port < 1 or port > 65535:
|
|
29
30
|
raise InvalidError("Port must be a positive integer between 1 and 65535.")
|
|
@@ -31,10 +32,12 @@ def validate_http_server_config(
|
|
|
31
32
|
raise InvalidError("The `startup_timeout` argument must be positive.")
|
|
32
33
|
if exit_grace_period is not None and exit_grace_period < 0:
|
|
33
34
|
raise InvalidError("The `exit_grace_period` argument must be non-negative.")
|
|
34
|
-
if exit_grace_period is not None and exit_grace_period > 25:
|
|
35
|
+
if not is_server and exit_grace_period is not None and exit_grace_period > 25:
|
|
35
36
|
raise InvalidError("The `exit_grace_period` argument must not exceed 25 seconds.")
|
|
36
37
|
|
|
37
38
|
if not proxy_regions:
|
|
39
|
+
if is_server:
|
|
40
|
+
raise InvalidError("The `routing_regions` argument must be non-empty.")
|
|
38
41
|
raise InvalidError("The `proxy_regions` argument must be non-empty.")
|
|
39
42
|
|
|
40
43
|
|
|
@@ -47,7 +50,7 @@ class _Server:
|
|
|
47
50
|
Instead, use the [`@app._experimental_server()`](https://modal.com/docs/reference/modal.App#server) decorator.
|
|
48
51
|
|
|
49
52
|
```python notest
|
|
50
|
-
@app._experimental_server(port=8000,
|
|
53
|
+
@app._experimental_server(port=8000, routing_regions=["us-east", "us-west"])
|
|
51
54
|
class MyServer:
|
|
52
55
|
@modal.enter()
|
|
53
56
|
def start_server(self):
|
|
@@ -1156,7 +1156,7 @@ class _App:
|
|
|
1156
1156
|
port: int = 8000, # Port the HTTP server listens on
|
|
1157
1157
|
startup_timeout: int = 30, # Maximum startup time in seconds
|
|
1158
1158
|
exit_grace_period: int = 0, # Grace period for in-flight requests on shutdown
|
|
1159
|
-
|
|
1159
|
+
routing_regions: list[str] = ["us-east"], # Required: Regions to deploy proxy endpoints
|
|
1160
1160
|
h2_enabled: bool = False, # Enable HTTP/2
|
|
1161
1161
|
target_concurrency: Optional[int] = None, # Target concurrency for the server
|
|
1162
1162
|
cloud: Optional[str] = None, # Cloud provider (aws, gcp, oci, auto)
|
|
@@ -1178,7 +1178,7 @@ class _App:
|
|
|
1178
1178
|
Example:
|
|
1179
1179
|
|
|
1180
1180
|
```python
|
|
1181
|
-
@app._experimental_server(port=8000,
|
|
1181
|
+
@app._experimental_server(port=8000, routing_regions=["us-east"])
|
|
1182
1182
|
class MyServer:
|
|
1183
1183
|
@modal.enter()
|
|
1184
1184
|
def start(self):
|
|
@@ -1195,9 +1195,10 @@ class _App:
|
|
|
1195
1195
|
# Validate HTTP server config
|
|
1196
1196
|
validate_http_server_config(
|
|
1197
1197
|
port=port,
|
|
1198
|
-
proxy_regions=
|
|
1198
|
+
proxy_regions=routing_regions,
|
|
1199
1199
|
startup_timeout=startup_timeout,
|
|
1200
1200
|
exit_grace_period=exit_grace_period,
|
|
1201
|
+
is_server=True,
|
|
1201
1202
|
)
|
|
1202
1203
|
|
|
1203
1204
|
if target_concurrency is not None:
|
|
@@ -1206,7 +1207,7 @@ class _App:
|
|
|
1206
1207
|
|
|
1207
1208
|
http_config = api_pb2.HTTPConfig(
|
|
1208
1209
|
port=port,
|
|
1209
|
-
proxy_regions=
|
|
1210
|
+
proxy_regions=routing_regions,
|
|
1210
1211
|
startup_timeout=startup_timeout,
|
|
1211
1212
|
exit_grace_period=exit_grace_period,
|
|
1212
1213
|
h2_enabled=h2_enabled,
|
|
@@ -577,7 +577,7 @@ class _App:
|
|
|
577
577
|
port: int = 8000,
|
|
578
578
|
startup_timeout: int = 30,
|
|
579
579
|
exit_grace_period: int = 0,
|
|
580
|
-
|
|
580
|
+
routing_regions: list[str] = ["us-east"],
|
|
581
581
|
h2_enabled: bool = False,
|
|
582
582
|
target_concurrency: typing.Optional[int] = None,
|
|
583
583
|
cloud: typing.Optional[str] = None,
|
|
@@ -599,7 +599,7 @@ class _App:
|
|
|
599
599
|
Example:
|
|
600
600
|
|
|
601
601
|
```python
|
|
602
|
-
@app._experimental_server(port=8000,
|
|
602
|
+
@app._experimental_server(port=8000, routing_regions=["us-east"])
|
|
603
603
|
class MyServer:
|
|
604
604
|
@modal.enter()
|
|
605
605
|
def start(self):
|
|
@@ -1297,7 +1297,7 @@ class App:
|
|
|
1297
1297
|
port: int = 8000,
|
|
1298
1298
|
startup_timeout: int = 30,
|
|
1299
1299
|
exit_grace_period: int = 0,
|
|
1300
|
-
|
|
1300
|
+
routing_regions: list[str] = ["us-east"],
|
|
1301
1301
|
h2_enabled: bool = False,
|
|
1302
1302
|
target_concurrency: typing.Optional[int] = None,
|
|
1303
1303
|
cloud: typing.Optional[str] = None,
|
|
@@ -1317,7 +1317,7 @@ class App:
|
|
|
1317
1317
|
Example:
|
|
1318
1318
|
|
|
1319
1319
|
```python
|
|
1320
|
-
@app._experimental_server(port=8000,
|
|
1320
|
+
@app._experimental_server(port=8000, routing_regions=["us-east"])
|
|
1321
1321
|
class MyServer:
|
|
1322
1322
|
@modal.enter()
|
|
1323
1323
|
def start(self):
|
|
@@ -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.4.
|
|
38
|
+
version: str = "1.4.4.dev2",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -175,7 +175,7 @@ class Client:
|
|
|
175
175
|
server_url: str,
|
|
176
176
|
client_type: int,
|
|
177
177
|
credentials: typing.Optional[tuple[str, str]],
|
|
178
|
-
version: str = "1.4.4.
|
|
178
|
+
version: str = "1.4.4.dev2",
|
|
179
179
|
):
|
|
180
180
|
"""mdmd:hidden
|
|
181
181
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -37,6 +37,7 @@ class _FlashManager:
|
|
|
37
37
|
startup_timeout: int = 30,
|
|
38
38
|
exit_grace_period: int = 0,
|
|
39
39
|
h2_enabled: bool = False,
|
|
40
|
+
is_server: bool = False,
|
|
40
41
|
):
|
|
41
42
|
self.client = client
|
|
42
43
|
self.port = port
|
|
@@ -46,9 +47,11 @@ class _FlashManager:
|
|
|
46
47
|
self.startup_timeout = startup_timeout
|
|
47
48
|
self.exit_grace_period = exit_grace_period
|
|
48
49
|
self.tunnel_manager = _forward_tunnel(port, h2_enabled=h2_enabled, client=client)
|
|
50
|
+
self.is_server = is_server
|
|
49
51
|
self.stopped = False
|
|
50
52
|
self.num_heartbeat_failures = 0
|
|
51
53
|
self.task_id = os.environ["MODAL_TASK_ID"]
|
|
54
|
+
self.heartbeat_task = None
|
|
52
55
|
|
|
53
56
|
async def is_port_connection_healthy(
|
|
54
57
|
self, process: Optional[subprocess.Popen], timeout: float = 0.5
|
|
@@ -84,13 +87,22 @@ class _FlashManager:
|
|
|
84
87
|
host = parsed_url.hostname
|
|
85
88
|
port = parsed_url.port or 443
|
|
86
89
|
|
|
90
|
+
if self.is_server:
|
|
91
|
+
await self._start_server_tunnel()
|
|
92
|
+
return
|
|
93
|
+
await self._start_flash_registration(host, port)
|
|
94
|
+
|
|
95
|
+
async def _start_server_tunnel(self) -> None:
|
|
96
|
+
# Worker-side HTTP relay owns Flash registration and drain for server tasks.
|
|
97
|
+
logger.warning(f"[Modal Flash] Server tunnel opened at {self.tunnel.url}.")
|
|
98
|
+
|
|
99
|
+
async def _start_flash_registration(self, host: str, port: int) -> None:
|
|
87
100
|
try:
|
|
88
101
|
await self._wait_for_port_success(host, port)
|
|
89
102
|
except (Exception, KeyboardInterrupt, asyncio.CancelledError):
|
|
90
103
|
await self._deregister()
|
|
91
104
|
await self.tunnel_manager.__aexit__(*sys.exc_info())
|
|
92
105
|
raise
|
|
93
|
-
|
|
94
106
|
self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
|
|
95
107
|
self.drain_task = asyncio.create_task(self._drain_container())
|
|
96
108
|
|
|
@@ -207,22 +219,24 @@ class _FlashManager:
|
|
|
207
219
|
if self.heartbeat_task:
|
|
208
220
|
self.heartbeat_task.cancel()
|
|
209
221
|
try:
|
|
222
|
+
# NOTE(gongy): We skip calling TunnelStop to avoid interrupting in-flight requests.
|
|
223
|
+
# It is up to the user to wait after calling .stop() to drain in-flight requests.
|
|
210
224
|
await asyncio.wait_for(self.heartbeat_task, timeout=5)
|
|
225
|
+
logger.warning(f"[Modal Flash] Stopping heartbeat task on {self.tunnel.url}.")
|
|
211
226
|
except (asyncio.TimeoutError, asyncio.CancelledError):
|
|
212
227
|
logger.warning("[Modal Flash] Heartbeat task did not stop within 5s.")
|
|
213
228
|
except Exception as e:
|
|
214
229
|
logger.error(f"[Modal Flash] Error stopping: {e}")
|
|
215
230
|
self.stopped = True
|
|
216
|
-
logger.warning(f"[Modal Flash] No longer accepting new requests on {self.tunnel.url}.")
|
|
217
|
-
|
|
218
|
-
# NOTE(gongy): We skip calling TunnelStop to avoid interrupting in-flight requests.
|
|
219
|
-
# It is up to the user to wait after calling .stop() to drain in-flight requests.
|
|
220
231
|
|
|
221
232
|
async def close(self):
|
|
222
233
|
if not self.stopped:
|
|
223
234
|
await self.stop()
|
|
224
235
|
|
|
225
|
-
|
|
236
|
+
# Server tasks drain via the worker-side HTTP relay, so skip the
|
|
237
|
+
# Python-side sleep here to avoid double-counting the grace period.
|
|
238
|
+
if not self.is_server:
|
|
239
|
+
await asyncio.sleep(self.exit_grace_period)
|
|
226
240
|
|
|
227
241
|
logger.warning(f"[Modal Flash] Closing tunnel on {self.tunnel.url}.")
|
|
228
242
|
await self.tunnel_manager.__aexit__(*sys.exc_info())
|
|
@@ -239,6 +253,7 @@ async def flash_forward(
|
|
|
239
253
|
startup_timeout: int = 30,
|
|
240
254
|
exit_grace_period: int = 0,
|
|
241
255
|
h2_enabled: bool = False,
|
|
256
|
+
is_server: bool = False,
|
|
242
257
|
) -> _FlashManager:
|
|
243
258
|
"""
|
|
244
259
|
Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
|
|
@@ -255,6 +270,7 @@ async def flash_forward(
|
|
|
255
270
|
startup_timeout=startup_timeout,
|
|
256
271
|
exit_grace_period=exit_grace_period,
|
|
257
272
|
h2_enabled=h2_enabled,
|
|
273
|
+
is_server=is_server,
|
|
258
274
|
)
|
|
259
275
|
await manager._start()
|
|
260
276
|
return manager
|
|
@@ -716,7 +732,7 @@ def _http_server(
|
|
|
716
732
|
raise InvalidError(
|
|
717
733
|
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.http_server()`."
|
|
718
734
|
)
|
|
719
|
-
validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period)
|
|
735
|
+
validate_http_server_config(port, proxy_regions, startup_timeout, exit_grace_period, is_server=False)
|
|
720
736
|
|
|
721
737
|
from modal._partial_function import _PartialFunction, _PartialFunctionParams
|
|
722
738
|
|
|
@@ -757,9 +773,10 @@ class _FlashContainerEntry:
|
|
|
757
773
|
|
|
758
774
|
flash_manager: Optional[FlashManager] # type: ignore
|
|
759
775
|
|
|
760
|
-
def __init__(self, http_config: api_pb2.HTTPConfig):
|
|
776
|
+
def __init__(self, http_config: api_pb2.HTTPConfig, is_server: bool = False):
|
|
761
777
|
self.http_config: api_pb2.HTTPConfig = http_config
|
|
762
778
|
self.flash_manager = None
|
|
779
|
+
self.is_server = is_server
|
|
763
780
|
|
|
764
781
|
def enter(self):
|
|
765
782
|
if self.http_config != api_pb2.HTTPConfig():
|
|
@@ -775,6 +792,7 @@ class _FlashContainerEntry:
|
|
|
775
792
|
startup_timeout=self.http_config.startup_timeout,
|
|
776
793
|
exit_grace_period=self.http_config.exit_grace_period,
|
|
777
794
|
h2_enabled=self.http_config.h2_enabled,
|
|
795
|
+
is_server=self.is_server,
|
|
778
796
|
)
|
|
779
797
|
except Exception as e:
|
|
780
798
|
logger.warning(f"[Modal Flash] Startup failed: {e}")
|
|
@@ -14,6 +14,7 @@ class _FlashManager:
|
|
|
14
14
|
startup_timeout: int = 30,
|
|
15
15
|
exit_grace_period: int = 0,
|
|
16
16
|
h2_enabled: bool = False,
|
|
17
|
+
is_server: bool = False,
|
|
17
18
|
):
|
|
18
19
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
19
20
|
...
|
|
@@ -22,6 +23,8 @@ class _FlashManager:
|
|
|
22
23
|
self, process: typing.Optional[subprocess.Popen], timeout: float = 0.5
|
|
23
24
|
) -> tuple[bool, typing.Optional[Exception]]: ...
|
|
24
25
|
async def _start(self): ...
|
|
26
|
+
async def _start_server_tunnel(self) -> None: ...
|
|
27
|
+
async def _start_flash_registration(self, host: str, port: int) -> None: ...
|
|
25
28
|
async def _deregister(self): ...
|
|
26
29
|
async def _drain_container(self):
|
|
27
30
|
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
|
@@ -43,6 +46,7 @@ class FlashManager:
|
|
|
43
46
|
startup_timeout: int = 30,
|
|
44
47
|
exit_grace_period: int = 0,
|
|
45
48
|
h2_enabled: bool = False,
|
|
49
|
+
is_server: bool = False,
|
|
46
50
|
): ...
|
|
47
51
|
|
|
48
52
|
class __is_port_connection_healthy_spec(typing_extensions.Protocol):
|
|
@@ -61,6 +65,18 @@ class FlashManager:
|
|
|
61
65
|
|
|
62
66
|
_start: ___start_spec
|
|
63
67
|
|
|
68
|
+
class ___start_server_tunnel_spec(typing_extensions.Protocol):
|
|
69
|
+
def __call__(self, /) -> None: ...
|
|
70
|
+
async def aio(self, /) -> None: ...
|
|
71
|
+
|
|
72
|
+
_start_server_tunnel: ___start_server_tunnel_spec
|
|
73
|
+
|
|
74
|
+
class ___start_flash_registration_spec(typing_extensions.Protocol):
|
|
75
|
+
def __call__(self, /, host: str, port: int) -> None: ...
|
|
76
|
+
async def aio(self, /, host: str, port: int) -> None: ...
|
|
77
|
+
|
|
78
|
+
_start_flash_registration: ___start_flash_registration_spec
|
|
79
|
+
|
|
64
80
|
class ___deregister_spec(typing_extensions.Protocol):
|
|
65
81
|
def __call__(self, /): ...
|
|
66
82
|
async def aio(self, /): ...
|
|
@@ -114,6 +130,7 @@ class __flash_forward_spec(typing_extensions.Protocol):
|
|
|
114
130
|
startup_timeout: int = 30,
|
|
115
131
|
exit_grace_period: int = 0,
|
|
116
132
|
h2_enabled: bool = False,
|
|
133
|
+
is_server: bool = False,
|
|
117
134
|
) -> FlashManager:
|
|
118
135
|
"""Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
|
|
119
136
|
This is a highly experimental method that can break or be removed at any time without warning.
|
|
@@ -130,6 +147,7 @@ class __flash_forward_spec(typing_extensions.Protocol):
|
|
|
130
147
|
startup_timeout: int = 30,
|
|
131
148
|
exit_grace_period: int = 0,
|
|
132
149
|
h2_enabled: bool = False,
|
|
150
|
+
is_server: bool = False,
|
|
133
151
|
) -> FlashManager:
|
|
134
152
|
"""Forward a port to the Modal Flash service, exposing that port as a stable endpoint.
|
|
135
153
|
This is a highly experimental method that can break or be removed at any time without warning.
|
|
@@ -450,7 +468,7 @@ class _FlashContainerEntry:
|
|
|
450
468
|
|
|
451
469
|
flash_manager: typing.Optional[FlashManager]
|
|
452
470
|
|
|
453
|
-
def __init__(self, http_config: modal_proto.api_pb2.HTTPConfig):
|
|
471
|
+
def __init__(self, http_config: modal_proto.api_pb2.HTTPConfig, is_server: bool = False):
|
|
454
472
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
455
473
|
...
|
|
456
474
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
import asyncio
|
|
3
3
|
import builtins
|
|
4
|
+
import enum
|
|
4
5
|
import json
|
|
5
6
|
import os
|
|
6
7
|
import time
|
|
@@ -75,11 +76,47 @@ _default_image: _Image = _Image.debian_slim()
|
|
|
75
76
|
# e.g. 'runsc exec ...'. So we use 2**16 as the limit.
|
|
76
77
|
ARG_MAX_BYTES = 2**16
|
|
77
78
|
TTL_NO_EXPIRY_SENTINEL = -1
|
|
79
|
+
_V1_SANDBOX_ID_ALPHABET = frozenset("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
|
80
|
+
_ULID_ALPHABET = frozenset("0123456789ABCDEFGHJKMNPQRSTVWXYZ")
|
|
78
81
|
|
|
79
82
|
if TYPE_CHECKING:
|
|
80
83
|
import modal.app
|
|
81
84
|
|
|
82
85
|
|
|
86
|
+
class SandboxVersion(enum.Enum):
|
|
87
|
+
V1 = 1
|
|
88
|
+
V2 = 2
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _is_v1_sandbox_id(sandbox_id: str) -> bool:
|
|
92
|
+
prefix, separator, suffix = sandbox_id.partition("-")
|
|
93
|
+
return (
|
|
94
|
+
prefix == "sb"
|
|
95
|
+
and separator == "-"
|
|
96
|
+
and len(suffix) == 22
|
|
97
|
+
and all(ch in _V1_SANDBOX_ID_ALPHABET for ch in suffix)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _is_v2_sandbox_id(sandbox_id: str) -> bool:
|
|
102
|
+
prefix, separator, suffix = sandbox_id.partition("-")
|
|
103
|
+
return (
|
|
104
|
+
prefix == "sb"
|
|
105
|
+
and separator == "-"
|
|
106
|
+
and len(suffix) == 26
|
|
107
|
+
and suffix[0] in "01234567"
|
|
108
|
+
and all(ch in _ULID_ALPHABET for ch in suffix)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _get_sandbox_version(sandbox_id: str) -> SandboxVersion:
|
|
113
|
+
if _is_v2_sandbox_id(sandbox_id):
|
|
114
|
+
return SandboxVersion.V2
|
|
115
|
+
if _is_v1_sandbox_id(sandbox_id):
|
|
116
|
+
return SandboxVersion.V1
|
|
117
|
+
raise InvalidError(f"Invalid Sandbox ID: {sandbox_id!r}")
|
|
118
|
+
|
|
119
|
+
|
|
83
120
|
def _result_returncode(result: Optional[api_pb2.GenericResult]) -> Optional[int]:
|
|
84
121
|
if result is None or result.status == api_pb2.GenericResult.GENERIC_STATUS_UNSPECIFIED:
|
|
85
122
|
return None
|
|
@@ -669,8 +706,17 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
669
706
|
) -> "_Sandbox":
|
|
670
707
|
"""Create a sandbox using the V2 backend.
|
|
671
708
|
|
|
672
|
-
|
|
673
|
-
|
|
709
|
+
Supported features include exec, encrypted tunnels, wait/poll/terminate,
|
|
710
|
+
CPU and memory configuration, region placement,
|
|
711
|
+
and filesystem snapshots.
|
|
712
|
+
|
|
713
|
+
Features like tags, memory snapshots, volumes, network file systems, GPUs,
|
|
714
|
+
custom domains, OIDC identity tokens, and proxies are not supported.
|
|
715
|
+
|
|
716
|
+
V2 sandboxes created with this method are not currently returned by
|
|
717
|
+
`Sandbox.list()` and cannot be looked up with `Sandbox.from_name()`.
|
|
718
|
+
Store `sandbox.object_id` if you need to retrieve the sandbox later, and
|
|
719
|
+
use `Sandbox.from_id(sandbox.object_id)` to reattach.
|
|
674
720
|
"""
|
|
675
721
|
from .app import _App
|
|
676
722
|
|
|
@@ -904,10 +950,18 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
904
950
|
if client is None:
|
|
905
951
|
client = await _Client.from_env()
|
|
906
952
|
|
|
953
|
+
sandbox_version = _get_sandbox_version(sandbox_id)
|
|
954
|
+
is_v2 = sandbox_version == SandboxVersion.V2
|
|
907
955
|
req = api_pb2.SandboxWaitRequest(sandbox_id=sandbox_id, timeout=0)
|
|
908
|
-
|
|
956
|
+
if is_v2:
|
|
957
|
+
assert client._auth_token_manager
|
|
958
|
+
auth_token = await client._auth_token_manager.get_token()
|
|
959
|
+
resp = await client.stub.SandboxWaitV2(req, metadata=[("x-modal-auth-token", auth_token)])
|
|
960
|
+
else:
|
|
961
|
+
resp = await client.stub.SandboxWait(req)
|
|
909
962
|
|
|
910
963
|
obj = _Sandbox._new_hydrated(sandbox_id, client, None)
|
|
964
|
+
obj._is_v2 = is_v2
|
|
911
965
|
|
|
912
966
|
if resp.result.status:
|
|
913
967
|
obj._result = resp.result
|
|
@@ -951,11 +1005,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
951
1005
|
`timeout` If the snapshot does not return within that window, the call is cancelled
|
|
952
1006
|
and `modal.exception.TimeoutError` is raised.
|
|
953
1007
|
"""
|
|
954
|
-
if os.environ.get("MODAL_USE_LEGACY_FILESYSTEM_SNAPSHOT") == "1":
|
|
1008
|
+
if os.environ.get("MODAL_USE_LEGACY_FILESYSTEM_SNAPSHOT") == "1" and not self._is_v2:
|
|
955
1009
|
return await self._legacy_snapshot_filesystem(timeout)
|
|
956
1010
|
|
|
957
|
-
self._ensure_v1("snapshot_filesystem")
|
|
958
|
-
|
|
959
1011
|
task_id = await self._get_task_id()
|
|
960
1012
|
command_router_client = await self._get_command_router_client(task_id)
|
|
961
1013
|
|
|
@@ -998,7 +1050,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
998
1050
|
# You can later mount this snapshot to another Sandbox:
|
|
999
1051
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
1000
1052
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
1001
|
-
sandbox_session_2.
|
|
1053
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1002
1054
|
```
|
|
1003
1055
|
"""
|
|
1004
1056
|
self._ensure_v1("mount_image")
|
|
@@ -1069,7 +1121,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1069
1121
|
# You can later mount this snapshot to another Sandbox:
|
|
1070
1122
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
1071
1123
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
1072
|
-
sandbox_session_2.
|
|
1124
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1073
1125
|
```
|
|
1074
1126
|
"""
|
|
1075
1127
|
self._ensure_v1("snapshot_directory")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _typeshed
|
|
2
2
|
import collections.abc
|
|
3
|
+
import enum
|
|
3
4
|
import google.protobuf.message
|
|
4
5
|
import modal._object
|
|
5
6
|
import modal._tunnel
|
|
@@ -27,6 +28,13 @@ import pathlib
|
|
|
27
28
|
import typing
|
|
28
29
|
import typing_extensions
|
|
29
30
|
|
|
31
|
+
class SandboxVersion(enum.Enum):
|
|
32
|
+
V1 = 1
|
|
33
|
+
V2 = 2
|
|
34
|
+
|
|
35
|
+
def _is_v1_sandbox_id(sandbox_id: str) -> bool: ...
|
|
36
|
+
def _is_v2_sandbox_id(sandbox_id: str) -> bool: ...
|
|
37
|
+
def _get_sandbox_version(sandbox_id: str) -> SandboxVersion: ...
|
|
30
38
|
def _result_returncode(result: typing.Optional[modal_proto.api_pb2.GenericResult]) -> typing.Optional[int]: ...
|
|
31
39
|
def _validate_exec_args(args: collections.abc.Sequence[str]) -> None: ...
|
|
32
40
|
|
|
@@ -327,8 +335,17 @@ class _Sandbox(modal._object._Object):
|
|
|
327
335
|
) -> _Sandbox:
|
|
328
336
|
"""Create a sandbox using the V2 backend.
|
|
329
337
|
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
Supported features include exec, encrypted tunnels, wait/poll/terminate,
|
|
339
|
+
CPU and memory configuration, region placement,
|
|
340
|
+
and filesystem snapshots.
|
|
341
|
+
|
|
342
|
+
Features like tags, memory snapshots, volumes, network file systems, GPUs,
|
|
343
|
+
custom domains, OIDC identity tokens, and proxies are not supported.
|
|
344
|
+
|
|
345
|
+
V2 sandboxes created with this method are not currently returned by
|
|
346
|
+
`Sandbox.list()` and cannot be looked up with `Sandbox.from_name()`.
|
|
347
|
+
Store `sandbox.object_id` if you need to retrieve the sandbox later, and
|
|
348
|
+
use `Sandbox.from_id(sandbox.object_id)` to reattach.
|
|
332
349
|
"""
|
|
333
350
|
...
|
|
334
351
|
|
|
@@ -413,7 +430,7 @@ class _Sandbox(modal._object._Object):
|
|
|
413
430
|
# You can later mount this snapshot to another Sandbox:
|
|
414
431
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
415
432
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
416
|
-
sandbox_session_2.
|
|
433
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
417
434
|
```
|
|
418
435
|
"""
|
|
419
436
|
...
|
|
@@ -439,7 +456,7 @@ class _Sandbox(modal._object._Object):
|
|
|
439
456
|
# You can later mount this snapshot to another Sandbox:
|
|
440
457
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
441
458
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
442
|
-
sandbox_session_2.
|
|
459
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
443
460
|
```
|
|
444
461
|
"""
|
|
445
462
|
...
|
|
@@ -1209,8 +1226,17 @@ class Sandbox(modal.object.Object):
|
|
|
1209
1226
|
) -> Sandbox:
|
|
1210
1227
|
"""Create a sandbox using the V2 backend.
|
|
1211
1228
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1229
|
+
Supported features include exec, encrypted tunnels, wait/poll/terminate,
|
|
1230
|
+
CPU and memory configuration, region placement,
|
|
1231
|
+
and filesystem snapshots.
|
|
1232
|
+
|
|
1233
|
+
Features like tags, memory snapshots, volumes, network file systems, GPUs,
|
|
1234
|
+
custom domains, OIDC identity tokens, and proxies are not supported.
|
|
1235
|
+
|
|
1236
|
+
V2 sandboxes created with this method are not currently returned by
|
|
1237
|
+
`Sandbox.list()` and cannot be looked up with `Sandbox.from_name()`.
|
|
1238
|
+
Store `sandbox.object_id` if you need to retrieve the sandbox later, and
|
|
1239
|
+
use `Sandbox.from_id(sandbox.object_id)` to reattach.
|
|
1214
1240
|
"""
|
|
1215
1241
|
...
|
|
1216
1242
|
|
|
@@ -1243,8 +1269,17 @@ class Sandbox(modal.object.Object):
|
|
|
1243
1269
|
) -> Sandbox:
|
|
1244
1270
|
"""Create a sandbox using the V2 backend.
|
|
1245
1271
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1272
|
+
Supported features include exec, encrypted tunnels, wait/poll/terminate,
|
|
1273
|
+
CPU and memory configuration, region placement,
|
|
1274
|
+
and filesystem snapshots.
|
|
1275
|
+
|
|
1276
|
+
Features like tags, memory snapshots, volumes, network file systems, GPUs,
|
|
1277
|
+
custom domains, OIDC identity tokens, and proxies are not supported.
|
|
1278
|
+
|
|
1279
|
+
V2 sandboxes created with this method are not currently returned by
|
|
1280
|
+
`Sandbox.list()` and cannot be looked up with `Sandbox.from_name()`.
|
|
1281
|
+
Store `sandbox.object_id` if you need to retrieve the sandbox later, and
|
|
1282
|
+
use `Sandbox.from_id(sandbox.object_id)` to reattach.
|
|
1248
1283
|
"""
|
|
1249
1284
|
...
|
|
1250
1285
|
|
|
@@ -1408,7 +1443,7 @@ class Sandbox(modal.object.Object):
|
|
|
1408
1443
|
# You can later mount this snapshot to another Sandbox:
|
|
1409
1444
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
1410
1445
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
1411
|
-
sandbox_session_2.
|
|
1446
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1412
1447
|
```
|
|
1413
1448
|
"""
|
|
1414
1449
|
...
|
|
@@ -1433,7 +1468,7 @@ class Sandbox(modal.object.Object):
|
|
|
1433
1468
|
# You can later mount this snapshot to another Sandbox:
|
|
1434
1469
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
1435
1470
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
1436
|
-
sandbox_session_2.
|
|
1471
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1437
1472
|
```
|
|
1438
1473
|
"""
|
|
1439
1474
|
...
|
|
@@ -1474,7 +1509,7 @@ class Sandbox(modal.object.Object):
|
|
|
1474
1509
|
# You can later mount this snapshot to another Sandbox:
|
|
1475
1510
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
1476
1511
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
1477
|
-
sandbox_session_2.
|
|
1512
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1478
1513
|
```
|
|
1479
1514
|
"""
|
|
1480
1515
|
...
|
|
@@ -1491,7 +1526,7 @@ class Sandbox(modal.object.Object):
|
|
|
1491
1526
|
# You can later mount this snapshot to another Sandbox:
|
|
1492
1527
|
sandbox_session_2 = modal.Sandbox.create(...)
|
|
1493
1528
|
sandbox_session_2.mount_image("/user_project", user_project_snapshot)
|
|
1494
|
-
sandbox_session_2.
|
|
1529
|
+
sandbox_session_2.filesystem.list_files("/user_project")
|
|
1495
1530
|
```
|
|
1496
1531
|
"""
|
|
1497
1532
|
...
|
|
@@ -14,7 +14,7 @@ class Server:
|
|
|
14
14
|
Instead, use the [`@app._experimental_server()`](https://modal.com/docs/reference/modal.App#server) decorator.
|
|
15
15
|
|
|
16
16
|
```python notest
|
|
17
|
-
@app._experimental_server(port=8000,
|
|
17
|
+
@app._experimental_server(port=8000, routing_regions=["us-east", "us-west"])
|
|
18
18
|
class MyServer:
|
|
19
19
|
@modal.enter()
|
|
20
20
|
def start_server(self):
|
|
@@ -263,6 +263,10 @@ class ModalClientBase(abc.ABC):
|
|
|
263
263
|
async def EndpointCreate(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.EndpointCreateRequest, modal_proto.api_pb2.EndpointCreateResponse]') -> None:
|
|
264
264
|
pass
|
|
265
265
|
|
|
266
|
+
@abc.abstractmethod
|
|
267
|
+
async def EndpointList(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.EndpointListRequest, modal_proto.api_pb2.EndpointListResponse]') -> None:
|
|
268
|
+
pass
|
|
269
|
+
|
|
266
270
|
@abc.abstractmethod
|
|
267
271
|
async def EnvironmentCreate(self, stream: 'grpclib.server.Stream[modal_proto.api_pb2.EnvironmentCreateRequest, google.protobuf.empty_pb2.Empty]') -> None:
|
|
268
272
|
pass
|
|
@@ -1183,6 +1187,12 @@ class ModalClientBase(abc.ABC):
|
|
|
1183
1187
|
modal_proto.api_pb2.EndpointCreateRequest,
|
|
1184
1188
|
modal_proto.api_pb2.EndpointCreateResponse,
|
|
1185
1189
|
),
|
|
1190
|
+
'/modal.client.ModalClient/EndpointList': grpclib.const.Handler(
|
|
1191
|
+
self.EndpointList,
|
|
1192
|
+
grpclib.const.Cardinality.UNARY_UNARY,
|
|
1193
|
+
modal_proto.api_pb2.EndpointListRequest,
|
|
1194
|
+
modal_proto.api_pb2.EndpointListResponse,
|
|
1195
|
+
),
|
|
1186
1196
|
'/modal.client.ModalClient/EnvironmentCreate': grpclib.const.Handler(
|
|
1187
1197
|
self.EnvironmentCreate,
|
|
1188
1198
|
grpclib.const.Cardinality.UNARY_UNARY,
|
|
@@ -2383,6 +2393,12 @@ class ModalClientStub:
|
|
|
2383
2393
|
modal_proto.api_pb2.EndpointCreateRequest,
|
|
2384
2394
|
modal_proto.api_pb2.EndpointCreateResponse,
|
|
2385
2395
|
)
|
|
2396
|
+
self.EndpointList = grpclib.client.UnaryUnaryMethod(
|
|
2397
|
+
channel,
|
|
2398
|
+
'/modal.client.ModalClient/EndpointList',
|
|
2399
|
+
modal_proto.api_pb2.EndpointListRequest,
|
|
2400
|
+
modal_proto.api_pb2.EndpointListResponse,
|
|
2401
|
+
)
|
|
2386
2402
|
self.EnvironmentCreate = grpclib.client.UnaryUnaryMethod(
|
|
2387
2403
|
channel,
|
|
2388
2404
|
'/modal.client.ModalClient/EnvironmentCreate',
|