modal 1.5.6.dev26__tar.gz → 1.5.6.dev27__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.6.dev26 → modal-1.5.6.dev27}/PKG-INFO +1 -1
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/__init__.py +4 -0
- modal-1.5.6.dev27/modal/_cluster.py +192 -0
- modal-1.5.6.dev27/modal/_cluster.pyi +108 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_container_entrypoint.py +3 -11
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_function_variants.py +71 -19
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_functions.py +75 -75
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_image.py +3 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_object.py +0 -31
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_partial_function.py +77 -2
- modal-1.5.6.dev27/modal/_runtime/gpu_memory_snapshot.py +56 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/task_lifecycle_manager.py +16 -28
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/task_lifecycle_manager.pyi +0 -5
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_server.py +189 -2
- modal-1.5.6.dev27/modal/_server.pyi +411 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/function_utils.py +1 -1
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/task_command_router_client.py +12 -3
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_workspace.py +20 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/app.py +17 -47
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/app.pyi +0 -56
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/app.py +8 -4
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/function.py +7 -5
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/server.py +3 -3
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/utils.py +2 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/workspace.py +24 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/client.pyi +2 -2
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cloud_bucket_mount.py +16 -9
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cloud_bucket_mount.pyi +32 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cls.py +12 -32
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cls.pyi +0 -20
- modal-1.5.6.dev27/modal/cluster.py +7 -0
- modal-1.5.6.dev27/modal/cluster.pyi +108 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/config.py +1 -2
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/__init__.py +9 -6
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/_flash_container_entry.py +2 -2
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/functions.pyi +18 -92
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/io_streams.py +164 -73
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/io_streams.pyi +65 -40
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/object.pyi +0 -15
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/partial_function.py +2 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/partial_function.pyi +62 -2
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox.py +2 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/server.pyi +77 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/types.py +239 -105
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/workspace.pyi +37 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/SOURCES.txt +5 -2
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/mdmd.py +24 -3
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2.pyi +1 -1
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_version/__init__.py +1 -1
- modal-1.5.6.dev26/modal/_clustered_functions.py +0 -97
- modal-1.5.6.dev26/modal/_clustered_functions.pyi +0 -38
- modal-1.5.6.dev26/modal/_runtime/gpu_memory_snapshot.py +0 -281
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/LICENSE +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/README.md +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/__main__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_billing.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_environments.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_grpc_client.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_ipython.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_load_context.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_location.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_logs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_logs_manager.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_logs_manager.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_outbound_policy.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_outbound_policy.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/manager.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/pty.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/rich.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_output/status.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_resolver.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_resources.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/asgi.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/execution_context.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_serialization.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_supports_logs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_traceback.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_tunnel.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_tunnel.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_type_manager.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/async_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/blob_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/logger.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_utils/time_utils.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_vendor/version.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/_watcher.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/billing.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/billing.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/README.md +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/builder/base-images.json +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/call_graph.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_download.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_help.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_logs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_stats.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/_traceback.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/billing.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/changelog.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/config.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/container.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/curl.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/dashboard.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/dict.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/endpoint.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/entry_point.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/environment.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/image.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/import_refs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/logo.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/profile.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/queues.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/run.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/secret.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/selector.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/shell.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/skills.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/token.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/cli/volume.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/client.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/container_process.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/container_process.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/dict.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/dict.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/environments.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/environments.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/exception.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash_server.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/experimental/ipython.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/functions.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/image.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/image.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/mount.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/mount.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/network_file_system.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/network_file_system.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/object.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/output.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/parallel_map.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/parallel_map.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/proxy.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/proxy.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/py.typed +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/queue.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/queue.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/retries.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/runner.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/runner.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/running_app.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox_fs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/schedule.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/scheduler_placement.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/secret.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/secret.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/server.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/serving.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/serving.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/snapshot.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/snapshot.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/stream_type.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/token_flow.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/token_flow.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/volume.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/volume.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal/workspace.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/__init__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_grpc.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/py.typed +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/modal_version/__main__.py +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/pyproject.toml +0 -0
- {modal-1.5.6.dev26 → modal-1.5.6.dev27}/setup.cfg +0 -0
|
@@ -16,6 +16,7 @@ try:
|
|
|
16
16
|
from .client import Client
|
|
17
17
|
from .cloud_bucket_mount import CloudBucketMount
|
|
18
18
|
from .cls import Cls, parameter
|
|
19
|
+
from .cluster import Cluster
|
|
19
20
|
from .dict import Dict
|
|
20
21
|
from .environments import Environment
|
|
21
22
|
from .exception import Error
|
|
@@ -33,6 +34,7 @@ try:
|
|
|
33
34
|
exit,
|
|
34
35
|
fastapi_endpoint,
|
|
35
36
|
method,
|
|
37
|
+
sessioned,
|
|
36
38
|
web_endpoint,
|
|
37
39
|
web_server,
|
|
38
40
|
wsgi_app,
|
|
@@ -62,6 +64,7 @@ __all__ = [
|
|
|
62
64
|
"__version__",
|
|
63
65
|
"App",
|
|
64
66
|
"Client",
|
|
67
|
+
"Cluster",
|
|
65
68
|
"Cls",
|
|
66
69
|
"Cron",
|
|
67
70
|
"Dict",
|
|
@@ -103,6 +106,7 @@ __all__ = [
|
|
|
103
106
|
"interact",
|
|
104
107
|
"method",
|
|
105
108
|
"parameter",
|
|
109
|
+
"sessioned",
|
|
106
110
|
"types",
|
|
107
111
|
"web_endpoint",
|
|
108
112
|
"web_server",
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Copyright Modal Labs 2026
|
|
2
|
+
import os
|
|
3
|
+
import socket
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Literal, cast
|
|
6
|
+
|
|
7
|
+
from google.protobuf.message import Message
|
|
8
|
+
|
|
9
|
+
from modal_proto import api_pb2
|
|
10
|
+
|
|
11
|
+
from ._load_context import LoadContext
|
|
12
|
+
from ._object import _Object, live_method
|
|
13
|
+
from ._resolver import Resolver
|
|
14
|
+
from ._utils.async_utils import synchronize_api
|
|
15
|
+
from .client import _Client
|
|
16
|
+
from .exception import InvalidError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ClusterContext:
|
|
21
|
+
"""Cluster metadata and the executing container's rank, initialized at startup."""
|
|
22
|
+
|
|
23
|
+
rank: int
|
|
24
|
+
cluster_id: str
|
|
25
|
+
container_ips: list[str]
|
|
26
|
+
container_ipv4_ips: list[str]
|
|
27
|
+
fabric_ids: list[str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
current_cluster_context: ClusterContext | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_current_cluster_context() -> ClusterContext:
|
|
34
|
+
"""Return the executing container's initialized cluster context without an RPC."""
|
|
35
|
+
if current_cluster_context is None:
|
|
36
|
+
raise InvalidError(
|
|
37
|
+
"Cluster context is not initialized. This operation requires execution "
|
|
38
|
+
"inside a clustered Function or Server."
|
|
39
|
+
)
|
|
40
|
+
return current_cluster_context
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _Cluster(_Object, type_prefix="cu"):
|
|
44
|
+
"""A group of containers scheduled together for a clustered Function or Server.
|
|
45
|
+
|
|
46
|
+
Use `Cluster.from_context()` inside a cluster, or `Cluster.from_id()` to
|
|
47
|
+
inspect a cluster remotely. Containers are ordered by cluster rank.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
_metadata: api_pb2.ClusterStats | None = None
|
|
51
|
+
|
|
52
|
+
def _hydrate_metadata(self, metadata: Message | None):
|
|
53
|
+
if metadata is not None:
|
|
54
|
+
assert isinstance(metadata, api_pb2.ClusterStats)
|
|
55
|
+
self._metadata = metadata
|
|
56
|
+
|
|
57
|
+
def _get_metadata(self) -> api_pb2.ClusterStats:
|
|
58
|
+
assert self._metadata is not None
|
|
59
|
+
return self._metadata
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def from_context() -> "_Cluster":
|
|
63
|
+
"""Reference a Cluster from within one of its containers.
|
|
64
|
+
|
|
65
|
+
Raises `InvalidError` outside an initialized clustered execution.
|
|
66
|
+
"""
|
|
67
|
+
context = get_current_cluster_context()
|
|
68
|
+
if not context.cluster_id:
|
|
69
|
+
raise InvalidError("The executing container does not have an initialized cluster ID.")
|
|
70
|
+
return _Cluster.from_id(context.cluster_id)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def from_id(cluster_id: str, *, client: _Client | None = None) -> "_Cluster":
|
|
74
|
+
"""Reference a Cluster by its ID.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
cluster_id: ID of the cluster.
|
|
78
|
+
client: Modal client to use; defaults to `Client.from_env()` when omitted.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
```python notest
|
|
82
|
+
cluster = modal.Cluster.from_id("cu-123")
|
|
83
|
+
```
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
async def _load(self: _Cluster, resolver: Resolver, context: LoadContext, existing_object_id: str | None):
|
|
87
|
+
response = await context.client._stub.ClusterGet(api_pb2.ClusterGetRequest(cluster_id=cluster_id))
|
|
88
|
+
self._hydrate(cluster_id, context.client, response.cluster)
|
|
89
|
+
|
|
90
|
+
obj = _Cluster._from_loader(
|
|
91
|
+
_load,
|
|
92
|
+
f"modal.Cluster.from_id({cluster_id!r})",
|
|
93
|
+
hydrate_lazily=True,
|
|
94
|
+
load_context_overrides=LoadContext(client=client),
|
|
95
|
+
)
|
|
96
|
+
obj._object_id = cluster_id
|
|
97
|
+
return obj
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def object_id(self) -> str:
|
|
101
|
+
"""The cluster's unique `cu-` object ID."""
|
|
102
|
+
return super().object_id
|
|
103
|
+
|
|
104
|
+
@live_method
|
|
105
|
+
async def container_ids(self) -> list[str]:
|
|
106
|
+
"""Return container IDs ordered by cluster rank."""
|
|
107
|
+
return list(self._get_metadata().task_ids)
|
|
108
|
+
|
|
109
|
+
async def container_ips(self, family: Literal["ipv4", "ipv6"] = "ipv6") -> list[str]:
|
|
110
|
+
"""Return container IP addresses ordered by cluster rank.
|
|
111
|
+
|
|
112
|
+
Returns IPv6 addresses by default; pass `family="ipv4"` for IPv4.
|
|
113
|
+
These addresses are for intra-cluster communication.
|
|
114
|
+
|
|
115
|
+
Must be called from a container in this cluster; otherwise raises `InvalidError`.
|
|
116
|
+
"""
|
|
117
|
+
if family not in ("ipv4", "ipv6"):
|
|
118
|
+
raise InvalidError("family must be 'ipv4' or 'ipv6'.")
|
|
119
|
+
context = get_current_cluster_context()
|
|
120
|
+
if context.cluster_id != self.object_id:
|
|
121
|
+
raise InvalidError("container_ips() must be called from a container in the referenced cluster.")
|
|
122
|
+
return list(context.container_ipv4_ips if family == "ipv4" else context.container_ips)
|
|
123
|
+
|
|
124
|
+
async def container_rank(self, container_id: str | None = None) -> int:
|
|
125
|
+
"""Return a container's rank within this cluster.
|
|
126
|
+
|
|
127
|
+
With no argument, return the executing container's rank without a network
|
|
128
|
+
request. Raises `InvalidError` if it is not a member of this cluster.
|
|
129
|
+
|
|
130
|
+
With an explicit container ID, look up its rank in the cluster's membership.
|
|
131
|
+
Raises `InvalidError` for nonmembers.
|
|
132
|
+
"""
|
|
133
|
+
if container_id is None:
|
|
134
|
+
context = get_current_cluster_context()
|
|
135
|
+
if context.cluster_id != self.object_id:
|
|
136
|
+
raise InvalidError("The executing container is not a member of the referenced cluster.")
|
|
137
|
+
return context.rank
|
|
138
|
+
|
|
139
|
+
container_ids = await self.container_ids()
|
|
140
|
+
try:
|
|
141
|
+
return container_ids.index(container_id)
|
|
142
|
+
except ValueError:
|
|
143
|
+
raise InvalidError(f"Container {container_id!r} is not a member of cluster {self.object_id!r}.") from None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def _initialize_clustered_function(client: _Client, task_id: str):
|
|
147
|
+
global current_cluster_context
|
|
148
|
+
|
|
149
|
+
def get_i6pn() -> str:
|
|
150
|
+
"""Returns the ipv6 address assigned to this container."""
|
|
151
|
+
# An AF_INET6 result always carries a (host, port, flowinfo, scope_id) sockaddr,
|
|
152
|
+
# but getaddrinfo is typed for every address family, some of which lead with an int
|
|
153
|
+
return cast(str, socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0])
|
|
154
|
+
|
|
155
|
+
hostname = socket.gethostname()
|
|
156
|
+
container_ip = get_i6pn()
|
|
157
|
+
|
|
158
|
+
# nccl's default host ID is $(hostname)$(cat /proc/sys/kernel/random/boot_id).
|
|
159
|
+
# on runc, if two i6pn-linked containers get scheduled on the same worker,
|
|
160
|
+
# their boot ID and hostname will both be identical, causing nccl to break.
|
|
161
|
+
# As a workaround, we can explicitly specify a unique host ID here.
|
|
162
|
+
# See MOD-4067.
|
|
163
|
+
os.environ["NCCL_HOSTID"] = f"{hostname}{container_ip}"
|
|
164
|
+
|
|
165
|
+
# We found these settings to work well in most cases. You may be able to achieve
|
|
166
|
+
# better performance by tuning these settings.
|
|
167
|
+
if os.environ["MODAL_CLOUD_PROVIDER"] in ("CLOUD_PROVIDER_GCP", "CLOUD_PROVIDER_OCI"):
|
|
168
|
+
os.environ["NCCL_SOCKET_NTHREADS"] = "4"
|
|
169
|
+
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
|
170
|
+
elif os.environ["MODAL_CLOUD_PROVIDER"] == "CLOUD_PROVIDER_AWS":
|
|
171
|
+
os.environ["NCCL_SOCKET_NTHREADS"] = "2"
|
|
172
|
+
os.environ["NCCL_NSOCKS_PERTHREAD"] = "8"
|
|
173
|
+
else:
|
|
174
|
+
os.environ["NCCL_SOCKET_NTHREADS"] = "1"
|
|
175
|
+
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
|
176
|
+
|
|
177
|
+
resp = await client._stub.TaskClusterHello(
|
|
178
|
+
api_pb2.TaskClusterHelloRequest(
|
|
179
|
+
task_id=task_id,
|
|
180
|
+
container_ip=container_ip,
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
current_cluster_context = ClusterContext(
|
|
184
|
+
rank=resp.cluster_rank,
|
|
185
|
+
cluster_id=resp.cluster_id,
|
|
186
|
+
container_ips=list(resp.container_ips),
|
|
187
|
+
container_ipv4_ips=list(resp.container_ipv4_ips),
|
|
188
|
+
fabric_ids=list(resp.fabric_ids),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
initialize_clustered_function = synchronize_api(_initialize_clustered_function)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import google.protobuf.message
|
|
2
|
+
import modal._object
|
|
3
|
+
import modal.client
|
|
4
|
+
import modal_proto.api_pb2
|
|
5
|
+
import types
|
|
6
|
+
import typing
|
|
7
|
+
import typing_extensions
|
|
8
|
+
|
|
9
|
+
class ClusterContext:
|
|
10
|
+
"""Cluster metadata and the executing container's rank, initialized at startup."""
|
|
11
|
+
|
|
12
|
+
rank: int
|
|
13
|
+
cluster_id: str
|
|
14
|
+
container_ips: list[str]
|
|
15
|
+
container_ipv4_ips: list[str]
|
|
16
|
+
fabric_ids: list[str]
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str]
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
"""Return repr(self)."""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def __eq__(self, other):
|
|
29
|
+
"""Return self==value."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
def get_current_cluster_context() -> ClusterContext:
|
|
33
|
+
"""Return the executing container's initialized cluster context without an RPC."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
class _Cluster(modal._object._Object):
|
|
37
|
+
"""A group of containers scheduled together for a clustered Function or Server.
|
|
38
|
+
|
|
39
|
+
Use `Cluster.from_context()` inside a cluster, or `Cluster.from_id()` to
|
|
40
|
+
inspect a cluster remotely. Containers are ordered by cluster rank.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_metadata: typing.Optional[modal_proto.api_pb2.ClusterStats]
|
|
44
|
+
|
|
45
|
+
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
46
|
+
def _get_metadata(self) -> modal_proto.api_pb2.ClusterStats: ...
|
|
47
|
+
@staticmethod
|
|
48
|
+
def from_context() -> _Cluster:
|
|
49
|
+
"""Reference a Cluster from within one of its containers.
|
|
50
|
+
|
|
51
|
+
Raises `InvalidError` outside an initialized clustered execution.
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def from_id(cluster_id: str, *, client: typing.Optional[modal.client._Client] = None) -> _Cluster:
|
|
57
|
+
"""Reference a Cluster by its ID.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
cluster_id: ID of the cluster.
|
|
61
|
+
client: Modal client to use; defaults to `Client.from_env()` when omitted.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
```python notest
|
|
65
|
+
cluster = modal.Cluster.from_id("cu-123")
|
|
66
|
+
```
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def object_id(self) -> str:
|
|
72
|
+
"""The cluster's unique `cu-` object ID."""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
async def container_ids(self) -> list[str]:
|
|
76
|
+
"""Return container IDs ordered by cluster rank."""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
async def container_ips(self, family: typing.Literal["ipv4", "ipv6"] = "ipv6") -> list[str]:
|
|
80
|
+
"""Return container IP addresses ordered by cluster rank.
|
|
81
|
+
|
|
82
|
+
Returns IPv6 addresses by default; pass `family="ipv4"` for IPv4.
|
|
83
|
+
These addresses are for intra-cluster communication.
|
|
84
|
+
|
|
85
|
+
Must be called from a container in this cluster; otherwise raises `InvalidError`.
|
|
86
|
+
"""
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
async def container_rank(self, container_id: typing.Optional[str] = None) -> int:
|
|
90
|
+
"""Return a container's rank within this cluster.
|
|
91
|
+
|
|
92
|
+
With no argument, return the executing container's rank without a network
|
|
93
|
+
request. Raises `InvalidError` if it is not a member of this cluster.
|
|
94
|
+
|
|
95
|
+
With an explicit container ID, look up its rank in the cluster's membership.
|
|
96
|
+
Raises `InvalidError` for nonmembers.
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
async def _initialize_clustered_function(client: modal.client._Client, task_id: str): ...
|
|
101
|
+
|
|
102
|
+
class __initialize_clustered_function_spec(typing_extensions.Protocol):
|
|
103
|
+
def __call__(self, /, client: modal.client.Client, task_id: str): ...
|
|
104
|
+
async def aio(self, /, client: modal.client.Client, task_id: str): ...
|
|
105
|
+
|
|
106
|
+
initialize_clustered_function: __initialize_clustered_function_spec
|
|
107
|
+
|
|
108
|
+
current_cluster_context: ClusterContext | None
|
|
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
24
24
|
|
|
25
25
|
from google.protobuf.message import Message
|
|
26
26
|
|
|
27
|
-
from modal.
|
|
27
|
+
from modal._cluster import initialize_clustered_function
|
|
28
28
|
from modal._runtime.user_code_event_loop import UserCodeEventLoop
|
|
29
29
|
from modal._serialization import deserialize, deserialize_params
|
|
30
30
|
from modal._utils.async_utils import TaskContext, aclosing, synchronizer
|
|
@@ -407,11 +407,7 @@ def run_server(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
407
407
|
service: Service = hydrate_function(container_args, task_lifecycle_manager, function_def, _client)
|
|
408
408
|
# Initialize clustered functions.
|
|
409
409
|
if function_def._experimental_group_size > 0:
|
|
410
|
-
initialize_clustered_function(
|
|
411
|
-
client,
|
|
412
|
-
container_args.task_id,
|
|
413
|
-
function_def._experimental_group_size,
|
|
414
|
-
)
|
|
410
|
+
initialize_clustered_function(client, container_args.task_id)
|
|
415
411
|
|
|
416
412
|
with UserCodeEventLoop() as event_loop:
|
|
417
413
|
with service.lifecycle_context(
|
|
@@ -452,11 +448,7 @@ def run_function(container_args: api_pb2.ContainerArguments, client: Client):
|
|
|
452
448
|
|
|
453
449
|
# Initialize clustered functions.
|
|
454
450
|
if function_def._experimental_group_size > 0:
|
|
455
|
-
initialize_clustered_function(
|
|
456
|
-
client,
|
|
457
|
-
container_args.task_id,
|
|
458
|
-
function_def._experimental_group_size,
|
|
459
|
-
)
|
|
451
|
+
initialize_clustered_function(client, container_args.task_id)
|
|
460
452
|
|
|
461
453
|
with service.function_execution_context(
|
|
462
454
|
event_loop=event_loop,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Copyright Modal Labs 2026
|
|
2
|
+
import copy
|
|
2
3
|
import dataclasses
|
|
3
4
|
from collections.abc import Collection, Sequence, Sized
|
|
4
5
|
from pathlib import PurePosixPath
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
6
7
|
|
|
7
8
|
import google.protobuf.message
|
|
8
9
|
|
|
9
|
-
from modal.types import CloudBucketMountInfo, FunctionInfo, VolumeMountInfo
|
|
10
10
|
from modal_proto import api_pb2
|
|
11
11
|
|
|
12
12
|
from ._resources import convert_fn_config_to_resources_config
|
|
@@ -23,6 +23,7 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
|
23
23
|
from .exception import InvalidError
|
|
24
24
|
from .retries import Retries
|
|
25
25
|
from .secret import _Secret
|
|
26
|
+
from .types import CloudBucketMountInfo, FunctionInfo, VolumeMountInfo
|
|
26
27
|
from .volume import _Volume, _volume_to_mount_proto
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
@@ -441,7 +442,7 @@ def _make_function_variant(
|
|
|
441
442
|
if options is not None:
|
|
442
443
|
function_variant._function_info = _get_function_info_with_options(base_function._function_info, options)
|
|
443
444
|
else:
|
|
444
|
-
function_variant._function_info = base_function._function_info
|
|
445
|
+
function_variant._function_info = copy.deepcopy(base_function._function_info)
|
|
445
446
|
|
|
446
447
|
def _deps():
|
|
447
448
|
if options:
|
|
@@ -464,44 +465,67 @@ def _make_function_variant(
|
|
|
464
465
|
if options is not None:
|
|
465
466
|
fun._function_info = _get_function_info_with_options(base_function._function_info, options)
|
|
466
467
|
else:
|
|
467
|
-
fun._function_info = base_function._function_info
|
|
468
|
+
fun._function_info = copy.deepcopy(base_function._function_info)
|
|
468
469
|
|
|
469
470
|
return fun
|
|
470
471
|
|
|
471
472
|
|
|
473
|
+
class _FunctionInfoArgsT(TypedDict, total=False):
|
|
474
|
+
cpu: float | tuple[float, float]
|
|
475
|
+
memory_mib: int | tuple[int, int]
|
|
476
|
+
gpus: list[tuple[str, int]]
|
|
477
|
+
ephemeral_disk_mib: int
|
|
478
|
+
timeout: int
|
|
479
|
+
max_retries: int
|
|
480
|
+
regions: list[str]
|
|
481
|
+
cloud: str
|
|
482
|
+
volumes: dict[str, VolumeMountInfo]
|
|
483
|
+
cloud_bucket_mounts: dict[str, CloudBucketMountInfo]
|
|
484
|
+
secrets: list[str]
|
|
485
|
+
routing_region: str
|
|
486
|
+
batching_info: FunctionInfo.BatchingInfo
|
|
487
|
+
concurrency_info: FunctionInfo.ConcurrencyInfo
|
|
488
|
+
|
|
489
|
+
|
|
472
490
|
def _get_function_info_with_options(info: FunctionInfo, options: _FunctionOptions):
|
|
473
|
-
|
|
491
|
+
args: _FunctionInfoArgsT = {}
|
|
474
492
|
|
|
475
493
|
if options.resources is not None:
|
|
476
494
|
res = options.resources
|
|
477
495
|
if res.milli_cpu_max > 0:
|
|
478
|
-
|
|
496
|
+
args["cpu"] = (res.milli_cpu / 1000, res.milli_cpu_max / 1000)
|
|
479
497
|
elif res.milli_cpu > 0:
|
|
480
|
-
|
|
498
|
+
args["cpu"] = res.milli_cpu / 1000
|
|
481
499
|
|
|
482
500
|
if res.memory_mb_max > 0:
|
|
483
|
-
|
|
501
|
+
args["memory_mib"] = (res.memory_mb, res.memory_mb_max)
|
|
484
502
|
elif res.memory_mb > 0:
|
|
485
|
-
|
|
503
|
+
args["memory_mib"] = res.memory_mb
|
|
486
504
|
|
|
487
505
|
if res.HasField("gpu_config") and res.gpu_config.count > 0:
|
|
488
|
-
|
|
506
|
+
args["gpus"] = [(res.gpu_config.gpu_type, res.gpu_config.count)]
|
|
489
507
|
|
|
490
508
|
if res.ephemeral_disk_mb > 0:
|
|
491
|
-
|
|
509
|
+
args["ephemeral_disk_mib"] = res.ephemeral_disk_mb
|
|
510
|
+
|
|
511
|
+
if options.timeout_secs is not None:
|
|
512
|
+
args["timeout"] = options.timeout_secs
|
|
513
|
+
|
|
514
|
+
if options.retry_policy is not None:
|
|
515
|
+
args["max_retries"] = options.retry_policy.retries
|
|
492
516
|
|
|
493
517
|
if options.scheduler_placement is not None:
|
|
494
518
|
sp = options.scheduler_placement
|
|
495
519
|
# Note: not putting `nonpreemptible` here as that is not currently possible to set via
|
|
496
520
|
# `.with_options(...)`
|
|
497
521
|
if len(sp.regions) > 0:
|
|
498
|
-
|
|
522
|
+
args["regions"] = list(sp.regions)
|
|
499
523
|
|
|
500
524
|
if options.cloud is not None:
|
|
501
|
-
|
|
525
|
+
args["cloud"] = options.cloud
|
|
502
526
|
|
|
503
527
|
if len(options.validated_volumes) > 0:
|
|
504
|
-
|
|
528
|
+
args["volumes"] = {
|
|
505
529
|
mount_path: VolumeMountInfo(vol._name, vol._object_id, False, None)
|
|
506
530
|
if vol._mount_options is None
|
|
507
531
|
else VolumeMountInfo(
|
|
@@ -514,16 +538,44 @@ def _get_function_info_with_options(info: FunctionInfo, options: _FunctionOption
|
|
|
514
538
|
}
|
|
515
539
|
|
|
516
540
|
if len(options.cloud_bucket_mounts) > 0:
|
|
517
|
-
|
|
541
|
+
args["cloud_bucket_mounts"] = {}
|
|
518
542
|
protos, _ = cloud_bucket_mounts_to_proto(options.cloud_bucket_mounts, include_secrets=False)
|
|
519
543
|
|
|
520
544
|
for proto in protos:
|
|
521
|
-
|
|
545
|
+
args["cloud_bucket_mounts"][proto.mount_path] = CloudBucketMountInfo._from_proto(proto)
|
|
522
546
|
|
|
523
547
|
if len(options.secrets) > 0:
|
|
524
|
-
|
|
548
|
+
args["secrets"] = [repr(s) for s in options.secrets]
|
|
525
549
|
|
|
526
550
|
if options.routing_region:
|
|
527
|
-
|
|
551
|
+
args["routing_region"] = options.routing_region
|
|
552
|
+
|
|
553
|
+
if options.batch_max_size is not None and options.batch_wait_ms is not None:
|
|
554
|
+
args["batching_info"] = FunctionInfo.BatchingInfo(
|
|
555
|
+
max_batch_size=options.batch_max_size,
|
|
556
|
+
wait_ms=options.batch_wait_ms,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
if options.max_concurrent_inputs is not None or options.target_concurrent_inputs is not None:
|
|
560
|
+
max_inputs = (
|
|
561
|
+
options.max_concurrent_inputs
|
|
562
|
+
if options.max_concurrent_inputs is not None
|
|
563
|
+
else info.concurrency_info.max_inputs
|
|
564
|
+
if info.concurrency_info is not None
|
|
565
|
+
else None
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
target_inputs = (
|
|
569
|
+
options.target_concurrent_inputs
|
|
570
|
+
if options.target_concurrent_inputs is not None
|
|
571
|
+
else info.concurrency_info.target_inputs
|
|
572
|
+
if info.concurrency_info is not None
|
|
573
|
+
else None
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
args["concurrency_info"] = FunctionInfo.ConcurrencyInfo(
|
|
577
|
+
max_inputs=max_inputs,
|
|
578
|
+
target_inputs=target_inputs,
|
|
579
|
+
)
|
|
528
580
|
|
|
529
|
-
return
|
|
581
|
+
return dataclasses.replace(copy.deepcopy(info), **args)
|