modal 1.5.2.dev21__tar.gz → 1.5.3.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.5.2.dev21 → modal-1.5.3.dev0}/PKG-INFO +1 -1
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/grpc_utils.py +19 -4
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/task_command_router_client.py +18 -34
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/_help.py +7 -1
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/changelog.py +7 -7
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/environment.py +9 -9
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/client.pyi +2 -2
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/functions.pyi +6 -6
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/parallel_map.py +56 -51
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox.py +26 -12
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox.pyi +12 -3
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_grpc.py +64 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2.py +979 -861
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2.pyi +283 -6
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2_grpc.py +133 -1
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/api_pb2_grpc.pyi +43 -3
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/modal_api_grpc.py +4 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_version/__init__.py +1 -1
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/LICENSE +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/README.md +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/__main__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_billing.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_environments.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_function_variants.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_functions.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_grpc_client.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_image.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_ipython.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_load_context.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_location.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_logs.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_object.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/manager.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/pty.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/rich.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_output/status.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_partial_function.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_resolver.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_resources.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_serialization.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_server.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_traceback.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_tunnel.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_type_manager.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_vendor/version.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_watcher.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/_workspace.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/app.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/app.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/billing.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/billing.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/README.md +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/builder/base-images.json +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/call_graph.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/_download.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/app.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/billing.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/config.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/container.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/curl.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/dashboard.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/dict.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/endpoint.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/image.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/launch.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/logo.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/profile.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/queues.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/run.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/secret.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/selector.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/shell.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/skills.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/token.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/utils.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/volume.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cli/workspace.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/client.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cls.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/cls.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/config.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/container_process.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/container_process.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/dict.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/dict.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/environments.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/environments.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/exception.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/flash.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/file_io.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/file_io.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/functions.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/image.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/image.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/io_streams.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/mount.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/mount.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/network_file_system.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/object.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/object.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/output.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/partial_function.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/proxy.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/proxy.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/py.typed +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/queue.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/queue.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/retries.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/runner.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/runner.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/running_app.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox_fs.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/schedule.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/secret.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/secret.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/server.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/server.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/serving.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/serving.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/snapshot.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/stream_type.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/token_flow.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/types.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/volume.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/volume.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/workspace.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal/workspace.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/py.typed +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/modal_version/__main__.py +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/pyproject.toml +0 -0
- {modal-1.5.2.dev21 → modal-1.5.3.dev0}/setup.cfg +0 -0
|
@@ -164,6 +164,9 @@ RETRYABLE_GRPC_STATUS_CODES = [
|
|
|
164
164
|
]
|
|
165
165
|
SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
|
|
166
166
|
DEFAULT_MAX_RETRIES = 3
|
|
167
|
+
GRPC_KEEPALIVE_TIME_SECS = 30.0
|
|
168
|
+
GRPC_KEEPALIVE_TIMEOUT_SECS = 10.0
|
|
169
|
+
GRPC_WINDOW_SIZE = 64 * 1024 * 1024 # 64 MiB
|
|
167
170
|
|
|
168
171
|
|
|
169
172
|
@dataclass
|
|
@@ -251,6 +254,21 @@ class CustomProtoStatusDetailsCodec(StatusDetailsCodecBase):
|
|
|
251
254
|
custom_detail_codec = CustomProtoStatusDetailsCodec()
|
|
252
255
|
|
|
253
256
|
|
|
257
|
+
def create_channel_config() -> grpclib.config.Configuration:
|
|
258
|
+
"""Shared grpclib channel settings for Modal client connections.
|
|
259
|
+
|
|
260
|
+
HTTP/2 keepalive probes keep idle transports warm through stateful
|
|
261
|
+
middleboxes and surface dead connections before retries reuse them.
|
|
262
|
+
"""
|
|
263
|
+
return grpclib.config.Configuration(
|
|
264
|
+
_keepalive_time=GRPC_KEEPALIVE_TIME_SECS,
|
|
265
|
+
_keepalive_timeout=GRPC_KEEPALIVE_TIMEOUT_SECS,
|
|
266
|
+
_keepalive_permit_without_calls=True,
|
|
267
|
+
http2_connection_window_size=GRPC_WINDOW_SIZE,
|
|
268
|
+
http2_stream_window_size=GRPC_WINDOW_SIZE,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
254
272
|
def create_channel(
|
|
255
273
|
server_url: str,
|
|
256
274
|
metadata: dict[str, str] = {},
|
|
@@ -262,10 +280,7 @@ def create_channel(
|
|
|
262
280
|
o = urllib.parse.urlparse(server_url)
|
|
263
281
|
|
|
264
282
|
channel: grpclib.client.Channel
|
|
265
|
-
config =
|
|
266
|
-
http2_connection_window_size=64 * 1024 * 1024, # 64 MiB
|
|
267
|
-
http2_stream_window_size=64 * 1024 * 1024, # 64 MiB
|
|
268
|
-
)
|
|
283
|
+
config = create_channel_config()
|
|
269
284
|
|
|
270
285
|
if o.scheme == "unix":
|
|
271
286
|
channel = grpclib.client.Channel(path=o.path, config=config, status_details_codec=custom_detail_codec)
|
|
@@ -13,7 +13,6 @@ from contextlib import suppress
|
|
|
13
13
|
from typing import TypeVar
|
|
14
14
|
|
|
15
15
|
import grpclib.client
|
|
16
|
-
import grpclib.config
|
|
17
16
|
from grpclib import GRPCError, Status
|
|
18
17
|
from grpclib.exceptions import StreamTerminatedError
|
|
19
18
|
|
|
@@ -23,7 +22,7 @@ from modal_proto import api_pb2, task_command_router_pb2 as sr_pb2
|
|
|
23
22
|
from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
24
23
|
|
|
25
24
|
from .._grpc_client import grpc_error_converter
|
|
26
|
-
from .._utils.grpc_utils import ModalChannel
|
|
25
|
+
from .._utils.grpc_utils import ModalChannel, create_channel_config
|
|
27
26
|
from .async_utils import aclosing, retry
|
|
28
27
|
from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
29
28
|
|
|
@@ -222,10 +221,7 @@ class TaskCommandRouterClient:
|
|
|
222
221
|
host,
|
|
223
222
|
port,
|
|
224
223
|
ssl=ssl_context,
|
|
225
|
-
config=
|
|
226
|
-
http2_connection_window_size=64 * 1024 * 1024, # 64 MiB
|
|
227
|
-
http2_stream_window_size=64 * 1024 * 1024, # 64 MiB
|
|
228
|
-
),
|
|
224
|
+
config=create_channel_config(),
|
|
229
225
|
closed_error_message="Unable to perform operation on a detached sandbox",
|
|
230
226
|
)
|
|
231
227
|
|
|
@@ -501,16 +497,7 @@ class TaskCommandRouterClient:
|
|
|
501
497
|
TimeoutError: If the sandbox does not become ready within `timeout`.
|
|
502
498
|
"""
|
|
503
499
|
request = sr_pb2.SandboxWaitUntilReadyTcrRequest(task_id=task_id, timeout=timeout)
|
|
504
|
-
|
|
505
|
-
try:
|
|
506
|
-
return await asyncio.wait_for(
|
|
507
|
-
call_with_retries_on_transient_errors(
|
|
508
|
-
lambda: self._call_with_auth_retry(self._stub.SandboxWaitUntilReady, request, timeout=timeout),
|
|
509
|
-
),
|
|
510
|
-
timeout=timeout,
|
|
511
|
-
)
|
|
512
|
-
except asyncio.TimeoutError:
|
|
513
|
-
raise ModalTimeoutError("Timeout expired")
|
|
500
|
+
return await self._unary_call_with_deadline(self._stub.SandboxWaitUntilReady, request, timeout=timeout)
|
|
514
501
|
|
|
515
502
|
async def exec_poll(self, task_id: str, exec_id: str, deadline: float | None = None) -> sr_pb2.TaskExecPollResponse:
|
|
516
503
|
"""Poll for the exit status of an exec'd command, properly retrying on transient errors.
|
|
@@ -820,21 +807,14 @@ class TaskCommandRouterClient:
|
|
|
820
807
|
window, the call is cancelled and a `modal.exception.TimeoutError` is raised.
|
|
821
808
|
"""
|
|
822
809
|
request = sr_pb2.TaskReloadVolumesRequest(task_id=task_id)
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
lambda: self._call_with_auth_retry(self._stub.TaskReloadVolumes, request, timeout=timeout),
|
|
828
|
-
),
|
|
829
|
-
timeout=timeout,
|
|
830
|
-
)
|
|
831
|
-
except asyncio.TimeoutError:
|
|
832
|
-
raise ModalTimeoutError("Timeout expired")
|
|
810
|
+
return await self._unary_call_with_deadline(self._stub.TaskReloadVolumes, request, timeout=timeout)
|
|
811
|
+
|
|
812
|
+
async def _unary_call_with_deadline(self, rpc, request, *, timeout: float, **kwargs):
|
|
813
|
+
"""Call a unary RPC with transient-error retries bounded by a total deadline.
|
|
833
814
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
# the total deadline budget
|
|
815
|
+
Raises `modal.exception.TimeoutError` once the deadline has elapsed, or when
|
|
816
|
+
the server responds with `DEADLINE_EXCEEDED`.
|
|
817
|
+
"""
|
|
838
818
|
timeout_deadline = time.monotonic() + timeout
|
|
839
819
|
|
|
840
820
|
def call():
|
|
@@ -852,17 +832,21 @@ class TaskCommandRouterClient:
|
|
|
852
832
|
exclude_status_codes=[Status.DEADLINE_EXCEEDED, Status.CANCELLED],
|
|
853
833
|
timeout_deadline=timeout_deadline,
|
|
854
834
|
)
|
|
855
|
-
except Exception:
|
|
856
|
-
if time.monotonic() >= timeout_deadline:
|
|
835
|
+
except Exception as exc:
|
|
836
|
+
if time.monotonic() >= timeout_deadline or getattr(exc, "_grpc_status", None) == Status.DEADLINE_EXCEEDED:
|
|
857
837
|
raise ModalTimeoutError("Timeout expired")
|
|
858
838
|
raise
|
|
859
839
|
|
|
860
840
|
async def snapshot_directory(
|
|
861
841
|
self, request: sr_pb2.TaskSnapshotDirectoryRequest, *, timeout: float, **kwargs
|
|
862
842
|
) -> sr_pb2.TaskSnapshotDirectoryResponse:
|
|
863
|
-
return await self.
|
|
843
|
+
return await self._unary_call_with_deadline(
|
|
844
|
+
self._stub.TaskSnapshotDirectory, request, timeout=timeout, **kwargs
|
|
845
|
+
)
|
|
864
846
|
|
|
865
847
|
async def snapshot_filesystem(
|
|
866
848
|
self, request: sr_pb2.TaskSnapshotFilesystemRequest, *, timeout: float, **kwargs
|
|
867
849
|
) -> sr_pb2.TaskSnapshotFilesystemResponse:
|
|
868
|
-
return await self.
|
|
850
|
+
return await self._unary_call_with_deadline(
|
|
851
|
+
self._stub.TaskSnapshotFilesystem, request, timeout=timeout, **kwargs
|
|
852
|
+
)
|
|
@@ -70,7 +70,13 @@ def _option_label(param: click.Parameter, ctx: click.Context) -> Text:
|
|
|
70
70
|
text.append(opt, style=_OPTION_FLAG_STYLE)
|
|
71
71
|
if not getattr(param, "is_flag", False) and not getattr(param, "count", False):
|
|
72
72
|
text.append(" ")
|
|
73
|
-
|
|
73
|
+
if "ctx" in inspect.signature(click.Parameter.make_metavar).parameters:
|
|
74
|
+
# click >= 8.2.0
|
|
75
|
+
metavar = param.make_metavar(ctx)
|
|
76
|
+
else:
|
|
77
|
+
# backwards compatibility
|
|
78
|
+
metavar = param.make_metavar() # type: ignore[call-arg]
|
|
79
|
+
text.append(metavar, style=_OPTION_METAVAR_STYLE)
|
|
74
80
|
return text
|
|
75
81
|
|
|
76
82
|
|
|
@@ -120,17 +120,17 @@ def changelog(
|
|
|
120
120
|
|
|
121
121
|
Examples:
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
```bash
|
|
124
|
+
modal changelog --since 1.2.0 # Show updates added after a specific version
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
modal changelog --since 2026-01-01 # Show updates added after a specific date
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
modal changelog --newer # Show updates released after the currently installed version
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
modal changelog --last 3 # Show updates included in the 3 most recent releases
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
modal changelog --for 1.3.1 # Show the changelog for a specific release
|
|
133
|
+
```
|
|
134
134
|
|
|
135
135
|
Note: when using `--since` or `--last`, only changes up to the currently installed version are shown.
|
|
136
136
|
|
|
@@ -242,21 +242,21 @@ async def environment_billing(
|
|
|
242
242
|
|
|
243
243
|
Examples:
|
|
244
244
|
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
```bash
|
|
246
|
+
modal environment billing report --start 2025-12-01 --end 2026-01-01
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
modal environment billing report --for "last month" --tag-names team,project
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
modal environment billing report test_env --for today --resolution h
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
modal environment billing report test_env --for "this month" --show-resources
|
|
253
253
|
|
|
254
|
-
|
|
254
|
+
modal environment billing report prod_env --for yesterday -r h --tz local
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
modal environment billing report main_env --for "last month" --csv > report.csv
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
modal environment billing report main_env --start 2025-12-01 --json > report.json
|
|
259
|
+
```
|
|
260
260
|
|
|
261
261
|
"""
|
|
262
262
|
if json and csv:
|
|
@@ -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.5.
|
|
38
|
+
version: str = "1.5.3.dev0",
|
|
39
39
|
):
|
|
40
40
|
"""mdmd:hidden
|
|
41
41
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -205,7 +205,7 @@ class Client:
|
|
|
205
205
|
server_url: str,
|
|
206
206
|
client_type: int,
|
|
207
207
|
credentials: typing.Optional[tuple[str, str]],
|
|
208
|
-
version: str = "1.5.
|
|
208
|
+
version: str = "1.5.3.dev0",
|
|
209
209
|
):
|
|
210
210
|
"""mdmd:hidden
|
|
211
211
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -491,7 +491,7 @@ class Function(
|
|
|
491
491
|
|
|
492
492
|
_call_generator: ___call_generator_spec
|
|
493
493
|
|
|
494
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
494
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
495
495
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
496
496
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
497
497
|
|
|
@@ -516,7 +516,7 @@ class Function(
|
|
|
516
516
|
"""
|
|
517
517
|
...
|
|
518
518
|
|
|
519
|
-
remote: __remote_spec[modal._functions.
|
|
519
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
520
520
|
|
|
521
521
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
522
522
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -566,7 +566,7 @@ class Function(
|
|
|
566
566
|
"""
|
|
567
567
|
...
|
|
568
568
|
|
|
569
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
569
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
570
570
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
571
571
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
572
572
|
|
|
@@ -599,7 +599,7 @@ class Function(
|
|
|
599
599
|
"""
|
|
600
600
|
...
|
|
601
601
|
|
|
602
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
602
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
603
603
|
|
|
604
604
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
605
605
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -607,7 +607,7 @@ class Function(
|
|
|
607
607
|
|
|
608
608
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
609
609
|
|
|
610
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
610
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
611
611
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
612
612
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
613
613
|
|
|
@@ -640,7 +640,7 @@ class Function(
|
|
|
640
640
|
"""
|
|
641
641
|
...
|
|
642
642
|
|
|
643
|
-
spawn: __spawn_spec[modal._functions.
|
|
643
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
644
644
|
|
|
645
645
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
646
646
|
"""Return the inner Python object wrapped by this Modal Function.
|
|
@@ -654,16 +654,14 @@ async def _map_invocation_inputplane(
|
|
|
654
654
|
stale_retry_duplicates = 0
|
|
655
655
|
already_complete_duplicates = 0
|
|
656
656
|
retried_outputs = 0
|
|
657
|
-
input_queue_size = 0
|
|
658
657
|
last_entry_id = ""
|
|
659
658
|
|
|
660
659
|
# The input-plane server returns this after the first request.
|
|
661
660
|
map_token = None
|
|
662
661
|
map_token_received = asyncio.Event()
|
|
663
662
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
queue: TimestampPriorityQueue[api_pb2.MapStartOrContinueItem] = TimestampPriorityQueue()
|
|
663
|
+
input_queue: asyncio.Queue[api_pb2.MapStartOrContinueItem | None] = asyncio.Queue()
|
|
664
|
+
retry_queue: TimestampPriorityQueue[api_pb2.MapStartOrContinueItem] = TimestampPriorityQueue()
|
|
667
665
|
|
|
668
666
|
# Maximum number of inputs that may be in-flight (the server sends this in
|
|
669
667
|
# the first response – fall back to the default if we never receive it for
|
|
@@ -682,7 +680,7 @@ async def _map_invocation_inputplane(
|
|
|
682
680
|
map_items_manager = _MapItemsManager(
|
|
683
681
|
retry_policy=retry_policy,
|
|
684
682
|
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
|
|
685
|
-
retry_queue=
|
|
683
|
+
retry_queue=retry_queue,
|
|
686
684
|
sync_client_retries_enabled=True,
|
|
687
685
|
max_inputs_outstanding=MAX_INPUTS_OUTSTANDING_DEFAULT,
|
|
688
686
|
is_input_plane_instance=True,
|
|
@@ -729,61 +727,67 @@ async def _map_invocation_inputplane(
|
|
|
729
727
|
async_map_ordered(input_iter(), create_input, concurrency=BLOB_MAX_PARALLELISM)
|
|
730
728
|
) as streamer:
|
|
731
729
|
async for q_item in streamer:
|
|
732
|
-
await
|
|
730
|
+
await input_queue.put(q_item)
|
|
733
731
|
|
|
734
732
|
# All inputs have been read.
|
|
733
|
+
await input_queue.put(None)
|
|
735
734
|
update_counters(set_have_all_inputs=True)
|
|
736
735
|
yield
|
|
737
736
|
|
|
738
|
-
async def
|
|
737
|
+
async def send_map_start_or_continue(request_items: list[api_pb2.MapStartOrContinueItem]):
|
|
739
738
|
nonlocal map_token, max_inputs_outstanding
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
739
|
+
request = api_pb2.MapStartOrContinueRequest(
|
|
740
|
+
function_id=function.object_id,
|
|
741
|
+
map_token=map_token or "",
|
|
742
|
+
parent_input_id=current_input_id() or "",
|
|
743
|
+
items=request_items,
|
|
744
|
+
)
|
|
745
745
|
|
|
746
|
-
|
|
746
|
+
metadata = await client.get_input_plane_metadata(function._input_plane_region)
|
|
747
747
|
|
|
748
|
-
|
|
749
|
-
request
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
)
|
|
748
|
+
response: api_pb2.MapStartOrContinueResponse = await input_plane_stub.MapStartOrContinue(
|
|
749
|
+
request,
|
|
750
|
+
retry=Retry(
|
|
751
|
+
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
752
|
+
max_delay=PUMP_INPUTS_MAX_RETRY_DELAY,
|
|
753
|
+
max_retries=None,
|
|
754
|
+
),
|
|
755
|
+
metadata=metadata,
|
|
756
|
+
)
|
|
755
757
|
|
|
756
|
-
|
|
758
|
+
# match response items to the corresponding request item index
|
|
759
|
+
response_items_idx_tuple = [
|
|
760
|
+
(request_items[idx].input.idx, attempt_token) for idx, attempt_token in enumerate(response.attempt_tokens)
|
|
761
|
+
]
|
|
757
762
|
|
|
758
|
-
|
|
759
|
-
request,
|
|
760
|
-
retry=Retry(
|
|
761
|
-
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
762
|
-
max_delay=PUMP_INPUTS_MAX_RETRY_DELAY,
|
|
763
|
-
max_retries=None,
|
|
764
|
-
),
|
|
765
|
-
metadata=metadata,
|
|
766
|
-
)
|
|
763
|
+
map_items_manager.handle_put_continue_response(response_items_idx_tuple)
|
|
767
764
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
765
|
+
# Set the function call id and actual retry policy with the data from the first response.
|
|
766
|
+
if map_token is None:
|
|
767
|
+
map_token = response.map_token
|
|
768
|
+
map_token_received.set()
|
|
769
|
+
max_inputs_outstanding = response.max_inputs_outstanding or MAX_INPUTS_OUTSTANDING_DEFAULT
|
|
770
|
+
map_items_manager.set_retry_policy(response.retry_policy)
|
|
771
|
+
# Update the retry policy for the first batch of inputs.
|
|
772
|
+
# Subsequent batches will have the correct user-specified retry policy
|
|
773
|
+
# set by the updated _MapItemsManager.
|
|
774
|
+
map_items_manager.update_items_retry_policy(response.retry_policy)
|
|
773
775
|
|
|
774
|
-
|
|
776
|
+
async def pump_inputs():
|
|
777
|
+
async for batch in queue_batch_iterator(input_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
|
|
778
|
+
request_items: list[api_pb2.MapStartOrContinueItem] = list(batch)
|
|
779
|
+
await map_items_manager.add_items_inputplane(request_items)
|
|
780
|
+
await send_map_start_or_continue(request_items)
|
|
781
|
+
yield
|
|
775
782
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
# Subsequent batches will have the correct user-specified retry policy
|
|
785
|
-
# set by the updated _MapItemsManager.
|
|
786
|
-
map_items_manager.update_items_retry_policy(response.retry_policy)
|
|
783
|
+
async def retry_inputs():
|
|
784
|
+
while True:
|
|
785
|
+
retry_item = await retry_queue.get()
|
|
786
|
+
if retry_item is None:
|
|
787
|
+
break
|
|
788
|
+
request_items = [retry_item]
|
|
789
|
+
await map_items_manager.add_items_inputplane(request_items)
|
|
790
|
+
await send_map_start_or_continue(request_items)
|
|
787
791
|
yield
|
|
788
792
|
|
|
789
793
|
async def check_lost_inputs():
|
|
@@ -897,8 +901,8 @@ async def _map_invocation_inputplane(
|
|
|
897
901
|
async for item in stream:
|
|
898
902
|
yield item
|
|
899
903
|
finally:
|
|
900
|
-
await
|
|
901
|
-
|
|
904
|
+
await input_queue.put(None)
|
|
905
|
+
await retry_queue.close()
|
|
902
906
|
|
|
903
907
|
async def fetch_output(item: api_pb2.FunctionGetOutputsItem) -> tuple[int, Any]:
|
|
904
908
|
try:
|
|
@@ -945,7 +949,8 @@ async def _map_invocation_inputplane(
|
|
|
945
949
|
f"no_context_duplicates={no_context_duplicates} stale_retry_duplicates={stale_retry_duplicates} "
|
|
946
950
|
f"already_complete_duplicates={already_complete_duplicates} retried_outputs={retried_outputs} "
|
|
947
951
|
f"map_token={map_token} max_inputs_outstanding={max_inputs_outstanding} "
|
|
948
|
-
f"map_items_manager_size={len(map_items_manager)} input_queue_size={
|
|
952
|
+
f"map_items_manager_size={len(map_items_manager)} input_queue_size={input_queue.qsize()} "
|
|
953
|
+
f"retry_queue_size={retry_queue.qsize()}"
|
|
949
954
|
)
|
|
950
955
|
|
|
951
956
|
while True:
|
|
@@ -960,7 +965,7 @@ async def _map_invocation_inputplane(
|
|
|
960
965
|
log_task = asyncio.create_task(log_debug_stats())
|
|
961
966
|
|
|
962
967
|
async with aclosing(
|
|
963
|
-
async_merge(drain_input_generator(), pump_inputs(), poll_outputs(), check_lost_inputs())
|
|
968
|
+
async_merge(drain_input_generator(), pump_inputs(), retry_inputs(), poll_outputs(), check_lost_inputs())
|
|
964
969
|
) as merged:
|
|
965
970
|
async for maybe_output in merged:
|
|
966
971
|
if maybe_output is not None: # ignore None sentinels
|
|
@@ -816,6 +816,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
816
816
|
*args: str,
|
|
817
817
|
app: "modal.app._App | None" = None,
|
|
818
818
|
name: str | None = None,
|
|
819
|
+
tags: dict[str, str] | None = None,
|
|
819
820
|
image: _Image | None = None,
|
|
820
821
|
env: dict[str, str | None] | None = None,
|
|
821
822
|
secrets: Collection[_Secret] | None = None,
|
|
@@ -851,7 +852,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
851
852
|
`secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
|
|
852
853
|
filesystem snapshots.
|
|
853
854
|
|
|
854
|
-
Features like
|
|
855
|
+
Features like memory snapshots, network file systems, GPUs, and custom
|
|
855
856
|
domains are not supported.
|
|
856
857
|
|
|
857
858
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
@@ -993,10 +994,12 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
993
994
|
),
|
|
994
995
|
)
|
|
995
996
|
|
|
997
|
+
tag_protos = [api_pb2.SandboxTag(tag_name=k, tag_value=v) for k, v in tags.items()] if tags else []
|
|
996
998
|
create_req = api_pb2.SandboxCreateV2Request(
|
|
997
999
|
app_id=load_context.app_id,
|
|
998
1000
|
definition=definition,
|
|
999
1001
|
ephemeral_secrets=api_pb2.StringMap(contents=env_dict) if env_dict else None,
|
|
1002
|
+
tags=tag_protos,
|
|
1000
1003
|
)
|
|
1001
1004
|
assert load_context.client._auth_token_manager
|
|
1002
1005
|
auth_token = await load_context.client._auth_token_manager.get_token()
|
|
@@ -1267,22 +1270,27 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1267
1270
|
Returns:
|
|
1268
1271
|
Tags as a map from tag name to tag value.
|
|
1269
1272
|
"""
|
|
1270
|
-
self._ensure_v1("get_tags")
|
|
1271
1273
|
req = api_pb2.SandboxTagsGetRequest(sandbox_id=self.object_id)
|
|
1272
|
-
|
|
1274
|
+
stub = self._client.stub
|
|
1275
|
+
if self._is_v2:
|
|
1276
|
+
assert self._client._auth_token_manager
|
|
1277
|
+
auth_token = await self._client._auth_token_manager.get_token()
|
|
1278
|
+
resp = await stub.SandboxTagsGetV2(req, metadata=[("x-modal-auth-token", auth_token)])
|
|
1279
|
+
else:
|
|
1280
|
+
resp = await stub.SandboxTagsGet(req)
|
|
1273
1281
|
|
|
1274
1282
|
return {tag.tag_name: tag.tag_value for tag in resp.tags}
|
|
1275
1283
|
|
|
1276
1284
|
async def set_tags(self, tags: dict[str, str], *, client: _Client | None = None) -> None:
|
|
1277
1285
|
"""Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
|
|
1278
1286
|
|
|
1287
|
+
Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
|
|
1288
|
+
|
|
1279
1289
|
Args:
|
|
1280
1290
|
tags: Tag names and values to set on this sandbox.
|
|
1281
1291
|
client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
|
|
1282
1292
|
|
|
1283
1293
|
"""
|
|
1284
|
-
self._ensure_v1("set_tags")
|
|
1285
|
-
environment_name = _get_environment_name()
|
|
1286
1294
|
if client is not None:
|
|
1287
1295
|
deprecation_warning(
|
|
1288
1296
|
(2025, 9, 18),
|
|
@@ -1291,13 +1299,19 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
1291
1299
|
)
|
|
1292
1300
|
|
|
1293
1301
|
tags_list = [api_pb2.SandboxTag(tag_name=name, tag_value=value) for name, value in tags.items()]
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
tags=tags_list
|
|
1299
|
-
|
|
1300
|
-
|
|
1302
|
+
stub = self._client.stub
|
|
1303
|
+
if self._is_v2:
|
|
1304
|
+
assert self._client._auth_token_manager
|
|
1305
|
+
auth_token = await self._client._auth_token_manager.get_token()
|
|
1306
|
+
req = api_pb2.SandboxTagsSetRequest(sandbox_id=self.object_id, tags=tags_list)
|
|
1307
|
+
await stub.SandboxTagsSetV2(req, metadata=[("x-modal-auth-token", auth_token)])
|
|
1308
|
+
else:
|
|
1309
|
+
req = api_pb2.SandboxTagsSetRequest(
|
|
1310
|
+
environment_name=_get_environment_name(),
|
|
1311
|
+
sandbox_id=self.object_id,
|
|
1312
|
+
tags=tags_list,
|
|
1313
|
+
)
|
|
1314
|
+
await stub.SandboxTagsSet(req)
|
|
1301
1315
|
|
|
1302
1316
|
async def _experimental_set_outbound_network_policy(
|
|
1303
1317
|
self,
|
|
@@ -361,6 +361,7 @@ class _Sandbox(modal._object._Object):
|
|
|
361
361
|
*args: str,
|
|
362
362
|
app: typing.Optional[modal.app._App] = None,
|
|
363
363
|
name: typing.Optional[str] = None,
|
|
364
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
364
365
|
image: typing.Optional[modal._image._Image] = None,
|
|
365
366
|
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
366
367
|
secrets: typing.Optional[collections.abc.Collection[modal.secret._Secret]] = None,
|
|
@@ -399,7 +400,7 @@ class _Sandbox(modal._object._Object):
|
|
|
399
400
|
`secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
|
|
400
401
|
filesystem snapshots.
|
|
401
402
|
|
|
402
|
-
Features like
|
|
403
|
+
Features like memory snapshots, network file systems, GPUs, and custom
|
|
403
404
|
domains are not supported.
|
|
404
405
|
|
|
405
406
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
@@ -518,6 +519,8 @@ class _Sandbox(modal._object._Object):
|
|
|
518
519
|
async def set_tags(self, tags: dict[str, str], *, client: typing.Optional[modal.client._Client] = None) -> None:
|
|
519
520
|
"""Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
|
|
520
521
|
|
|
522
|
+
Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
|
|
523
|
+
|
|
521
524
|
Args:
|
|
522
525
|
tags: Tag names and values to set on this sandbox.
|
|
523
526
|
client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
|
|
@@ -1711,6 +1714,7 @@ class Sandbox(modal.object.Object):
|
|
|
1711
1714
|
*args: str,
|
|
1712
1715
|
app: typing.Optional[modal.app.App] = None,
|
|
1713
1716
|
name: typing.Optional[str] = None,
|
|
1717
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
1714
1718
|
image: typing.Optional[modal.image.Image] = None,
|
|
1715
1719
|
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1716
1720
|
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
@@ -1749,7 +1753,7 @@ class Sandbox(modal.object.Object):
|
|
|
1749
1753
|
`secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
|
|
1750
1754
|
filesystem snapshots.
|
|
1751
1755
|
|
|
1752
|
-
Features like
|
|
1756
|
+
Features like memory snapshots, network file systems, GPUs, and custom
|
|
1753
1757
|
domains are not supported.
|
|
1754
1758
|
|
|
1755
1759
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
@@ -1772,6 +1776,7 @@ class Sandbox(modal.object.Object):
|
|
|
1772
1776
|
*args: str,
|
|
1773
1777
|
app: typing.Optional[modal.app.App] = None,
|
|
1774
1778
|
name: typing.Optional[str] = None,
|
|
1779
|
+
tags: typing.Optional[dict[str, str]] = None,
|
|
1775
1780
|
image: typing.Optional[modal.image.Image] = None,
|
|
1776
1781
|
env: typing.Optional[dict[str, typing.Optional[str]]] = None,
|
|
1777
1782
|
secrets: typing.Optional[collections.abc.Collection[modal.secret.Secret]] = None,
|
|
@@ -1810,7 +1815,7 @@ class Sandbox(modal.object.Object):
|
|
|
1810
1815
|
`secret=...` or `oidc_auth_role_arn`), OIDC identity tokens, proxies, and
|
|
1811
1816
|
filesystem snapshots.
|
|
1812
1817
|
|
|
1813
|
-
Features like
|
|
1818
|
+
Features like memory snapshots, network file systems, GPUs, and custom
|
|
1814
1819
|
domains are not supported.
|
|
1815
1820
|
|
|
1816
1821
|
Set `i6pn=True` to enable private IPv6 networking so sandboxes in the same
|
|
@@ -2035,6 +2040,8 @@ class Sandbox(modal.object.Object):
|
|
|
2035
2040
|
def __call__(self, /, tags: dict[str, str], *, client: typing.Optional[modal.client.Client] = None) -> None:
|
|
2036
2041
|
"""Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
|
|
2037
2042
|
|
|
2043
|
+
Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
|
|
2044
|
+
|
|
2038
2045
|
Args:
|
|
2039
2046
|
tags: Tag names and values to set on this sandbox.
|
|
2040
2047
|
client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
|
|
@@ -2044,6 +2051,8 @@ class Sandbox(modal.object.Object):
|
|
|
2044
2051
|
async def aio(self, /, tags: dict[str, str], *, client: typing.Optional[modal.client.Client] = None) -> None:
|
|
2045
2052
|
"""Set tags (key-value pairs) on the Sandbox. Tags can be used to filter results in `Sandbox.list`.
|
|
2046
2053
|
|
|
2054
|
+
Setting tags replaces the Sandbox's entire tag set; passing an empty dict clears all tags.
|
|
2055
|
+
|
|
2047
2056
|
Args:
|
|
2048
2057
|
tags: Tag names and values to set on this sandbox.
|
|
2049
2058
|
client: Deprecated. Prefer setting the client when creating or re-attaching to the sandbox.
|