modal 1.4.3.dev27__tar.gz → 1.4.3.dev29__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.dev27 → modal-1.4.3.dev29}/PKG-INFO +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_environments.py +2 -2
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_functions.py +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_grpc_client.py +7 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_object.py +13 -13
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_resolver.py +3 -5
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/user_code_imports.py +2 -2
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/task_command_router_client.py +54 -5
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/queues.py +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/secret.py +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/client.pyi +2 -2
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cls.py +2 -2
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/dict.py +4 -4
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/functions.pyi +6 -6
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/mount.py +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/network_file_system.py +2 -2
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/object.pyi +6 -6
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/proxy.py +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/queue.py +4 -4
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox.py +29 -4
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox.pyi +19 -3
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/secret.py +2 -2
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/volume.py +4 -4
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_grpc.py +32 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2.py +85 -35
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2.pyi +95 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2_grpc.py +70 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/task_command_router_pb2_grpc.pyi +32 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_version/__init__.py +1 -1
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/LICENSE +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/README.md +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/__main__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_billing.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_clustered_functions.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_clustered_functions.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_container_entrypoint.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_function_variants.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_ipython.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_load_context.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_location.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_logs.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/manager.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/pty.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/rich.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_output/status.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_partial_function.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_resources.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/asgi.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/execution_context.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/telemetry.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_serialization.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_server.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_traceback.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_tunnel.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_tunnel.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_type_manager.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/app_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/async_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/blob_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/browser_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/deprecation.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/docker_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/function_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/git_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/hash_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/http_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/logger.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/mount_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/name_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/package_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/shell_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_utils/time_utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/tblib.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_vendor/version.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/_watcher.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/app.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/app.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/billing.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2023.12.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2024.04.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2024.10.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/2025.06.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/README.md +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/builder/base-images.json +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/call_graph.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/_download.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/_help.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/_traceback.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/app.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/billing.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/bootstrap.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/changelog.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/cluster.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/config.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/container.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/dashboard.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/dict.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/entry_point.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/environment.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/import_refs.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/launch.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/logo.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/network_file_system.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/profile.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/programs/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/programs/vscode.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/run.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/selector.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/shell.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/token.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/utils.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cli/volume.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/client.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/cls.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/config.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/container_process.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/container_process.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/dict.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/environments.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/environments.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/exception.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/flash.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/flash.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/experimental/ipython.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/file_io.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/file_io.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/file_pattern_matcher.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/functions.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/image.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/image.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/io_streams.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/io_streams.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/mount.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/network_file_system.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/object.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/output.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/parallel_map.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/parallel_map.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/partial_function.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/partial_function.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/proxy.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/py.typed +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/queue.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/retries.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/runner.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/runner.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/running_app.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox_fs.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/sandbox_fs.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/schedule.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/scheduler_placement.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/secret.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/server.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/server.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/serving.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/serving.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/snapshot.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/snapshot.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/stream_type.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/token_flow.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/token_flow.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal/volume.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/requires.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal.egg-info/top_level.txt +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/__init__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_grpc.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_proto/py.typed +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/modal_version/__main__.py +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/pyproject.toml +0 -0
- {modal-1.4.3.dev27 → modal-1.4.3.dev29}/setup.cfg +0 -0
|
@@ -75,7 +75,7 @@ class _EnvironmentManager:
|
|
|
75
75
|
item.environment_id,
|
|
76
76
|
client,
|
|
77
77
|
metadata,
|
|
78
|
-
|
|
78
|
+
skip_reload=True,
|
|
79
79
|
rep=f"Environment.from_name({item.name!r})",
|
|
80
80
|
)
|
|
81
81
|
environments.append(env)
|
|
@@ -351,7 +351,7 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
351
351
|
return _Environment._from_loader(
|
|
352
352
|
_load,
|
|
353
353
|
repr,
|
|
354
|
-
|
|
354
|
+
skip_reload=True,
|
|
355
355
|
hydrate_lazily=True,
|
|
356
356
|
name=name,
|
|
357
357
|
load_context_overrides=LoadContext(client=client),
|
|
@@ -1242,7 +1242,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1242
1242
|
)
|
|
1243
1243
|
rep = f"modal.Function.from_name('{app_name}', '{name}'{environment_rep})"
|
|
1244
1244
|
return cls._from_loader(
|
|
1245
|
-
_load_remote, rep,
|
|
1245
|
+
_load_remote, rep, skip_reload=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
|
|
1246
1246
|
)
|
|
1247
1247
|
|
|
1248
1248
|
@classmethod
|
|
@@ -44,6 +44,10 @@ _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
|
+
|
|
47
51
|
def __enter__(self):
|
|
48
52
|
pass
|
|
49
53
|
|
|
@@ -52,6 +56,9 @@ class grpc_error_converter:
|
|
|
52
56
|
use_full_traceback = config.get("traceback")
|
|
53
57
|
with suppress_tb_frame():
|
|
54
58
|
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
|
+
|
|
55
62
|
modal_exc = _STATUS_TO_EXCEPTION[exc.status](exc.message)
|
|
56
63
|
modal_exc._grpc_message = exc.message
|
|
57
64
|
modal_exc._grpc_status = exc.status
|
|
@@ -82,7 +82,7 @@ class _Object:
|
|
|
82
82
|
_load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None
|
|
83
83
|
_preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]]
|
|
84
84
|
_rep: str
|
|
85
|
-
|
|
85
|
+
_skip_reload: bool
|
|
86
86
|
_hydrate_lazily: bool
|
|
87
87
|
_deps: Optional[Callable[..., Sequence["_Object"]]]
|
|
88
88
|
_deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
|
|
@@ -113,9 +113,9 @@ class _Object:
|
|
|
113
113
|
self,
|
|
114
114
|
rep: str,
|
|
115
115
|
load: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
|
|
116
|
-
is_another_app: bool = False,
|
|
117
116
|
preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
|
|
118
117
|
hydrate_lazily: bool = False,
|
|
118
|
+
skip_reload: bool = False,
|
|
119
119
|
deps: Optional[Callable[..., Sequence["_Object"]]] = None,
|
|
120
120
|
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
|
121
121
|
name: Optional[str] = None,
|
|
@@ -123,10 +123,10 @@ class _Object:
|
|
|
123
123
|
load_context_overrides: Optional[LoadContext] = None,
|
|
124
124
|
):
|
|
125
125
|
self._local_uuid = str(uuid.uuid4())
|
|
126
|
+
self._rep = rep
|
|
126
127
|
self._load = load
|
|
127
128
|
self._preload = preload
|
|
128
|
-
self.
|
|
129
|
-
self._is_another_app = is_another_app
|
|
129
|
+
self._skip_reload = skip_reload
|
|
130
130
|
self._hydrate_lazily = hydrate_lazily
|
|
131
131
|
self._deps = deps
|
|
132
132
|
self._deduplication_key = deduplication_key
|
|
@@ -218,7 +218,7 @@ class _Object:
|
|
|
218
218
|
cls,
|
|
219
219
|
load: Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]],
|
|
220
220
|
rep: str,
|
|
221
|
-
|
|
221
|
+
skip_reload: bool = False,
|
|
222
222
|
preload: Optional[Callable[[Self, Resolver, LoadContext, Optional[str]], Awaitable[None]]] = None,
|
|
223
223
|
hydrate_lazily: bool = False,
|
|
224
224
|
deps: Optional[Callable[..., Sequence["_Object"]]] = None,
|
|
@@ -232,12 +232,12 @@ class _Object:
|
|
|
232
232
|
obj._init(
|
|
233
233
|
rep,
|
|
234
234
|
load,
|
|
235
|
-
|
|
236
|
-
preload,
|
|
237
|
-
hydrate_lazily,
|
|
238
|
-
deps,
|
|
239
|
-
deduplication_key,
|
|
240
|
-
name,
|
|
235
|
+
skip_reload=skip_reload,
|
|
236
|
+
preload=preload,
|
|
237
|
+
hydrate_lazily=hydrate_lazily,
|
|
238
|
+
deps=deps,
|
|
239
|
+
deduplication_key=deduplication_key,
|
|
240
|
+
name=name,
|
|
241
241
|
load_context_overrides=load_context_overrides,
|
|
242
242
|
)
|
|
243
243
|
return obj
|
|
@@ -268,7 +268,7 @@ class _Object:
|
|
|
268
268
|
object_id: str,
|
|
269
269
|
client: _Client,
|
|
270
270
|
handle_metadata: Optional[Message],
|
|
271
|
-
|
|
271
|
+
skip_reload: bool = False,
|
|
272
272
|
rep: Optional[str] = None,
|
|
273
273
|
) -> Self:
|
|
274
274
|
obj_cls: type[Self]
|
|
@@ -287,7 +287,7 @@ class _Object:
|
|
|
287
287
|
# Instantiate provider
|
|
288
288
|
obj = _Object.__new__(obj_cls)
|
|
289
289
|
rep = rep or f"modal.{obj_cls.__name__.strip('_')}.from_id({object_id!r})"
|
|
290
|
-
obj._init(rep,
|
|
290
|
+
obj._init(rep, skip_reload=skip_reload)
|
|
291
291
|
obj._hydrate(object_id, client, handle_metadata)
|
|
292
292
|
|
|
293
293
|
return obj
|
|
@@ -44,8 +44,7 @@ class Resolver:
|
|
|
44
44
|
*,
|
|
45
45
|
existing_object_id: Optional[str] = None,
|
|
46
46
|
):
|
|
47
|
-
if obj._is_hydrated and obj.
|
|
48
|
-
# No need to reload this, it won't typically change
|
|
47
|
+
if obj._is_hydrated and obj._skip_reload:
|
|
49
48
|
if obj.local_uuid not in self._local_uuid_to_future:
|
|
50
49
|
# a bit dumb - but we still need to store a reference to the object here
|
|
51
50
|
# to be able to include all referenced objects when setting up the app
|
|
@@ -89,12 +88,11 @@ class Resolver:
|
|
|
89
88
|
await obj._load(obj, self, load_context, existing_object_id)
|
|
90
89
|
|
|
91
90
|
# Check that the id of functions didn't change
|
|
92
|
-
# Persisted refs are ignored because their life cycle is managed independently.
|
|
93
91
|
if (
|
|
94
|
-
not
|
|
95
|
-
and existing_object_id is not None
|
|
92
|
+
existing_object_id is not None
|
|
96
93
|
and existing_object_id.startswith("fu-")
|
|
97
94
|
and obj.object_id != existing_object_id
|
|
95
|
+
and not obj._skip_reload
|
|
98
96
|
):
|
|
99
97
|
raise Exception(
|
|
100
98
|
f"Tried creating an object using existing id {existing_object_id} "
|
|
@@ -570,7 +570,7 @@ def import_class_service(
|
|
|
570
570
|
service_function_hydration_data.object_id,
|
|
571
571
|
_client,
|
|
572
572
|
service_function_hydration_data.function_handle_metadata,
|
|
573
|
-
|
|
573
|
+
skip_reload=True, # this skips re-loading the function, which is required since it doesn't have a loader
|
|
574
574
|
)
|
|
575
575
|
_cls = modal.cls._Cls.from_local(cls_or_user_cls, active_app, _service_function)
|
|
576
576
|
# hydration of the class itself - just sets the id and triggers some side effects
|
|
@@ -625,7 +625,7 @@ def import_server_service(
|
|
|
625
625
|
service_function_hydration_data.object_id,
|
|
626
626
|
_client,
|
|
627
627
|
service_function_hydration_data.function_handle_metadata,
|
|
628
|
-
|
|
628
|
+
skip_reload=True, # this skips re-loading the function, which is required since it doesn't have a loader
|
|
629
629
|
)
|
|
630
630
|
|
|
631
631
|
_server = modal._server._Server._from_local(cls_or_user_cls, active_app, _service_function)
|
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
import socket
|
|
6
6
|
import ssl
|
|
7
7
|
import time
|
|
8
|
+
import typing
|
|
8
9
|
import urllib.parse
|
|
9
10
|
import weakref
|
|
10
11
|
from contextlib import suppress
|
|
@@ -16,7 +17,7 @@ from grpclib import GRPCError, Status
|
|
|
16
17
|
from grpclib.exceptions import StreamTerminatedError
|
|
17
18
|
|
|
18
19
|
from modal.config import logger
|
|
19
|
-
from modal.exception import ExecTimeoutError
|
|
20
|
+
from modal.exception import ExecTimeoutError, TimeoutError as ModalTimeoutError
|
|
20
21
|
from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
21
22
|
from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
22
23
|
|
|
@@ -72,13 +73,24 @@ async def call_with_retries_on_transient_errors(
|
|
|
72
73
|
base_delay_secs: float = 0.01,
|
|
73
74
|
delay_factor: float = 2,
|
|
74
75
|
max_retries: Optional[int] = 10,
|
|
76
|
+
exclude_status_codes: Optional[list[Status]] = None,
|
|
77
|
+
expect_timeouts: bool = False, # when True we convert TimeoutError to exception.TimeoutError and don't retry"""
|
|
75
78
|
):
|
|
76
79
|
"""Call func() with transient error retries and exponential backoff.
|
|
77
80
|
|
|
78
81
|
Authentication retries are expected to be handled by the caller.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
exclude_status_codes: gRPC status codes to exclude from retry logic even if
|
|
85
|
+
they are in RETRYABLE_GRPC_STATUS_CODES. Use this to let certain errors
|
|
86
|
+
(e.g. DEADLINE_EXCEEDED) propagate immediately rather than being retried.
|
|
79
87
|
"""
|
|
80
88
|
delay_secs = base_delay_secs
|
|
81
89
|
num_retries = 0
|
|
90
|
+
exclude_status_codes = exclude_status_codes or []
|
|
91
|
+
|
|
92
|
+
def is_retryable_status(status: Status) -> bool:
|
|
93
|
+
return status in RETRYABLE_GRPC_STATUS_CODES and status not in exclude_status_codes
|
|
82
94
|
|
|
83
95
|
async def sleep_and_update_delay_and_num_retries_remaining(e: Exception):
|
|
84
96
|
nonlocal delay_secs, num_retries
|
|
@@ -91,7 +103,7 @@ async def call_with_retries_on_transient_errors(
|
|
|
91
103
|
try:
|
|
92
104
|
return await func()
|
|
93
105
|
except GRPCError as e:
|
|
94
|
-
if (max_retries is None or num_retries < max_retries) and e.status
|
|
106
|
+
if (max_retries is None or num_retries < max_retries) and is_retryable_status(e.status):
|
|
95
107
|
await sleep_and_update_delay_and_num_retries_remaining(e)
|
|
96
108
|
else:
|
|
97
109
|
raise e
|
|
@@ -108,7 +120,16 @@ async def call_with_retries_on_transient_errors(
|
|
|
108
120
|
await sleep_and_update_delay_and_num_retries_remaining(e)
|
|
109
121
|
else:
|
|
110
122
|
raise e
|
|
111
|
-
except
|
|
123
|
+
except asyncio.TimeoutError as e:
|
|
124
|
+
if expect_timeouts:
|
|
125
|
+
# grpclib raises TimeoutError for the client-side timeout/deadlines
|
|
126
|
+
raise ModalTimeoutError("Timeout expired")
|
|
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:
|
|
112
133
|
if max_retries is None or num_retries < max_retries:
|
|
113
134
|
await sleep_and_update_delay_and_num_retries_remaining(e)
|
|
114
135
|
else:
|
|
@@ -186,6 +207,15 @@ class TaskCommandRouterClient:
|
|
|
186
207
|
closed_error_message="Unable to perform operation on a detached sandbox",
|
|
187
208
|
)
|
|
188
209
|
|
|
210
|
+
async def send_request(event: grpclib.events.SendRequest) -> None:
|
|
211
|
+
idempotency_key = typing.cast(Optional[str], event.metadata.get("x-idempotency-key"))
|
|
212
|
+
if idempotency_key is None:
|
|
213
|
+
logger.debug(f"Sending request to {event.method_name}")
|
|
214
|
+
else:
|
|
215
|
+
logger.debug(f"Sending request to {event.method_name} ({idempotency_key[:8]})")
|
|
216
|
+
|
|
217
|
+
grpclib.events.listen(channel, grpclib.events.SendRequest, send_request)
|
|
218
|
+
|
|
189
219
|
try:
|
|
190
220
|
await _connect_channel(channel)
|
|
191
221
|
except socket.gaierror as exc:
|
|
@@ -618,9 +648,28 @@ class TaskCommandRouterClient:
|
|
|
618
648
|
)
|
|
619
649
|
|
|
620
650
|
async def snapshot_directory(
|
|
621
|
-
self, request: sr_pb2.TaskSnapshotDirectoryRequest
|
|
651
|
+
self, request: sr_pb2.TaskSnapshotDirectoryRequest, **kwargs
|
|
622
652
|
) -> sr_pb2.TaskSnapshotDirectoryResponse:
|
|
623
653
|
with grpc_error_converter():
|
|
624
654
|
return await call_with_retries_on_transient_errors(
|
|
625
|
-
lambda: self._call_with_auth_retry(self._stub.TaskSnapshotDirectory, request)
|
|
655
|
+
lambda: self._call_with_auth_retry(self._stub.TaskSnapshotDirectory, request, **kwargs)
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
async def snapshot_filesystem(
|
|
659
|
+
self, request: sr_pb2.TaskSnapshotFilesystemRequest, *, timeout: Optional[int] = None, **kwargs
|
|
660
|
+
) -> sr_pb2.TaskSnapshotFilesystemResponse:
|
|
661
|
+
expect_timeouts = timeout is not None
|
|
662
|
+
with grpc_error_converter(expect_timeouts=expect_timeouts):
|
|
663
|
+
# note: TaskSnapshotFilesystem has a timeout concept with multiple variants:
|
|
664
|
+
# * Normally it would time out on the client side inside of grpclib - this causes an asyncio.TimeoutError
|
|
665
|
+
# * It also propagates the timeout to the server however, which could potentially trigger, in particular
|
|
666
|
+
# if an in-progress requests is idempotently rejoined as a retry. These errors appear to be propagated
|
|
667
|
+
# as Status.CANCELLED at the time of writing this, but we may change the backend to return
|
|
668
|
+
# DEADLINE_EXCEEDED in the future, so we want to make the code compatible with both here:
|
|
669
|
+
return await call_with_retries_on_transient_errors(
|
|
670
|
+
lambda: self._call_with_auth_retry(
|
|
671
|
+
self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
|
|
672
|
+
),
|
|
673
|
+
exclude_status_codes=[Status.DEADLINE_EXCEEDED, Status.CANCELLED],
|
|
674
|
+
expect_timeouts=expect_timeouts, # client wanted a timeout - raise as such immediately
|
|
626
675
|
)
|
|
@@ -106,7 +106,7 @@ async def list_(*, json: bool = False, env: Optional[str] = None):
|
|
|
106
106
|
break
|
|
107
107
|
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
|
108
108
|
|
|
109
|
-
queues = [_Queue._new_hydrated(item.queue_id, client, item.metadata,
|
|
109
|
+
queues = [_Queue._new_hydrated(item.queue_id, client, item.metadata, skip_reload=True) for item in items]
|
|
110
110
|
|
|
111
111
|
rows = []
|
|
112
112
|
for obj, resp_data in zip(queues, items):
|
|
@@ -55,7 +55,7 @@ async def list_(env: Optional[str] = None, json: bool = False):
|
|
|
55
55
|
break
|
|
56
56
|
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
|
57
57
|
|
|
58
|
-
secrets = [_Secret._new_hydrated(item.secret_id, client, item.metadata,
|
|
58
|
+
secrets = [_Secret._new_hydrated(item.secret_id, client, item.metadata, skip_reload=True) for item in items]
|
|
59
59
|
|
|
60
60
|
rows = []
|
|
61
61
|
for obj, resp_data in zip(secrets, items):
|
|
@@ -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.3.
|
|
38
|
+
version: str = "1.4.3.dev29",
|
|
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.3.
|
|
178
|
+
version: str = "1.4.3.dev29",
|
|
179
179
|
):
|
|
180
180
|
"""mdmd:hidden
|
|
181
181
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -519,7 +519,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
|
519
519
|
new_cls = _Cls._from_loader(
|
|
520
520
|
_load_from_base,
|
|
521
521
|
rep=f"{self._name}.{method_name}(...)",
|
|
522
|
-
|
|
522
|
+
skip_reload=True,
|
|
523
523
|
deps=lambda: [],
|
|
524
524
|
load_context_overrides=self._load_context_overrides,
|
|
525
525
|
hydrate_lazily=True,
|
|
@@ -680,7 +680,7 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
680
680
|
cls = cls._from_loader(
|
|
681
681
|
_load_remote,
|
|
682
682
|
rep,
|
|
683
|
-
|
|
683
|
+
skip_reload=True,
|
|
684
684
|
hydrate_lazily=True,
|
|
685
685
|
load_context_overrides=load_context_overrides,
|
|
686
686
|
)
|
|
@@ -198,7 +198,7 @@ class _DictManager:
|
|
|
198
198
|
item.dict_id,
|
|
199
199
|
client,
|
|
200
200
|
item.metadata,
|
|
201
|
-
|
|
201
|
+
skip_reload=True,
|
|
202
202
|
rep=_Dict._repr(item.name, environment_name),
|
|
203
203
|
)
|
|
204
204
|
for item in items
|
|
@@ -354,7 +354,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
354
354
|
response.dict_id,
|
|
355
355
|
client,
|
|
356
356
|
response.metadata,
|
|
357
|
-
|
|
357
|
+
skip_reload=True,
|
|
358
358
|
rep="modal.Dict.ephemeral()",
|
|
359
359
|
)
|
|
360
360
|
|
|
@@ -393,7 +393,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
393
393
|
return _Dict._from_loader(
|
|
394
394
|
_load,
|
|
395
395
|
rep,
|
|
396
|
-
|
|
396
|
+
skip_reload=True,
|
|
397
397
|
hydrate_lazily=True,
|
|
398
398
|
name=name,
|
|
399
399
|
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
@@ -436,7 +436,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
436
436
|
return _Dict._from_loader(
|
|
437
437
|
_load,
|
|
438
438
|
rep,
|
|
439
|
-
|
|
439
|
+
skip_reload=True,
|
|
440
440
|
hydrate_lazily=True,
|
|
441
441
|
load_context_overrides=LoadContext(client=client),
|
|
442
442
|
)
|
|
@@ -420,7 +420,7 @@ class Function(
|
|
|
420
420
|
|
|
421
421
|
_call_generator: ___call_generator_spec
|
|
422
422
|
|
|
423
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
423
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
424
424
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
425
425
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
426
426
|
...
|
|
@@ -429,7 +429,7 @@ class Function(
|
|
|
429
429
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
430
430
|
...
|
|
431
431
|
|
|
432
|
-
remote: __remote_spec[modal._functions.
|
|
432
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P]
|
|
433
433
|
|
|
434
434
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
435
435
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -456,7 +456,7 @@ class Function(
|
|
|
456
456
|
"""
|
|
457
457
|
...
|
|
458
458
|
|
|
459
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
459
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
460
460
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
461
461
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
462
462
|
|
|
@@ -479,7 +479,7 @@ class Function(
|
|
|
479
479
|
"""
|
|
480
480
|
...
|
|
481
481
|
|
|
482
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
482
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
483
483
|
|
|
484
484
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
485
485
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -487,7 +487,7 @@ class Function(
|
|
|
487
487
|
|
|
488
488
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
489
489
|
|
|
490
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
490
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
|
491
491
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
492
492
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
493
493
|
|
|
@@ -508,7 +508,7 @@ class Function(
|
|
|
508
508
|
"""
|
|
509
509
|
...
|
|
510
510
|
|
|
511
|
-
spawn: __spawn_spec[modal._functions.
|
|
511
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P]
|
|
512
512
|
|
|
513
513
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
514
514
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -664,7 +664,7 @@ class _Mount(_Object, type_prefix="mo"):
|
|
|
664
664
|
return _Mount._from_loader(
|
|
665
665
|
_load,
|
|
666
666
|
"Mount()",
|
|
667
|
-
|
|
667
|
+
skip_reload=True,
|
|
668
668
|
hydrate_lazily=True,
|
|
669
669
|
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
670
670
|
)
|
|
@@ -134,7 +134,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
134
134
|
return _NetworkFileSystem._from_loader(
|
|
135
135
|
_load,
|
|
136
136
|
"NetworkFileSystem()",
|
|
137
|
-
|
|
137
|
+
skip_reload=True,
|
|
138
138
|
hydrate_lazily=True,
|
|
139
139
|
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
140
140
|
)
|
|
@@ -174,7 +174,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
174
174
|
response.shared_volume_id,
|
|
175
175
|
client,
|
|
176
176
|
None,
|
|
177
|
-
|
|
177
|
+
skip_reload=True,
|
|
178
178
|
rep="modal.NetworkFileSystem.ephemeral()",
|
|
179
179
|
)
|
|
180
180
|
|
|
@@ -24,7 +24,7 @@ class Object:
|
|
|
24
24
|
]
|
|
25
25
|
]
|
|
26
26
|
_rep: str
|
|
27
|
-
|
|
27
|
+
_skip_reload: bool
|
|
28
28
|
_hydrate_lazily: bool
|
|
29
29
|
_deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]]
|
|
30
30
|
_deduplication_key: typing.Optional[
|
|
@@ -60,7 +60,6 @@ class Object:
|
|
|
60
60
|
None,
|
|
61
61
|
]
|
|
62
62
|
] = None,
|
|
63
|
-
is_another_app: bool = False,
|
|
64
63
|
preload: typing.Optional[
|
|
65
64
|
collections.abc.Callable[
|
|
66
65
|
[
|
|
@@ -73,6 +72,7 @@ class Object:
|
|
|
73
72
|
]
|
|
74
73
|
] = None,
|
|
75
74
|
hydrate_lazily: bool = False,
|
|
75
|
+
skip_reload: bool = False,
|
|
76
76
|
deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]] = None,
|
|
77
77
|
deduplication_key: typing.Optional[collections.abc.Callable[[], collections.abc.Hashable]] = None,
|
|
78
78
|
name: typing.Optional[str] = None,
|
|
@@ -94,7 +94,6 @@ class Object:
|
|
|
94
94
|
collections.abc.Awaitable[None],
|
|
95
95
|
]
|
|
96
96
|
] = None,
|
|
97
|
-
is_another_app: bool = False,
|
|
98
97
|
preload: typing.Optional[
|
|
99
98
|
collections.abc.Callable[
|
|
100
99
|
[
|
|
@@ -107,6 +106,7 @@ class Object:
|
|
|
107
106
|
]
|
|
108
107
|
] = None,
|
|
109
108
|
hydrate_lazily: bool = False,
|
|
109
|
+
skip_reload: bool = False,
|
|
110
110
|
deps: typing.Optional[collections.abc.Callable[..., collections.abc.Sequence[Object]]] = None,
|
|
111
111
|
deduplication_key: typing.Optional[
|
|
112
112
|
collections.abc.Callable[[], collections.abc.Awaitable[collections.abc.Hashable]]
|
|
@@ -148,7 +148,7 @@ class Object:
|
|
|
148
148
|
None,
|
|
149
149
|
],
|
|
150
150
|
rep: str,
|
|
151
|
-
|
|
151
|
+
skip_reload: bool = False,
|
|
152
152
|
preload: typing.Optional[
|
|
153
153
|
collections.abc.Callable[
|
|
154
154
|
[
|
|
@@ -180,7 +180,7 @@ class Object:
|
|
|
180
180
|
collections.abc.Awaitable[None],
|
|
181
181
|
],
|
|
182
182
|
rep: str,
|
|
183
|
-
|
|
183
|
+
skip_reload: bool = False,
|
|
184
184
|
preload: typing.Optional[
|
|
185
185
|
collections.abc.Callable[
|
|
186
186
|
[
|
|
@@ -216,7 +216,7 @@ class Object:
|
|
|
216
216
|
object_id: str,
|
|
217
217
|
client: modal.client.Client,
|
|
218
218
|
handle_metadata: typing.Optional[google.protobuf.message.Message],
|
|
219
|
-
|
|
219
|
+
skip_reload: bool = False,
|
|
220
220
|
rep: typing.Optional[str] = None,
|
|
221
221
|
) -> typing_extensions.Self: ...
|
|
222
222
|
def _hydrate_from_other(self, other: typing_extensions.Self): ...
|
|
@@ -163,7 +163,7 @@ class _QueueManager:
|
|
|
163
163
|
item.queue_id,
|
|
164
164
|
client,
|
|
165
165
|
item.metadata,
|
|
166
|
-
|
|
166
|
+
skip_reload=True,
|
|
167
167
|
rep=_Queue._repr(item.name, environment_name),
|
|
168
168
|
)
|
|
169
169
|
for item in items
|
|
@@ -353,7 +353,7 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
353
353
|
async with TaskContext() as tc:
|
|
354
354
|
request = api_pb2.QueueHeartbeatRequest(queue_id=response.queue_id)
|
|
355
355
|
tc.infinite_loop(lambda: client.stub.QueueHeartbeat(request), sleep=_heartbeat_sleep)
|
|
356
|
-
yield cls._new_hydrated(response.queue_id, client, response.metadata,
|
|
356
|
+
yield cls._new_hydrated(response.queue_id, client, response.metadata, skip_reload=True)
|
|
357
357
|
|
|
358
358
|
@staticmethod
|
|
359
359
|
def from_name(
|
|
@@ -389,7 +389,7 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
389
389
|
return _Queue._from_loader(
|
|
390
390
|
_load,
|
|
391
391
|
rep,
|
|
392
|
-
|
|
392
|
+
skip_reload=True,
|
|
393
393
|
hydrate_lazily=True,
|
|
394
394
|
name=name,
|
|
395
395
|
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
@@ -432,7 +432,7 @@ class _Queue(_Object, type_prefix="qu"):
|
|
|
432
432
|
return _Queue._from_loader(
|
|
433
433
|
_load,
|
|
434
434
|
rep,
|
|
435
|
-
|
|
435
|
+
skip_reload=True,
|
|
436
436
|
hydrate_lazily=True,
|
|
437
437
|
load_context_overrides=LoadContext(client=client),
|
|
438
438
|
)
|
|
@@ -74,7 +74,7 @@ _default_image: _Image = _Image.debian_slim()
|
|
|
74
74
|
# We need some bytes of overhead for the rest of the command line besides the args,
|
|
75
75
|
# e.g. 'runsc exec ...'. So we use 2**16 as the limit.
|
|
76
76
|
ARG_MAX_BYTES = 2**16
|
|
77
|
-
|
|
77
|
+
TTL_NO_EXPIRY_SENTINEL = -1
|
|
78
78
|
|
|
79
79
|
if TYPE_CHECKING:
|
|
80
80
|
import modal.app
|
|
@@ -955,9 +955,29 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
955
955
|
|
|
956
956
|
Returns an [`Image`](https://modal.com/docs/reference/modal.Image) object which
|
|
957
957
|
can be used to spawn a new Sandbox with the same filesystem.
|
|
958
|
+
|
|
959
|
+
`timeout` If the snapshot does not return within that window, the call is cancelled
|
|
960
|
+
and `modal.exception.TimeoutError` is raised.
|
|
958
961
|
"""
|
|
962
|
+
if os.environ.get("MODAL_USE_LEGACY_FILESYSTEM_SNAPSHOT") == "1":
|
|
963
|
+
return await self._legacy_snapshot_filesystem(timeout)
|
|
964
|
+
|
|
959
965
|
self._ensure_v1("snapshot_filesystem")
|
|
960
|
-
|
|
966
|
+
|
|
967
|
+
task_id = await self._get_task_id()
|
|
968
|
+
command_router_client = await self._get_command_router_client(task_id)
|
|
969
|
+
|
|
970
|
+
req = sr_pb2.TaskSnapshotFilesystemRequest(
|
|
971
|
+
task_id=task_id,
|
|
972
|
+
snapshot_id=str(uuid.uuid4()),
|
|
973
|
+
ttl_seconds=TTL_NO_EXPIRY_SENTINEL,
|
|
974
|
+
)
|
|
975
|
+
res = await command_router_client.snapshot_filesystem(req, timeout=float(timeout))
|
|
976
|
+
return _Image._new_hydrated(res.image_id, self._client, None)
|
|
977
|
+
|
|
978
|
+
async def _legacy_snapshot_filesystem(self, timeout: int = 55) -> _Image:
|
|
979
|
+
self._ensure_v1("snapshot_filesystem")
|
|
980
|
+
await self._get_task_id()
|
|
961
981
|
req = api_pb2.SandboxSnapshotFsRequest(sandbox_id=self.object_id, timeout=timeout)
|
|
962
982
|
resp = await self._client.stub.SandboxSnapshotFs(req)
|
|
963
983
|
|
|
@@ -1048,7 +1068,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1048
1068
|
async def snapshot_directory(self, path: Union[PurePosixPath, str]) -> _Image:
|
|
1049
1069
|
"""Snapshot a directory in a running Sandbox, creating a new Image with its content.
|
|
1050
1070
|
|
|
1051
|
-
Directory snapshots are currently persisted for 30 days after they were
|
|
1071
|
+
Directory snapshots are currently persisted for 30 days after they were created.
|
|
1052
1072
|
|
|
1053
1073
|
Usage:
|
|
1054
1074
|
```py notest
|
|
@@ -1071,7 +1091,12 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1071
1091
|
path_bytes = posix_path.as_posix().encode("utf8")
|
|
1072
1092
|
|
|
1073
1093
|
snapshot_id = str(uuid.uuid4())
|
|
1074
|
-
req = sr_pb2.TaskSnapshotDirectoryRequest(
|
|
1094
|
+
req = sr_pb2.TaskSnapshotDirectoryRequest(
|
|
1095
|
+
task_id=task_id,
|
|
1096
|
+
path=path_bytes,
|
|
1097
|
+
snapshot_id=snapshot_id,
|
|
1098
|
+
ttl_seconds=None, # pretending to be old client for now - set server decide on retention
|
|
1099
|
+
)
|
|
1075
1100
|
res = await command_router_client.snapshot_directory(req)
|
|
1076
1101
|
return _Image._new_hydrated(res.image_id, self._client, None)
|
|
1077
1102
|
|