modal 1.4.3.dev29__tar.gz → 1.4.4.dev0__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.3.dev29 → modal-1.4.4.dev0}/PKG-INFO +1 -1
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_function_variants.py +2 -10
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_functions.py +5 -8
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_grpc_client.py +0 -7
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/mount_utils.py +1 -1
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/task_command_router_client.py +68 -44
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/app.py +4 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/app.pyi +4 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/shell.py +37 -2
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/client.pyi +2 -2
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/functions.pyi +7 -6
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/image.py +2 -10
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/sandbox.py +2 -10
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/volume.py +110 -12
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/volume.pyi +100 -18
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/api_pb2.py +728 -728
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/api_pb2.pyi +10 -2
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_version/__init__.py +1 -1
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/LICENSE +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/README.md +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/__main__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_billing.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_environments.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_ipython.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_load_context.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_location.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_logs.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_object.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_output/manager.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_output/pty.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_output/rich.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_output/status.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_partial_function.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_resolver.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_resources.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_serialization.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_server.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_traceback.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_tunnel.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_type_manager.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_vendor/version.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/_watcher.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/billing.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/README.md +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/call_graph.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/_download.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/_help.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/app.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/billing.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/bootstrap.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/changelog.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/config.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/container.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/dict.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/environment.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/launch.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/logo.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/profile.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/queues.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/run.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/secret.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/selector.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/token.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/utils.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cli/volume.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/client.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cls.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/cls.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/config.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/container_process.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/container_process.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/dict.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/dict.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/environments.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/environments.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/exception.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/file_io.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/file_io.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/functions.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/image.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/io_streams.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/mount.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/mount.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/network_file_system.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/object.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/object.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/output.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/parallel_map.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/partial_function.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/proxy.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/proxy.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/py.typed +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/queue.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/queue.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/retries.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/runner.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/runner.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/running_app.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/sandbox.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/sandbox_fs.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/sandbox_fs.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/schedule.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/secret.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/secret.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/server.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/server.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/serving.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/serving.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/snapshot.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/stream_type.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/token_flow.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/py.typed +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/modal_version/__main__.py +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/pyproject.toml +0 -0
- {modal-1.4.3.dev29 → modal-1.4.4.dev0}/setup.cfg +0 -0
|
@@ -18,7 +18,7 @@ from ._utils.mount_utils import validate_volumes, validate_volumes_by_object_id
|
|
|
18
18
|
from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
19
19
|
from .retries import Retries
|
|
20
20
|
from .secret import _Secret
|
|
21
|
-
from .volume import _Volume
|
|
21
|
+
from .volume import _Volume, _volume_to_mount_proto
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from ._functions import _Function
|
|
@@ -162,15 +162,7 @@ class _FunctionOptions:
|
|
|
162
162
|
# Needs to be called late so that volumes are hydrated
|
|
163
163
|
validate_volumes_by_object_id(self.validated_volumes)
|
|
164
164
|
|
|
165
|
-
volume_mounts = [
|
|
166
|
-
api_pb2.VolumeMount(
|
|
167
|
-
mount_path=path,
|
|
168
|
-
volume_id=volume.object_id,
|
|
169
|
-
allow_background_commits=True,
|
|
170
|
-
read_only=volume._read_only,
|
|
171
|
-
)
|
|
172
|
-
for path, volume in self.validated_volumes
|
|
173
|
-
]
|
|
165
|
+
volume_mounts = [_volume_to_mount_proto(path, volume) for path, volume in self.validated_volumes]
|
|
174
166
|
return api_pb2.FunctionOptions(
|
|
175
167
|
secret_ids=[secret.object_id for secret in self.secrets],
|
|
176
168
|
replace_secret_ids=bool(self.secrets),
|
|
@@ -89,7 +89,7 @@ from .proxy import _Proxy
|
|
|
89
89
|
from .retries import Retries, RetryManager
|
|
90
90
|
from .schedule import Schedule
|
|
91
91
|
from .secret import _Secret
|
|
92
|
-
from .volume import _Volume
|
|
92
|
+
from .volume import _Volume, _volume_to_mount_proto
|
|
93
93
|
|
|
94
94
|
if TYPE_CHECKING:
|
|
95
95
|
import modal.app
|
|
@@ -667,6 +667,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
667
667
|
batch_wait_ms: Optional[int] = None,
|
|
668
668
|
cloud: Optional[str] = None,
|
|
669
669
|
region: Optional[Union[str, Sequence[str]]] = None,
|
|
670
|
+
routing_region: Optional[str] = None,
|
|
670
671
|
nonpreemptible: bool = False,
|
|
671
672
|
is_builder_function: bool = False,
|
|
672
673
|
is_auto_snapshot: bool = False,
|
|
@@ -946,13 +947,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
946
947
|
|
|
947
948
|
# Relies on dicts being ordered (true as of Python 3.6).
|
|
948
949
|
volume_mounts = [
|
|
949
|
-
|
|
950
|
-
mount_path=path,
|
|
951
|
-
volume_id=volume.object_id,
|
|
952
|
-
allow_background_commits=True,
|
|
953
|
-
read_only=volume._read_only,
|
|
954
|
-
)
|
|
955
|
-
for path, volume in validated_volumes_no_cloud_buckets
|
|
950
|
+
_volume_to_mount_proto(path, volume) for path, volume in validated_volumes_no_cloud_buckets
|
|
956
951
|
]
|
|
957
952
|
loaded_mount_ids = {m.object_id for m in all_mounts} | {m.object_id for m in image._mount_layers}
|
|
958
953
|
|
|
@@ -1033,6 +1028,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1033
1028
|
supported_output_formats=supported_output_formats,
|
|
1034
1029
|
http_config=http_config,
|
|
1035
1030
|
is_server=is_server,
|
|
1031
|
+
routing_region=routing_region or "",
|
|
1036
1032
|
)
|
|
1037
1033
|
|
|
1038
1034
|
if isinstance(gpu, list):
|
|
@@ -1072,6 +1068,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1072
1068
|
supported_output_formats=supported_output_formats,
|
|
1073
1069
|
http_config=http_config,
|
|
1074
1070
|
is_server=function_definition.is_server,
|
|
1071
|
+
routing_region=function_definition.routing_region,
|
|
1075
1072
|
)
|
|
1076
1073
|
|
|
1077
1074
|
ranked_functions = []
|
|
@@ -44,10 +44,6 @@ _STATUS_TO_EXCEPTION: dict[Status, type[exception._GRPCErrorWrapper]] = {
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class grpc_error_converter:
|
|
47
|
-
def __init__(self, *, expect_timeouts: bool = False):
|
|
48
|
-
"""expect_timeouts: if this is set we convert cancelled/deadline_exceeded statuses to exception.TimeoutError"""
|
|
49
|
-
self._expect_timeouts = expect_timeouts
|
|
50
|
-
|
|
51
47
|
def __enter__(self):
|
|
52
48
|
pass
|
|
53
49
|
|
|
@@ -56,9 +52,6 @@ class grpc_error_converter:
|
|
|
56
52
|
use_full_traceback = config.get("traceback")
|
|
57
53
|
with suppress_tb_frame():
|
|
58
54
|
if isinstance(exc, GRPCError):
|
|
59
|
-
if self._expect_timeouts and exc.status in (Status.CANCELLED, Status.DEADLINE_EXCEEDED):
|
|
60
|
-
raise exception.TimeoutError("Timeout expired")
|
|
61
|
-
|
|
62
55
|
modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
|
|
63
56
|
modal_exc._grpc_message = exc.message
|
|
64
57
|
modal_exc._grpc_status = exc.status
|
|
@@ -68,7 +68,7 @@ def validate_volumes(
|
|
|
68
68
|
for path, volume in validated_volumes:
|
|
69
69
|
if not isinstance(volume, (_Volume, _CloudBucketMount)):
|
|
70
70
|
raise InvalidError(f"Object of type {type(volume)} mounted at '{path}' is not usable as a volume.")
|
|
71
|
-
elif isinstance(volume,
|
|
71
|
+
elif isinstance(volume, _Volume):
|
|
72
72
|
volume_to_paths.setdefault(volume, []).append(path)
|
|
73
73
|
for paths in volume_to_paths.values():
|
|
74
74
|
if len(paths) > 1:
|
|
@@ -74,7 +74,7 @@ async def call_with_retries_on_transient_errors(
|
|
|
74
74
|
delay_factor: float = 2,
|
|
75
75
|
max_retries: Optional[int] = 10,
|
|
76
76
|
exclude_status_codes: Optional[list[Status]] = None,
|
|
77
|
-
|
|
77
|
+
timeout_deadline: Optional[float] = None,
|
|
78
78
|
):
|
|
79
79
|
"""Call func() with transient error retries and exponential backoff.
|
|
80
80
|
|
|
@@ -84,6 +84,13 @@ async def call_with_retries_on_transient_errors(
|
|
|
84
84
|
exclude_status_codes: gRPC status codes to exclude from retry logic even if
|
|
85
85
|
they are in RETRYABLE_GRPC_STATUS_CODES. Use this to let certain errors
|
|
86
86
|
(e.g. DEADLINE_EXCEEDED) propagate immediately rather than being retried.
|
|
87
|
+
timeout_deadline: Optional monotonic deadline (`time.monotonic()` value).
|
|
88
|
+
When set, retries are not attempted once the deadline is reached and
|
|
89
|
+
the backoff sleep is clamped so we don't sleep past it. It's up to
|
|
90
|
+
the caller to decide whether to further translate the surfaced
|
|
91
|
+
exception (e.g., into a TimeoutError) based on the deadline check.
|
|
92
|
+
The caller is also responsible for propagating the remaining budget
|
|
93
|
+
into `func()` (typically as the per-call gRPC timeout).
|
|
87
94
|
"""
|
|
88
95
|
delay_secs = base_delay_secs
|
|
89
96
|
num_retries = 0
|
|
@@ -92,10 +99,22 @@ async def call_with_retries_on_transient_errors(
|
|
|
92
99
|
def is_retryable_status(status: Status) -> bool:
|
|
93
100
|
return status in RETRYABLE_GRPC_STATUS_CODES and status not in exclude_status_codes
|
|
94
101
|
|
|
95
|
-
|
|
102
|
+
def can_retry() -> bool:
|
|
103
|
+
if max_retries is not None and num_retries >= max_retries:
|
|
104
|
+
return False
|
|
105
|
+
if timeout_deadline is not None and time.monotonic() >= timeout_deadline:
|
|
106
|
+
return False
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
async def sleep_and_advance(e: Exception):
|
|
96
110
|
nonlocal delay_secs, num_retries
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
# Clamp the backoff sleep to the remaining deadline so we don't sleep
|
|
112
|
+
# past it just to fail on the next iteration's deadline check.
|
|
113
|
+
sleep_for = delay_secs
|
|
114
|
+
if timeout_deadline is not None:
|
|
115
|
+
sleep_for = min(sleep_for, max(0.0, timeout_deadline - time.monotonic()))
|
|
116
|
+
logger.debug(f"Retrying RPC with delay {sleep_for}s due to error: {e}")
|
|
117
|
+
await asyncio.sleep(sleep_for)
|
|
99
118
|
delay_secs *= delay_factor
|
|
100
119
|
num_retries += 1
|
|
101
120
|
|
|
@@ -103,37 +122,28 @@ async def call_with_retries_on_transient_errors(
|
|
|
103
122
|
try:
|
|
104
123
|
return await func()
|
|
105
124
|
except GRPCError as e:
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
raise e
|
|
125
|
+
if not is_retryable_status(e.status) or not can_retry():
|
|
126
|
+
raise
|
|
127
|
+
await sleep_and_advance(e)
|
|
110
128
|
except AttributeError as e:
|
|
111
129
|
# StreamTerminatedError are not properly raised in grpclib<=0.4.7
|
|
112
130
|
# fixed in https://github.com/vmagamedov/grpclib/issues/185
|
|
113
131
|
# TODO: update to newer version (>=0.4.8) once stable
|
|
114
|
-
if
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
raise e
|
|
132
|
+
if "_write_appdata" not in str(e) or not can_retry():
|
|
133
|
+
raise
|
|
134
|
+
await sleep_and_advance(e)
|
|
118
135
|
except StreamTerminatedError as e:
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if max_retries is None or num_retries < max_retries:
|
|
129
|
-
await sleep_and_update_delay_and_num_retries_remaining(e)
|
|
130
|
-
else:
|
|
131
|
-
raise ConnectionError(str(e))
|
|
132
|
-
except OSError as e:
|
|
133
|
-
if max_retries is None or num_retries < max_retries:
|
|
134
|
-
await sleep_and_update_delay_and_num_retries_remaining(e)
|
|
135
|
-
else:
|
|
136
|
+
if not can_retry():
|
|
137
|
+
raise
|
|
138
|
+
await sleep_and_advance(e)
|
|
139
|
+
except (asyncio.TimeoutError, OSError) as e:
|
|
140
|
+
if not can_retry():
|
|
141
|
+
# Client-side timeout / network OSError surfaces as a generic
|
|
142
|
+
# ConnectionError once we stop retrying. Callers that pass
|
|
143
|
+
# `timeout_deadline` can further translate this based on
|
|
144
|
+
# whether the deadline has elapsed.
|
|
136
145
|
raise ConnectionError(str(e))
|
|
146
|
+
await sleep_and_advance(e)
|
|
137
147
|
|
|
138
148
|
|
|
139
149
|
async def fetch_command_router_access(server_client, task_id: str) -> api_pb2.TaskGetCommandRouterAccessResponse:
|
|
@@ -656,20 +666,34 @@ class TaskCommandRouterClient:
|
|
|
656
666
|
)
|
|
657
667
|
|
|
658
668
|
async def snapshot_filesystem(
|
|
659
|
-
self, request: sr_pb2.TaskSnapshotFilesystemRequest, *, timeout:
|
|
669
|
+
self, request: sr_pb2.TaskSnapshotFilesystemRequest, *, timeout: float, **kwargs
|
|
660
670
|
) -> sr_pb2.TaskSnapshotFilesystemResponse:
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
)
|
|
673
|
-
|
|
674
|
-
|
|
671
|
+
# Compute the overall deadline once; each retry attempt passes the
|
|
672
|
+
# remaining budget as the per-call gRPC timeout so we honor the
|
|
673
|
+
# caller-specified `timeout` across retries instead of giving each
|
|
674
|
+
# attempt a fresh full window.
|
|
675
|
+
timeout_deadline = time.monotonic() + timeout
|
|
676
|
+
|
|
677
|
+
def call():
|
|
678
|
+
call_timeout = timeout_deadline - time.monotonic()
|
|
679
|
+
if call_timeout <= 0.0:
|
|
680
|
+
# doesn't matter which exception type this is
|
|
681
|
+
# as it will be caught by the catch all below
|
|
682
|
+
raise ModalTimeoutError("Timeout expired")
|
|
683
|
+
|
|
684
|
+
return self._call_with_auth_retry(
|
|
685
|
+
self._stub.TaskSnapshotFilesystem, request, timeout=call_timeout, **kwargs
|
|
675
686
|
)
|
|
687
|
+
|
|
688
|
+
# Any failure observed at or after the deadline is treated as a timeout
|
|
689
|
+
try:
|
|
690
|
+
with grpc_error_converter():
|
|
691
|
+
return await call_with_retries_on_transient_errors(
|
|
692
|
+
call,
|
|
693
|
+
exclude_status_codes=[Status.DEADLINE_EXCEEDED, Status.CANCELLED],
|
|
694
|
+
timeout_deadline=timeout_deadline,
|
|
695
|
+
)
|
|
696
|
+
except Exception:
|
|
697
|
+
if time.monotonic() >= timeout_deadline:
|
|
698
|
+
raise ModalTimeoutError("Timeout expired")
|
|
699
|
+
raise
|
|
@@ -743,6 +743,7 @@ class _App:
|
|
|
743
743
|
] = None, # Set this to True if it's a non-generator function returning a [sync/async] generator object
|
|
744
744
|
cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
|
|
745
745
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
|
|
746
|
+
routing_region: Optional[str] = None, # Region to route inputs to the function through.
|
|
746
747
|
nonpreemptible: bool = False, # Whether to run the function on a nonpreemptible instance.
|
|
747
748
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
748
749
|
block_network: bool = False, # Whether to block network access
|
|
@@ -902,6 +903,7 @@ class _App:
|
|
|
902
903
|
startup_timeout=startup_timeout or timeout,
|
|
903
904
|
cloud=cloud,
|
|
904
905
|
region=region,
|
|
906
|
+
routing_region=routing_region,
|
|
905
907
|
nonpreemptible=nonpreemptible,
|
|
906
908
|
webhook_config=webhook_config,
|
|
907
909
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
@@ -957,6 +959,7 @@ class _App:
|
|
|
957
959
|
startup_timeout: Optional[int] = None, # Maximum startup time in seconds with higher precedence than `timeout`.
|
|
958
960
|
cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
|
|
959
961
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
|
|
962
|
+
routing_region: Optional[str] = None, # Region to route inputs to the function through.
|
|
960
963
|
nonpreemptible: bool = False, # Whether to run the function on a non-preemptible instance.
|
|
961
964
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
962
965
|
block_network: bool = False, # Whether to block network access
|
|
@@ -1100,6 +1103,7 @@ class _App:
|
|
|
1100
1103
|
startup_timeout=startup_timeout or timeout,
|
|
1101
1104
|
cloud=cloud,
|
|
1102
1105
|
region=region,
|
|
1106
|
+
routing_region=routing_region,
|
|
1103
1107
|
nonpreemptible=nonpreemptible,
|
|
1104
1108
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
1105
1109
|
block_network=block_network,
|
|
@@ -490,6 +490,7 @@ class _App:
|
|
|
490
490
|
is_generator: typing.Optional[bool] = None,
|
|
491
491
|
cloud: typing.Optional[str] = None,
|
|
492
492
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
493
|
+
routing_region: typing.Optional[str] = None,
|
|
493
494
|
nonpreemptible: bool = False,
|
|
494
495
|
enable_memory_snapshot: bool = False,
|
|
495
496
|
block_network: bool = False,
|
|
@@ -537,6 +538,7 @@ class _App:
|
|
|
537
538
|
startup_timeout: typing.Optional[int] = None,
|
|
538
539
|
cloud: typing.Optional[str] = None,
|
|
539
540
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
541
|
+
routing_region: typing.Optional[str] = None,
|
|
540
542
|
nonpreemptible: bool = False,
|
|
541
543
|
enable_memory_snapshot: bool = False,
|
|
542
544
|
block_network: bool = False,
|
|
@@ -1208,6 +1210,7 @@ class App:
|
|
|
1208
1210
|
is_generator: typing.Optional[bool] = None,
|
|
1209
1211
|
cloud: typing.Optional[str] = None,
|
|
1210
1212
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1213
|
+
routing_region: typing.Optional[str] = None,
|
|
1211
1214
|
nonpreemptible: bool = False,
|
|
1212
1215
|
enable_memory_snapshot: bool = False,
|
|
1213
1216
|
block_network: bool = False,
|
|
@@ -1255,6 +1258,7 @@ class App:
|
|
|
1255
1258
|
startup_timeout: typing.Optional[int] = None,
|
|
1256
1259
|
cloud: typing.Optional[str] = None,
|
|
1257
1260
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1261
|
+
routing_region: typing.Optional[str] = None,
|
|
1258
1262
|
nonpreemptible: bool = False,
|
|
1259
1263
|
enable_memory_snapshot: bool = False,
|
|
1260
1264
|
block_network: bool = False,
|
|
@@ -58,6 +58,23 @@ def _passed_forbidden_args(
|
|
|
58
58
|
return passed_forbidden
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def _parse_experimental_options(options: Iterable[str]) -> dict[str, bool]:
|
|
62
|
+
parsed: dict[str, bool] = {}
|
|
63
|
+
for option in options:
|
|
64
|
+
key, sep, raw_value = option.partition("=")
|
|
65
|
+
if not key or not sep:
|
|
66
|
+
raise click.BadParameter("must use KEY=VALUE")
|
|
67
|
+
|
|
68
|
+
value = raw_value.lower()
|
|
69
|
+
if value in {"1", "true"}:
|
|
70
|
+
parsed[key] = True
|
|
71
|
+
elif value in {"0", "false"}:
|
|
72
|
+
parsed[key] = False
|
|
73
|
+
else:
|
|
74
|
+
raise click.BadParameter("BOOL must be one of true, false, 1, or 0")
|
|
75
|
+
return parsed
|
|
76
|
+
|
|
77
|
+
|
|
61
78
|
def _is_valid_modal_id(ref: str, prefix: str) -> bool:
|
|
62
79
|
assert prefix.endswith("-")
|
|
63
80
|
return ref.startswith(prefix) and len(ref[len(prefix) :]) > 0 and ref[len(prefix) :].isalnum()
|
|
@@ -180,6 +197,7 @@ def _start_shell_from_function_spec(
|
|
|
180
197
|
timeout: int,
|
|
181
198
|
function_spec: _FunctionSpec,
|
|
182
199
|
pty: bool,
|
|
200
|
+
experimental_options: dict[str, bool],
|
|
183
201
|
) -> None:
|
|
184
202
|
interactive_shell(
|
|
185
203
|
app,
|
|
@@ -198,6 +216,7 @@ def _start_shell_from_function_spec(
|
|
|
198
216
|
region=function_spec.scheduler_placement.regions if function_spec.scheduler_placement else None,
|
|
199
217
|
pty=pty,
|
|
200
218
|
proxy=function_spec.proxy,
|
|
219
|
+
experimental_options=experimental_options,
|
|
201
220
|
)
|
|
202
221
|
|
|
203
222
|
|
|
@@ -216,6 +235,7 @@ def _start_shell_from_image(
|
|
|
216
235
|
cloud: Optional[str],
|
|
217
236
|
region: Optional[str],
|
|
218
237
|
pty: bool,
|
|
238
|
+
experimental_options: dict[str, bool],
|
|
219
239
|
) -> None:
|
|
220
240
|
volumes = {f"/mnt/{vol}": Volume.from_name(vol) for vol in volume}
|
|
221
241
|
secrets = [Secret.from_name(s) for s in secret]
|
|
@@ -246,6 +266,7 @@ def _start_shell_from_image(
|
|
|
246
266
|
secrets=secrets,
|
|
247
267
|
region=region.split(",") if region else [],
|
|
248
268
|
pty=pty,
|
|
269
|
+
experimental_options=experimental_options,
|
|
249
270
|
)
|
|
250
271
|
|
|
251
272
|
|
|
@@ -298,6 +319,14 @@ def _start_shell_from_image(
|
|
|
298
319
|
default=False,
|
|
299
320
|
help="Interpret argument as a Python module path instead of a file/script path",
|
|
300
321
|
)
|
|
322
|
+
@click.option(
|
|
323
|
+
"--experimental-option",
|
|
324
|
+
"experimental_options",
|
|
325
|
+
multiple=True,
|
|
326
|
+
default=(),
|
|
327
|
+
hidden=True,
|
|
328
|
+
metavar="KEY=VALUE",
|
|
329
|
+
)
|
|
301
330
|
def shell(
|
|
302
331
|
ref: Optional[str] = None,
|
|
303
332
|
cmd: str = "/bin/bash",
|
|
@@ -314,6 +343,7 @@ def shell(
|
|
|
314
343
|
region: Optional[str] = None,
|
|
315
344
|
pty: Optional[bool] = None,
|
|
316
345
|
use_module_mode: bool = False,
|
|
346
|
+
experimental_options: tuple[str, ...] = (),
|
|
317
347
|
):
|
|
318
348
|
"""Run a command or interactive shell inside a Modal container.
|
|
319
349
|
|
|
@@ -384,12 +414,16 @@ def shell(
|
|
|
384
414
|
# NB: invoking under bash makes --cmd a lot more flexible.
|
|
385
415
|
cmds = shlex.split(f'/bin/bash -c "{cmd}"')
|
|
386
416
|
timeout = 3600
|
|
417
|
+
parsed_experimental_options = _parse_experimental_options(experimental_options)
|
|
387
418
|
|
|
388
419
|
if ref is not None and not _is_valid_modal_id(ref, "im-"):
|
|
389
420
|
# If ref it not a Modal Image ID, then it's a function reference, and we'll start a new container from its spec.
|
|
390
421
|
ctx = click.get_current_context()
|
|
391
422
|
if passed_forbidden := _passed_forbidden_args(
|
|
392
|
-
shell.params,
|
|
423
|
+
shell.params,
|
|
424
|
+
ctx,
|
|
425
|
+
locals(),
|
|
426
|
+
allowed=lambda p: p in {"cmd", "env", "pty", "ref", "use_module_mode", "experimental_options"},
|
|
393
427
|
):
|
|
394
428
|
raise ClickException(
|
|
395
429
|
f"Cannot specify container configuration arguments ({', '.join(passed_forbidden)}) "
|
|
@@ -397,7 +431,7 @@ def shell(
|
|
|
397
431
|
)
|
|
398
432
|
|
|
399
433
|
function_spec = _function_spec_from_ref(ref, use_module_mode)
|
|
400
|
-
_start_shell_from_function_spec(app, cmds, env, timeout, function_spec, pty)
|
|
434
|
+
_start_shell_from_function_spec(app, cmds, env, timeout, function_spec, pty, parsed_experimental_options)
|
|
401
435
|
return
|
|
402
436
|
|
|
403
437
|
if ref is not None and _is_valid_modal_id(ref, "im-"):
|
|
@@ -428,4 +462,5 @@ def shell(
|
|
|
428
462
|
cloud,
|
|
429
463
|
region,
|
|
430
464
|
pty,
|
|
465
|
+
parsed_experimental_options,
|
|
431
466
|
)
|
|
@@ -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.
|
|
38
|
+
version: str = "1.4.4.dev0",
|
|
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.
|
|
178
|
+
version: str = "1.4.4.dev0",
|
|
179
179
|
):
|
|
180
180
|
"""mdmd:hidden
|
|
181
181
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -97,6 +97,7 @@ class Function(
|
|
|
97
97
|
batch_wait_ms: typing.Optional[int] = None,
|
|
98
98
|
cloud: typing.Optional[str] = None,
|
|
99
99
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
100
|
+
routing_region: typing.Optional[str] = None,
|
|
100
101
|
nonpreemptible: bool = False,
|
|
101
102
|
is_builder_function: bool = False,
|
|
102
103
|
is_auto_snapshot: bool = False,
|
|
@@ -420,7 +421,7 @@ class Function(
|
|
|
420
421
|
|
|
421
422
|
_call_generator: ___call_generator_spec
|
|
422
423
|
|
|
423
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
424
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
424
425
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
425
426
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
426
427
|
...
|
|
@@ -429,7 +430,7 @@ class Function(
|
|
|
429
430
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
430
431
|
...
|
|
431
432
|
|
|
432
|
-
remote: __remote_spec[modal._functions.
|
|
433
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
433
434
|
|
|
434
435
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
435
436
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -456,7 +457,7 @@ class Function(
|
|
|
456
457
|
"""
|
|
457
458
|
...
|
|
458
459
|
|
|
459
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
460
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
460
461
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
461
462
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
462
463
|
|
|
@@ -479,7 +480,7 @@ class Function(
|
|
|
479
480
|
"""
|
|
480
481
|
...
|
|
481
482
|
|
|
482
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
483
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
483
484
|
|
|
484
485
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
485
486
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -487,7 +488,7 @@ class Function(
|
|
|
487
488
|
|
|
488
489
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
489
490
|
|
|
490
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
491
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
491
492
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
492
493
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
493
494
|
|
|
@@ -508,7 +509,7 @@ class Function(
|
|
|
508
509
|
"""
|
|
509
510
|
...
|
|
510
511
|
|
|
511
|
-
spawn: __spawn_spec[modal._functions.
|
|
512
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
512
513
|
|
|
513
514
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
514
515
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -59,7 +59,7 @@ from .mount import _Mount, python_standalone_mount_name
|
|
|
59
59
|
from .network_file_system import _NetworkFileSystem
|
|
60
60
|
from .output import OutputManager
|
|
61
61
|
from .secret import _Secret
|
|
62
|
-
from .volume import _Volume
|
|
62
|
+
from .volume import _Volume, _volume_to_mount_proto
|
|
63
63
|
|
|
64
64
|
if typing.TYPE_CHECKING:
|
|
65
65
|
import modal._functions
|
|
@@ -631,15 +631,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
631
631
|
validate_volumes_by_object_id(validated_volumes)
|
|
632
632
|
|
|
633
633
|
# Relies on dicts being ordered (true as of Python 3.6).
|
|
634
|
-
volume_mounts = [
|
|
635
|
-
api_pb2.VolumeMount(
|
|
636
|
-
mount_path=path,
|
|
637
|
-
volume_id=volume.object_id,
|
|
638
|
-
allow_background_commits=True,
|
|
639
|
-
read_only=volume._read_only,
|
|
640
|
-
)
|
|
641
|
-
for path, volume in validated_volumes
|
|
642
|
-
]
|
|
634
|
+
volume_mounts = [_volume_to_mount_proto(path, volume) for path, volume in validated_volumes]
|
|
643
635
|
|
|
644
636
|
image_definition = api_pb2.Image(
|
|
645
637
|
base_images=base_images_pb2s,
|
|
@@ -21,7 +21,7 @@ from google.protobuf.message import Message
|
|
|
21
21
|
from modal._tunnel import Tunnel
|
|
22
22
|
from modal.cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
23
23
|
from modal.mount import _Mount
|
|
24
|
-
from modal.volume import _Volume
|
|
24
|
+
from modal.volume import _Volume, _volume_to_mount_proto
|
|
25
25
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
26
26
|
|
|
27
27
|
from ._load_context import LoadContext
|
|
@@ -301,15 +301,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
301
301
|
validate_volumes_by_object_id(validated_volumes)
|
|
302
302
|
|
|
303
303
|
# Relies on dicts being ordered (true as of Python 3.6).
|
|
304
|
-
volume_mounts = [
|
|
305
|
-
api_pb2.VolumeMount(
|
|
306
|
-
mount_path=path,
|
|
307
|
-
volume_id=volume.object_id,
|
|
308
|
-
allow_background_commits=True,
|
|
309
|
-
read_only=volume._read_only,
|
|
310
|
-
)
|
|
311
|
-
for path, volume in validated_volumes
|
|
312
|
-
]
|
|
304
|
+
volume_mounts = [_volume_to_mount_proto(path, volume) for path, volume in validated_volumes]
|
|
313
305
|
|
|
314
306
|
open_ports = [api_pb2.PortSpec(port=port, unencrypted=False) for port in encrypted_ports]
|
|
315
307
|
open_ports.extend([api_pb2.PortSpec(port=port, unencrypted=True) for port in unencrypted_ports])
|