modal 1.6.2.dev9__tar.gz → 1.6.2.dev11__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.6.2.dev9 → modal-1.6.2.dev11}/PKG-INFO +4 -3
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/README.md +1 -1
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/__init__.py +2 -2
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_functions.py +140 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_image.py +2 -2
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_server.py +179 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_server.pyi +83 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/async_utils.py +6 -2
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_workspace.py +14 -11
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/2025.06.txt +8 -4
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/PREVIEW.txt +4 -2
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/base-images.json +4 -2
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/function.py +1 -1
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/server.py +2 -2
- modal-1.6.2.dev11/modal/cli/workspace.py +641 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/client.pyi +2 -2
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cls.py +1 -1
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cls.pyi +1 -1
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/functions.py +2 -1
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/functions.pyi +155 -6
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/mount.py +2 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/mount.pyi +3 -3
- modal-1.6.2.dev11/modal/server.py +6 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/server.pyi +164 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/workspace.pyi +6 -18
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal.egg-info/PKG-INFO +4 -3
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal.egg-info/requires.txt +3 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/api_grpc.py +16 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/api_pb2.py +809 -799
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/api_pb2.pyi +15 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_version/__init__.py +1 -1
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/pyproject.toml +3 -1
- modal-1.6.2.dev9/modal/cli/workspace.py +0 -269
- modal-1.6.2.dev9/modal/server.py +0 -5
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/LICENSE +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/__main__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_billing.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_cluster.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_cluster.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_container_entrypoint.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_environments.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_function_variants.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_grpc_client.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_ipython.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_load_context.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_location.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_logs.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_logs_manager.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_logs_manager.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_metrics.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_metrics.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_object.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_outbound_policy.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_outbound_policy.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_output/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_output/manager.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_output/pty.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_output/rich.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_output/status.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_partial_function.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_resolver.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_resources.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/asgi.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/execution_context.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/telemetry.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_serialization.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_supports_logs.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_traceback.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_tunnel.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_tunnel.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_type_manager.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/app_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/blob_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/browser_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/curl_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/deprecation.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/display_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/docker_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/function_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/git_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/hash_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/http_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/idle_countdown.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/logger.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/mount_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/name_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/package_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/proxy_support.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/shell_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_utils/time_utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_vendor/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_vendor/tblib.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_vendor/version.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/_watcher.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/app.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/app.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/billing.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/billing.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/2023.12.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/2024.04.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/2024.10.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/builder/README.md +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/call_graph.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/_download.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/_help.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/_logs.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/_stats.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/_traceback.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/app.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/billing.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/changelog.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/config.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/container.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/curl.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/dashboard.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/dict.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/endpoint.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/entry_point.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/environment.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/image.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/import_refs.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/logo.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/network_file_system.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/profile.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/queues.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/run.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/secret.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/selector.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/shell.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/skills.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/token.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/utils.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cli/volume.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/client.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cluster.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/cluster.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/config.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/container_process.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/container_process.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/dict.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/dict.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/environments.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/environments.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/exception.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/_flash_container_entry.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/flash.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/flash.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/flash_server.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/flash_server.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/experimental/ipython.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/file_pattern_matcher.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/image.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/image.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/io_streams.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/io_streams.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/network_file_system.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/network_file_system.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/object.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/object.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/output.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/parallel_map.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/parallel_map.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/partial_function.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/partial_function.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/proxy.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/proxy.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/py.typed +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/queue.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/queue.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/retries.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/runner.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/runner.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/running_app.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/_common.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/_filesystem.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/_sandbox.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/_sidecar.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/_snapshot.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/_task_command_router_client.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/sandbox.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/sandbox/sandbox.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/schedule.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/scheduler_placement.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/secret.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/secret.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/serving.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/serving.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/stream_type.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/token_flow.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/token_flow.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/types.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/volume.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/volume.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal/workspace.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal.egg-info/top_level.txt +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/mdmd/types.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_docs/reference_intro.md +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/__init__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/py.typed +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/modal_version/__main__.py +0 -0
- {modal-1.6.2.dev9 → modal-1.6.2.dev11}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.6.2.
|
|
3
|
+
Version: 1.6.2.dev11
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -12,7 +12,7 @@ Keywords: modal,client,cloud,serverless,infrastructure
|
|
|
12
12
|
Classifier: Topic :: System :: Distributed Computing
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Requires-Python: <3.
|
|
15
|
+
Requires-Python: <3.16,>=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: aiohttp
|
|
@@ -29,6 +29,7 @@ Requires-Dist: types-certifi
|
|
|
29
29
|
Requires-Dist: types-toml
|
|
30
30
|
Requires-Dist: watchfiles
|
|
31
31
|
Requires-Dist: typing_extensions~=4.6
|
|
32
|
+
Requires-Dist: typing_extensions>=4.14; python_version >= "3.15"
|
|
32
33
|
Provides-Extra: api-proxy-support
|
|
33
34
|
Requires-Dist: python-socks[asyncio]>=2.5; extra == "api-proxy-support"
|
|
34
35
|
Requires-Dist: aiohttp-socks>=0.8; extra == "api-proxy-support"
|
|
@@ -52,7 +53,7 @@ a [user guide](https://modal.com/docs/guide), and the detailed
|
|
|
52
53
|
|
|
53
54
|
## Installation
|
|
54
55
|
|
|
55
|
-
**This library requires Python 3.10 – 3.
|
|
56
|
+
**This library requires Python 3.10 – 3.15.**
|
|
56
57
|
|
|
57
58
|
Install the package with `uv` or `pip`:
|
|
58
59
|
|
|
@@ -3,8 +3,8 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
if sys.version_info[:2] < (3, 10):
|
|
5
5
|
raise RuntimeError("This version of Modal requires at least Python 3.10")
|
|
6
|
-
if sys.version_info[:2] >= (3,
|
|
7
|
-
raise RuntimeError("This version of Modal does not support Python 3.
|
|
6
|
+
if sys.version_info[:2] >= (3, 16):
|
|
7
|
+
raise RuntimeError("This version of Modal does not support Python 3.16+")
|
|
8
8
|
|
|
9
9
|
from modal_version import __version__
|
|
10
10
|
|
|
@@ -51,6 +51,7 @@ from ._utils.async_utils import (
|
|
|
51
51
|
)
|
|
52
52
|
from ._utils.auth_token_manager import _AuthTokenManager
|
|
53
53
|
from ._utils.blob_utils import MAX_ASYNC_OBJECT_SIZE_BYTES, MAX_OBJECT_SIZE_BYTES
|
|
54
|
+
from ._utils.deprecation import deprecation_warning
|
|
54
55
|
from ._utils.function_utils import (
|
|
55
56
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
56
57
|
OUTPUTS_TIMEOUT,
|
|
@@ -1396,6 +1397,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1396
1397
|
```
|
|
1397
1398
|
|
|
1398
1399
|
"""
|
|
1400
|
+
|
|
1401
|
+
deprecation_warning(
|
|
1402
|
+
(2026, 10, 16),
|
|
1403
|
+
"`Function.update_autoscaler()` is deprecated and will be removed in a future version "
|
|
1404
|
+
"of `modal`. Use `Function.autoscaler.update()` instead.",
|
|
1405
|
+
)
|
|
1406
|
+
|
|
1399
1407
|
# Assert .update_autoscaler() is not called on a method as opposed to the Object. Applicable for Cls only.
|
|
1400
1408
|
if self._is_method:
|
|
1401
1409
|
raise InvalidError("Cannot call .update_autoscaler() on a method. Call it on the class instance instead.")
|
|
@@ -2312,6 +2320,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
2312
2320
|
assert self._metadata
|
|
2313
2321
|
return self._metadata.function_schema
|
|
2314
2322
|
|
|
2323
|
+
@property
|
|
2324
|
+
def autoscaler(self) -> "_FunctionAutoscalerManager":
|
|
2325
|
+
"""Namespace for Function autoscaler APIs."""
|
|
2326
|
+
return _FunctionAutoscalerManager(self)
|
|
2327
|
+
|
|
2315
2328
|
# A bit hacky - but the map-style functions need to not be synchronicity-wrapped
|
|
2316
2329
|
# in order to not execute their input iterators on the synchronicity event loop.
|
|
2317
2330
|
# We still need to wrap them using MethodWithAio to maintain a synchronicity-like
|
|
@@ -2565,3 +2578,130 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2565
2578
|
except Exception as exc:
|
|
2566
2579
|
# TODO: kill all running function calls
|
|
2567
2580
|
raise exc
|
|
2581
|
+
|
|
2582
|
+
|
|
2583
|
+
UNSET_PARAMETER = -1
|
|
2584
|
+
|
|
2585
|
+
|
|
2586
|
+
class _FunctionAutoscalerManager:
|
|
2587
|
+
"""mdmd:namespace"""
|
|
2588
|
+
|
|
2589
|
+
def __init__(self, function: _Function):
|
|
2590
|
+
"""mdmd:hidden"""
|
|
2591
|
+
self._function = function
|
|
2592
|
+
|
|
2593
|
+
async def settings(self) -> FunctionAutoscalerSettings:
|
|
2594
|
+
"""View the current Autoscaler settings for this Function.
|
|
2595
|
+
|
|
2596
|
+
Returns:
|
|
2597
|
+
A `FunctionAutoscalerSettings` dataclass.
|
|
2598
|
+
|
|
2599
|
+
Examples:
|
|
2600
|
+
|
|
2601
|
+
```python notest
|
|
2602
|
+
fn = modal.Function.from_name("my-app", "my_function")
|
|
2603
|
+
settings = fn.autoscaler.settings()
|
|
2604
|
+
```
|
|
2605
|
+
"""
|
|
2606
|
+
|
|
2607
|
+
if not self._function._is_hydrated:
|
|
2608
|
+
await self._function.hydrate()
|
|
2609
|
+
|
|
2610
|
+
response = await self._function.client._stub.FunctionGetSchedulingParams(
|
|
2611
|
+
api_pb2.FunctionGetSchedulingParamsRequest(function_id=self._function.object_id)
|
|
2612
|
+
)
|
|
2613
|
+
|
|
2614
|
+
ret = FunctionAutoscalerSettings._from_proto(response.autoscaler_configuration.settings)
|
|
2615
|
+
if ret.max_containers == 0:
|
|
2616
|
+
ret = dataclasses.replace(ret, max_containers=None)
|
|
2617
|
+
|
|
2618
|
+
return ret
|
|
2619
|
+
|
|
2620
|
+
async def update(
|
|
2621
|
+
self,
|
|
2622
|
+
*,
|
|
2623
|
+
min_containers: int | None = UNSET_PARAMETER,
|
|
2624
|
+
max_containers: int | None = UNSET_PARAMETER,
|
|
2625
|
+
buffer_containers: int | None = UNSET_PARAMETER,
|
|
2626
|
+
scaledown_window: int = UNSET_PARAMETER,
|
|
2627
|
+
) -> FunctionAutoscalerSettings:
|
|
2628
|
+
"""Override the current Autoscaler settings for this Function.
|
|
2629
|
+
|
|
2630
|
+
All parameters to this function are optional; not passing an argument for a given parameter
|
|
2631
|
+
will not update its value.
|
|
2632
|
+
|
|
2633
|
+
Note that re-deploying the App containing this Function will wipe any overrides and revert
|
|
2634
|
+
its Autoscaler settings back to those defined on the Function at creation time.
|
|
2635
|
+
|
|
2636
|
+
Args:
|
|
2637
|
+
min_containers: Minimum number of containers to keep running. Must be a nonnegative
|
|
2638
|
+
integer, or `None`. Passing `None` is equivalent to setting the minimum to 0.
|
|
2639
|
+
max_containers: Maximum concurrent containers. Must be a positive integer, or `None`.
|
|
2640
|
+
Passing `None` is equivalent to disabling the upper bound on container concurrency at
|
|
2641
|
+
the Function level (note that Environment and Workspace level container upper limits
|
|
2642
|
+
still apply.)
|
|
2643
|
+
buffer_containers: Extra containers to keep warm beyond current demand. Must be a
|
|
2644
|
+
nonnegative integer, or `None`. Passing `None` is equivalent to setting the buffer to
|
|
2645
|
+
0.
|
|
2646
|
+
scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
|
|
2647
|
+
Must be a positive integer between 2 and 3600, inclusive.
|
|
2648
|
+
|
|
2649
|
+
Returns:
|
|
2650
|
+
A `FunctionAutoscalerSettings` dataclass.
|
|
2651
|
+
|
|
2652
|
+
Examples:
|
|
2653
|
+
|
|
2654
|
+
Update `my_function` so that 2 containers are always active.
|
|
2655
|
+
|
|
2656
|
+
```python notest
|
|
2657
|
+
fn = modal.Function.from_name("my-app", "my_function")
|
|
2658
|
+
fn.autoscaler.update(min_containers=2)
|
|
2659
|
+
```
|
|
2660
|
+
|
|
2661
|
+
Update `my_function` so that there is no Function-level limit on `max_containers`:
|
|
2662
|
+
|
|
2663
|
+
```python notest
|
|
2664
|
+
fn = modal.Function.from_name("my-app", "my_function")
|
|
2665
|
+
fn.autoscaler.update(max_containers=None)
|
|
2666
|
+
```
|
|
2667
|
+
"""
|
|
2668
|
+
|
|
2669
|
+
if not self._function._is_hydrated:
|
|
2670
|
+
await self._function.hydrate()
|
|
2671
|
+
|
|
2672
|
+
settings: api_pb2.AutoscalerSettings = api_pb2.AutoscalerSettings()
|
|
2673
|
+
|
|
2674
|
+
if min_containers != UNSET_PARAMETER:
|
|
2675
|
+
min_containers = min_containers if min_containers is not None else 0
|
|
2676
|
+
if min_containers < 0:
|
|
2677
|
+
raise InvalidError("Invalid argument: `min_containers` must be a nonnegative integer or `None`.")
|
|
2678
|
+
|
|
2679
|
+
settings.min_containers = min_containers
|
|
2680
|
+
|
|
2681
|
+
if buffer_containers != UNSET_PARAMETER:
|
|
2682
|
+
buffer_containers = buffer_containers if buffer_containers is not None else 0
|
|
2683
|
+
if buffer_containers < 0:
|
|
2684
|
+
raise InvalidError("Invalid argument: `buffer_containers` must be a nonnegative integer or `None`.")
|
|
2685
|
+
|
|
2686
|
+
settings.buffer_containers = buffer_containers
|
|
2687
|
+
|
|
2688
|
+
if max_containers != UNSET_PARAMETER:
|
|
2689
|
+
if max_containers is not None and max_containers <= 0:
|
|
2690
|
+
raise InvalidError("Invalid argument: `max_containers` must be a postive integer or `None`.")
|
|
2691
|
+
|
|
2692
|
+
settings.max_containers = max_containers if max_containers is not None else 0
|
|
2693
|
+
|
|
2694
|
+
if scaledown_window != UNSET_PARAMETER:
|
|
2695
|
+
if not (2 <= scaledown_window <= 3600):
|
|
2696
|
+
raise InvalidError("Invalid argument: `scaledown_window` must be between 2 and 3600 (inclusive)")
|
|
2697
|
+
|
|
2698
|
+
settings.scaledown_window = scaledown_window
|
|
2699
|
+
|
|
2700
|
+
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self._function.object_id, settings=settings)
|
|
2701
|
+
response = await self._function.client._stub.FunctionUpdateSchedulingParams(request)
|
|
2702
|
+
|
|
2703
|
+
ret = FunctionAutoscalerSettings._from_proto(response.current_settings)
|
|
2704
|
+
if ret.max_containers == 0:
|
|
2705
|
+
ret = dataclasses.replace(ret, max_containers=None)
|
|
2706
|
+
|
|
2707
|
+
return ret
|
|
@@ -76,8 +76,8 @@ ImageBuilderVersion = Literal["2023.12", "2024.04", "2024.10", "2025.06", "PREVI
|
|
|
76
76
|
# Python versions in mount.py where we specify the "standalone Python versions" we create mounts for.
|
|
77
77
|
# Consider consolidating these multiple sources of truth?
|
|
78
78
|
SUPPORTED_PYTHON_SERIES: dict[ImageBuilderVersion, list[str]] = {
|
|
79
|
-
"PREVIEW": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"],
|
|
80
|
-
"2025.06": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"],
|
|
79
|
+
"PREVIEW": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"],
|
|
80
|
+
"2025.06": ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"],
|
|
81
81
|
"2024.10": ["3.10", "3.11", "3.12", "3.13"],
|
|
82
82
|
"2024.04": ["3.10", "3.11", "3.12"],
|
|
83
83
|
"2023.12": ["3.10", "3.11", "3.12"],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
import asyncio
|
|
3
|
+
import dataclasses
|
|
3
4
|
import inspect
|
|
4
5
|
import json
|
|
5
6
|
import time
|
|
@@ -23,6 +24,8 @@ from ._partial_function import (
|
|
|
23
24
|
)
|
|
24
25
|
from ._supports_logs import _LogQueryData
|
|
25
26
|
from ._utils.async_utils import retry, synchronize_api
|
|
27
|
+
from ._utils.deprecation import deprecation_warning
|
|
28
|
+
from ._utils.function_utils import normalize_fractional_target_concurrency, validate_target_concurrency
|
|
26
29
|
from ._utils.http_utils import ClientSessionRegistry
|
|
27
30
|
from .client import _Client
|
|
28
31
|
from .cls import is_parameter
|
|
@@ -125,6 +128,11 @@ class _Server:
|
|
|
125
128
|
"""Start and terminate sticky sessions on a Server decorated with `@modal.sessioned()`."""
|
|
126
129
|
return _ServerSessionsManager(self)
|
|
127
130
|
|
|
131
|
+
@property
|
|
132
|
+
def autoscaler(self) -> "_ServerAutoscalerManager":
|
|
133
|
+
"""View and update Autoscaler settings on a Server."""
|
|
134
|
+
return _ServerAutoscalerManager(self)
|
|
135
|
+
|
|
128
136
|
async def info(self, *, refresh: bool = False) -> ServerInfo:
|
|
129
137
|
"""Get an overview of a Server's resource requests, associated mounts, http config, etc.
|
|
130
138
|
|
|
@@ -238,6 +246,13 @@ class _Server:
|
|
|
238
246
|
```
|
|
239
247
|
|
|
240
248
|
"""
|
|
249
|
+
|
|
250
|
+
deprecation_warning(
|
|
251
|
+
(2026, 10, 16),
|
|
252
|
+
"`Server.update_autoscaler()` is deprecated and will be removed in a future version "
|
|
253
|
+
"of `modal`. Use `Server.autoscaler.update()` instead.",
|
|
254
|
+
)
|
|
255
|
+
|
|
241
256
|
return await self._get_service_function()._update_autoscaler_server(
|
|
242
257
|
min_containers=min_containers,
|
|
243
258
|
max_containers=max_containers,
|
|
@@ -618,3 +633,167 @@ class _ServerSessionsManager:
|
|
|
618
633
|
|
|
619
634
|
|
|
620
635
|
ServerSessionsManager = synchronize_api(_ServerSessionsManager, target_module=__name__)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
UNSET_PARAMETER = -1
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class _ServerAutoscalerManager:
|
|
642
|
+
def __init__(self, server: _Server):
|
|
643
|
+
"""mdmd:hidden"""
|
|
644
|
+
self._server = server
|
|
645
|
+
|
|
646
|
+
async def settings(self) -> ServerAutoscalerSettings:
|
|
647
|
+
"""View the current Autoscaler settings for this Server.
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
A `ServerAutoscalerSettings` dataclass.
|
|
651
|
+
|
|
652
|
+
Examples:
|
|
653
|
+
|
|
654
|
+
```python notest
|
|
655
|
+
server = modal.Server.from_name("my-app", "MyServer")
|
|
656
|
+
settings = server.autoscaler.settings()
|
|
657
|
+
```
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
await self._server.hydrate()
|
|
661
|
+
|
|
662
|
+
service_fn = self._server._get_service_function()
|
|
663
|
+
response = await service_fn.client._stub.FunctionGetSchedulingParams(
|
|
664
|
+
api_pb2.FunctionGetSchedulingParamsRequest(function_id=service_fn.object_id)
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
ret = ServerAutoscalerSettings._from_proto(response.autoscaler_configuration.settings)
|
|
668
|
+
|
|
669
|
+
updates = {}
|
|
670
|
+
if ret.target_concurrency == 0.0:
|
|
671
|
+
updates["target_concurrency"] = None
|
|
672
|
+
if ret.max_containers == 0:
|
|
673
|
+
updates["max_containers"] = None
|
|
674
|
+
|
|
675
|
+
if updates:
|
|
676
|
+
ret = dataclasses.replace(ret, **updates)
|
|
677
|
+
|
|
678
|
+
return ret
|
|
679
|
+
|
|
680
|
+
async def update(
|
|
681
|
+
self,
|
|
682
|
+
*,
|
|
683
|
+
target_concurrency: float | None = UNSET_PARAMETER,
|
|
684
|
+
min_containers: int | None = UNSET_PARAMETER,
|
|
685
|
+
max_containers: int | None = UNSET_PARAMETER,
|
|
686
|
+
buffer_containers: int | None = UNSET_PARAMETER,
|
|
687
|
+
scaledown_window: int = UNSET_PARAMETER,
|
|
688
|
+
scaleup_window: int = UNSET_PARAMETER,
|
|
689
|
+
) -> ServerAutoscalerSettings:
|
|
690
|
+
"""Override the current Autoscaler settings for this Server.
|
|
691
|
+
|
|
692
|
+
All parameters to this function are optional; not passing an argument for a given parameter
|
|
693
|
+
will not update its value.
|
|
694
|
+
|
|
695
|
+
Note that re-deploying the App containing this Server will wipe any overrides and revert
|
|
696
|
+
its Autoscaler settings back to those defined on the Server at creation time.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
target_concurrency: Target number of concurrent requests per container. Must either be a
|
|
700
|
+
positive float (e.g. 1.5 to target 3 requests for every 2 containers), or `None`.
|
|
701
|
+
Passing `None` will disable autoscaling of this Server (effectively setting target
|
|
702
|
+
concurrency to infinity).
|
|
703
|
+
min_containers: Minimum number of containers to keep running. Must be a nonnegative
|
|
704
|
+
integer, or `None`. Passing `None` is equivalent to setting the minimum to 0.
|
|
705
|
+
max_containers: Maximum concurrent containers. Must be a positive integer, or `None`.
|
|
706
|
+
Passing `None` is equivalent to disabling the upper bound on container concurrency at
|
|
707
|
+
the Server level (note that Environment and Workspace level container upper limits
|
|
708
|
+
still apply.)
|
|
709
|
+
buffer_containers: Extra containers to keep warm beyond current demand. Must be a
|
|
710
|
+
nonnegative integer, or `None`. Passing `None` is equivalent to setting the buffer to
|
|
711
|
+
0.
|
|
712
|
+
scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
|
|
713
|
+
Must be a positive integer between 2 and 3600, inclusive.
|
|
714
|
+
scaleup_window: Seconds of sustained demand required before scaling up new containers.
|
|
715
|
+
Must be a positive integer.
|
|
716
|
+
|
|
717
|
+
Returns:
|
|
718
|
+
A `ServerAutoscalerSettings` dataclass.
|
|
719
|
+
|
|
720
|
+
Examples:
|
|
721
|
+
|
|
722
|
+
Update `MyServer` so that 2 containers are always active.
|
|
723
|
+
|
|
724
|
+
```python notest
|
|
725
|
+
MyServer = modal.Server.from_name("my-app", "MyServer")
|
|
726
|
+
MyServer.autoscaler.update(min_containers=2)
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Update `MyServer` so that there is no Server-level limit on `max_containers`:
|
|
730
|
+
|
|
731
|
+
```python notest
|
|
732
|
+
MyServer = modal.Server.from_name("my-app", "MyServer")
|
|
733
|
+
MyServer.autoscaler.update(max_containers=None)
|
|
734
|
+
```
|
|
735
|
+
"""
|
|
736
|
+
await self._server.hydrate()
|
|
737
|
+
|
|
738
|
+
service_fn = self._server._get_service_function()
|
|
739
|
+
|
|
740
|
+
settings: api_pb2.AutoscalerSettings = api_pb2.AutoscalerSettings()
|
|
741
|
+
|
|
742
|
+
if min_containers != UNSET_PARAMETER:
|
|
743
|
+
min_containers = min_containers if min_containers is not None else 0
|
|
744
|
+
if min_containers < 0:
|
|
745
|
+
raise InvalidError("Invalid argument: `min_containers` must be a nonnegative integer or `None`.")
|
|
746
|
+
|
|
747
|
+
settings.min_containers = min_containers
|
|
748
|
+
|
|
749
|
+
if buffer_containers != UNSET_PARAMETER:
|
|
750
|
+
buffer_containers = buffer_containers if buffer_containers is not None else 0
|
|
751
|
+
if buffer_containers < 0:
|
|
752
|
+
raise InvalidError("Invalid argument: `buffer_containers` must be a nonnegative integer or `None`.")
|
|
753
|
+
|
|
754
|
+
settings.buffer_containers = buffer_containers
|
|
755
|
+
|
|
756
|
+
if max_containers != UNSET_PARAMETER:
|
|
757
|
+
if max_containers is not None and max_containers <= 0:
|
|
758
|
+
raise InvalidError("Invalid argument: `max_containers` must be a postive integer or `None`.")
|
|
759
|
+
|
|
760
|
+
settings.max_containers = max_containers if max_containers is not None else 0
|
|
761
|
+
|
|
762
|
+
if scaledown_window != UNSET_PARAMETER:
|
|
763
|
+
if not (2 <= scaledown_window <= 3600):
|
|
764
|
+
raise InvalidError("Invalid argument: `scaledown_window` must be between 2 and 3600 (inclusive)")
|
|
765
|
+
|
|
766
|
+
settings.scaledown_window = scaledown_window
|
|
767
|
+
|
|
768
|
+
if scaleup_window != UNSET_PARAMETER:
|
|
769
|
+
if scaleup_window <= 0:
|
|
770
|
+
raise InvalidError("Invalid argument: `scaleup_window` must be a postive integer.")
|
|
771
|
+
|
|
772
|
+
settings.scaleup_window = scaleup_window
|
|
773
|
+
|
|
774
|
+
if target_concurrency != UNSET_PARAMETER:
|
|
775
|
+
if target_concurrency is not None:
|
|
776
|
+
if target_concurrency <= 0:
|
|
777
|
+
raise InvalidError("Invalid argument: `target_concurrency` must be a postive float or `None`.")
|
|
778
|
+
|
|
779
|
+
validate_target_concurrency(target_concurrency, "target_concurrency", allow_fractional=True)
|
|
780
|
+
|
|
781
|
+
settings.target_concurrency_float = (
|
|
782
|
+
normalize_fractional_target_concurrency(target_concurrency) if target_concurrency is not None else 0.0
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=service_fn.object_id, settings=settings)
|
|
786
|
+
response = await service_fn.client._stub.FunctionUpdateSchedulingParams(request)
|
|
787
|
+
|
|
788
|
+
ret = ServerAutoscalerSettings._from_proto(response.current_settings)
|
|
789
|
+
|
|
790
|
+
updates = {}
|
|
791
|
+
if ret.target_concurrency == 0.0:
|
|
792
|
+
updates["target_concurrency"] = None
|
|
793
|
+
if ret.max_containers == 0:
|
|
794
|
+
updates["max_containers"] = None
|
|
795
|
+
|
|
796
|
+
if updates:
|
|
797
|
+
ret = dataclasses.replace(ret, **updates)
|
|
798
|
+
|
|
799
|
+
return ret
|
|
@@ -76,6 +76,11 @@ class _Server:
|
|
|
76
76
|
"""Start and terminate sticky sessions on a Server decorated with `@modal.sessioned()`."""
|
|
77
77
|
...
|
|
78
78
|
|
|
79
|
+
@property
|
|
80
|
+
def autoscaler(self) -> _ServerAutoscalerManager:
|
|
81
|
+
"""View and update Autoscaler settings on a Server."""
|
|
82
|
+
...
|
|
83
|
+
|
|
79
84
|
async def info(self, *, refresh: bool = False) -> modal.types.ServerInfo:
|
|
80
85
|
"""Get an overview of a Server's resource requests, associated mounts, http config, etc.
|
|
81
86
|
|
|
@@ -450,3 +455,81 @@ class ServerSessionsManager:
|
|
|
450
455
|
...
|
|
451
456
|
|
|
452
457
|
terminate: __terminate_spec
|
|
458
|
+
|
|
459
|
+
class _ServerAutoscalerManager:
|
|
460
|
+
def __init__(self, server: _Server):
|
|
461
|
+
"""mdmd:hidden"""
|
|
462
|
+
...
|
|
463
|
+
|
|
464
|
+
async def settings(self) -> modal.types.ServerAutoscalerSettings:
|
|
465
|
+
"""View the current Autoscaler settings for this Server.
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
A `ServerAutoscalerSettings` dataclass.
|
|
469
|
+
|
|
470
|
+
Examples:
|
|
471
|
+
|
|
472
|
+
```python notest
|
|
473
|
+
server = modal.Server.from_name("my-app", "MyServer")
|
|
474
|
+
settings = server.autoscaler.settings()
|
|
475
|
+
```
|
|
476
|
+
"""
|
|
477
|
+
...
|
|
478
|
+
|
|
479
|
+
async def update(
|
|
480
|
+
self,
|
|
481
|
+
*,
|
|
482
|
+
target_concurrency: typing.Optional[float] = -1,
|
|
483
|
+
min_containers: typing.Optional[int] = -1,
|
|
484
|
+
max_containers: typing.Optional[int] = -1,
|
|
485
|
+
buffer_containers: typing.Optional[int] = -1,
|
|
486
|
+
scaledown_window: int = -1,
|
|
487
|
+
scaleup_window: int = -1,
|
|
488
|
+
) -> modal.types.ServerAutoscalerSettings:
|
|
489
|
+
"""Override the current Autoscaler settings for this Server.
|
|
490
|
+
|
|
491
|
+
All parameters to this function are optional; not passing an argument for a given parameter
|
|
492
|
+
will not update its value.
|
|
493
|
+
|
|
494
|
+
Note that re-deploying the App containing this Server will wipe any overrides and revert
|
|
495
|
+
its Autoscaler settings back to those defined on the Server at creation time.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
target_concurrency: Target number of concurrent requests per container. Must either be a
|
|
499
|
+
positive float (e.g. 1.5 to target 3 requests for every 2 containers), or `None`.
|
|
500
|
+
Passing `None` will disable autoscaling of this Server (effectively setting target
|
|
501
|
+
concurrency to infinity).
|
|
502
|
+
min_containers: Minimum number of containers to keep running. Must be a nonnegative
|
|
503
|
+
integer, or `None`. Passing `None` is equivalent to setting the minimum to 0.
|
|
504
|
+
max_containers: Maximum concurrent containers. Must be a positive integer, or `None`.
|
|
505
|
+
Passing `None` is equivalent to disabling the upper bound on container concurrency at
|
|
506
|
+
the Server level (note that Environment and Workspace level container upper limits
|
|
507
|
+
still apply.)
|
|
508
|
+
buffer_containers: Extra containers to keep warm beyond current demand. Must be a
|
|
509
|
+
nonnegative integer, or `None`. Passing `None` is equivalent to setting the buffer to
|
|
510
|
+
0.
|
|
511
|
+
scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
|
|
512
|
+
Must be a positive integer between 2 and 3600, inclusive.
|
|
513
|
+
scaleup_window: Seconds of sustained demand required before scaling up new containers.
|
|
514
|
+
Must be a positive integer.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
A `ServerAutoscalerSettings` dataclass.
|
|
518
|
+
|
|
519
|
+
Examples:
|
|
520
|
+
|
|
521
|
+
Update `MyServer` so that 2 containers are always active.
|
|
522
|
+
|
|
523
|
+
```python notest
|
|
524
|
+
MyServer = modal.Server.from_name("my-app", "MyServer")
|
|
525
|
+
MyServer.autoscaler.update(min_containers=2)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Update `MyServer` so that there is no Server-level limit on `max_containers`:
|
|
529
|
+
|
|
530
|
+
```python notest
|
|
531
|
+
MyServer = modal.Server.from_name("my-app", "MyServer")
|
|
532
|
+
MyServer.autoscaler.update(max_containers=None)
|
|
533
|
+
```
|
|
534
|
+
"""
|
|
535
|
+
...
|
|
@@ -378,7 +378,9 @@ def retry(
|
|
|
378
378
|
|
|
379
379
|
Usage:
|
|
380
380
|
|
|
381
|
-
```python
|
|
381
|
+
```python
|
|
382
|
+
from modal._utils.async_utils import retry
|
|
383
|
+
|
|
382
384
|
@retry
|
|
383
385
|
async def may_fail_default():
|
|
384
386
|
# ...
|
|
@@ -907,7 +909,9 @@ async def asyncnullcontext(*args, **kwargs):
|
|
|
907
909
|
Note that for Python 3.10+ you can use contextlib.nullcontext() instead.
|
|
908
910
|
|
|
909
911
|
Usage:
|
|
910
|
-
```python
|
|
912
|
+
```python
|
|
913
|
+
from modal._utils.async_utils import asyncnullcontext
|
|
914
|
+
|
|
911
915
|
async with asyncnullcontext():
|
|
912
916
|
pass
|
|
913
917
|
```
|
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
|
8
8
|
|
|
9
9
|
from google.protobuf.empty_pb2 import Empty
|
|
10
10
|
|
|
11
|
-
from modal.exception import DeprecationError, InvalidError
|
|
11
|
+
from modal.exception import DeprecationError, InvalidError, NotFoundError
|
|
12
12
|
from modal.types import ServiceUserInfo
|
|
13
13
|
from modal_proto import api_pb2
|
|
14
14
|
|
|
@@ -629,8 +629,7 @@ class _WorkspaceServiceUserManager:
|
|
|
629
629
|
service_user_name: The name of the Service User.
|
|
630
630
|
environment_name: The name of the Environment to which the Service User's new role will
|
|
631
631
|
apply.
|
|
632
|
-
role: The access level to grant this Service User.
|
|
633
|
-
`"viewer"`, or `"contributor"`.
|
|
632
|
+
role: The access level to grant this Service User.
|
|
634
633
|
|
|
635
634
|
Examples:
|
|
636
635
|
|
|
@@ -644,6 +643,9 @@ class _WorkspaceServiceUserManager:
|
|
|
644
643
|
```
|
|
645
644
|
"""
|
|
646
645
|
|
|
646
|
+
if not role:
|
|
647
|
+
raise InvalidError("Invalid argument: `role` must not be empty.")
|
|
648
|
+
|
|
647
649
|
if not self._workspace._is_hydrated:
|
|
648
650
|
await self._workspace.hydrate()
|
|
649
651
|
|
|
@@ -663,8 +665,6 @@ class _WorkspaceServiceUserManager:
|
|
|
663
665
|
async def info(self, name: str) -> ServiceUserInfo:
|
|
664
666
|
"""Get information about a specific Service User.
|
|
665
667
|
|
|
666
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
667
|
-
|
|
668
668
|
Args:
|
|
669
669
|
name: The name of the Service User to introspect.
|
|
670
670
|
|
|
@@ -691,8 +691,6 @@ class _WorkspaceServiceUserManager:
|
|
|
691
691
|
async def list(self) -> list[ServiceUserInfo]:
|
|
692
692
|
"""List all Service Users in the current workspace.
|
|
693
693
|
|
|
694
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
695
|
-
|
|
696
694
|
Returns:
|
|
697
695
|
A list of `ServiceUserInfo` dataclasses.
|
|
698
696
|
|
|
@@ -712,13 +710,12 @@ class _WorkspaceServiceUserManager:
|
|
|
712
710
|
|
|
713
711
|
return [ServiceUserInfo._from_proto(su) for su in resp.service_users]
|
|
714
712
|
|
|
715
|
-
async def delete(self, name: str) -> None:
|
|
713
|
+
async def delete(self, name: str, *, allow_missing: bool = False) -> None:
|
|
716
714
|
"""Permanently delete a Service User.
|
|
717
715
|
|
|
718
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
719
|
-
|
|
720
716
|
Args:
|
|
721
717
|
name: The name of the Service User to delete.
|
|
718
|
+
allow_missing: If `True`, do not throw an exception if the Service User does not exist.
|
|
722
719
|
|
|
723
720
|
Examples:
|
|
724
721
|
|
|
@@ -731,4 +728,10 @@ class _WorkspaceServiceUserManager:
|
|
|
731
728
|
if not self._workspace._is_hydrated:
|
|
732
729
|
await self._workspace.hydrate()
|
|
733
730
|
|
|
734
|
-
|
|
731
|
+
try:
|
|
732
|
+
await self._workspace.client._stub.ServiceUserDelete(
|
|
733
|
+
api_pb2.ServiceUserDeleteRequest(service_user_name=name)
|
|
734
|
+
)
|
|
735
|
+
except NotFoundError:
|
|
736
|
+
if not allow_missing:
|
|
737
|
+
raise
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
aiohappyeyeballs==2.6.1
|
|
2
2
|
aiohttp==3.12.7 ; python_version < "3.14"
|
|
3
|
-
aiohttp==3.13.2 ; python_version
|
|
3
|
+
aiohttp==3.13.2 ; python_version == "3.14"
|
|
4
|
+
aiohttp==3.14.4 ; python_version >= "3.15"
|
|
4
5
|
aiosignal==1.3.2 ; python_version < "3.14"
|
|
5
6
|
aiosignal==1.4.0 ; python_version >= "3.14"
|
|
6
7
|
async-timeout==5.0.1 ; python_version < "3.11"
|
|
@@ -16,6 +17,9 @@ hyperframe==6.1.0
|
|
|
16
17
|
idna==3.10
|
|
17
18
|
multidict==6.4.4
|
|
18
19
|
propcache==0.3.1
|
|
19
|
-
protobuf==6.31.1
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
protobuf==6.31.1 ; python_version < "3.15"
|
|
21
|
+
protobuf==7.35.1 ; python_version >= "3.15"
|
|
22
|
+
typing_extensions==4.13.2 ; python_version < "3.15"
|
|
23
|
+
typing_extensions==4.16.0 ; python_version >= "3.15"
|
|
24
|
+
yarl==1.20.0 ; python_version < "3.15"
|
|
25
|
+
yarl==1.25.1 ; python_version >= "3.15"
|
|
@@ -14,6 +14,8 @@ hyperframe==6.1.0
|
|
|
14
14
|
idna==3.10
|
|
15
15
|
multidict==6.4.4
|
|
16
16
|
propcache==0.3.1
|
|
17
|
-
protobuf==6.31.1
|
|
18
|
-
|
|
17
|
+
protobuf==6.31.1 ; python_version <= "3.14"
|
|
18
|
+
protobuf==7.35.1 ; python_version >= "3.15"
|
|
19
|
+
typing_extensions==4.13.2 ; python_version <= "3.14"
|
|
20
|
+
typing_extensions==4.16.0 ; python_version >= "3.15"
|
|
19
21
|
yarl==1.20.0
|