modal 1.5.3.dev13__tar.gz → 1.5.4__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.3.dev13 → modal-1.5.4}/PKG-INFO +1 -1
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_clustered_functions.py +18 -4
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_clustered_functions.pyi +6 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_environments.py +72 -38
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_functions.py +71 -14
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_image.py +121 -52
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_logs.py +9 -7
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_logs_manager.py +340 -108
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_logs_manager.pyi +373 -173
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_output/pty.py +12 -9
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_output/rich.py +12 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/asgi.py +1 -1
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/container_io_manager.py +107 -19
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/container_io_manager.pyi +21 -9
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/execution_context.py +6 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/execution_context.pyi +1 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_server.py +14 -4
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_supports_logs.py +20 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_tunnel.py +2 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/async_utils.py +19 -2
- modal-1.5.4/modal/_utils/auth_token_manager.py +181 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/blob_utils.py +17 -11
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/function_utils.py +32 -4
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/grpc_utils.py +4 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/task_command_router_client.py +59 -13
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/time_utils.py +4 -1
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_watcher.py +4 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_workspace.py +83 -1
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/app.py +37 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/app.pyi +59 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/_download.py +3 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/app.py +16 -9
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/billing.py +30 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/container.py +66 -99
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/entry_point.py +3 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/environment.py +107 -44
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/image.py +50 -25
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/run.py +36 -8
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/shell.py +6 -7
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/utils.py +8 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/volume.py +14 -17
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/workspace.py +34 -8
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/client.py +1 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/client.pyi +4 -10
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cls.py +7 -1
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cls.pyi +16 -3
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/config.py +1 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/dict.py +19 -15
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/environments.py +3 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/environments.pyi +82 -55
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/exception.py +8 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/experimental/__init__.py +12 -3
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/experimental/flash.py +15 -9
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/experimental/flash.pyi +6 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/functions.pyi +47 -16
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/image.pyi +36 -4
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/mount.py +2 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/mount.pyi +2 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/network_file_system.py +15 -11
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/parallel_map.py +14 -12
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/parallel_map.pyi +3 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/queue.py +19 -15
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/runner.py +13 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/runner.pyi +9 -3
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/sandbox.py +267 -142
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/sandbox.pyi +174 -37
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/server.pyi +25 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/token_flow.py +7 -5
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/types.py +49 -3
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/volume.py +61 -39
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/volume.pyi +21 -15
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/workspace.pyi +20 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal.egg-info/PKG-INFO +1 -1
- modal-1.5.4/modal_proto/__init__.py +11 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/api_grpc.py +96 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/api_pb2.py +1438 -1282
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/api_pb2.pyi +403 -30
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/api_pb2_grpc.py +201 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/api_pb2_grpc.pyi +73 -6
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/modal_api_grpc.py +6 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/task_command_router_pb2.py +92 -92
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/task_command_router_pb2.pyi +10 -2
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_version/__init__.py +1 -1
- {modal-1.5.3.dev13 → modal-1.5.4}/pyproject.toml +10 -35
- modal-1.5.3.dev13/modal/_utils/auth_token_manager.py +0 -110
- modal-1.5.3.dev13/modal_proto/__init__.py +0 -4
- {modal-1.5.3.dev13 → modal-1.5.4}/LICENSE +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/README.md +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/__main__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_billing.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_container_entrypoint.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_function_variants.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_grpc_client.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_ipython.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_load_context.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_location.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_object.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_output/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_output/manager.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_output/status.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_partial_function.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_resolver.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_resources.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/task_lifecycle_manager.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/task_lifecycle_manager.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/telemetry.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_serialization.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_traceback.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_tunnel.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_type_manager.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/app_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/browser_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/curl_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/deprecation.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/display_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/docker_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/git_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/hash_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/http_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/logger.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/mount_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/name_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/package_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/proxy_support.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_utils/shell_utils.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_vendor/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_vendor/tblib.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/_vendor/version.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/billing.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/billing.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/2023.12.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/2024.04.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/2024.10.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/2025.06.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/README.md +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/builder/base-images.json +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/call_graph.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/_help.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/_traceback.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/bootstrap.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/changelog.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/cluster.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/config.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/curl.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/dashboard.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/dict.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/endpoint.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/import_refs.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/launch.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/logo.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/network_file_system.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/profile.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/programs/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/programs/vscode.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/queues.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/secret.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/selector.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/skills.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cli/token.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/container_process.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/container_process.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/dict.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/experimental/ipython.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/file_io.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/file_io.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/file_pattern_matcher.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/functions.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/image.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/io_streams.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/io_streams.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/network_file_system.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/object.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/object.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/output.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/partial_function.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/partial_function.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/proxy.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/proxy.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/py.typed +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/queue.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/retries.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/running_app.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/sandbox_fs.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/sandbox_fs.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/schedule.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/scheduler_placement.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/secret.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/secret.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/server.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/serving.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/serving.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/skills/modal/SKILL.md +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/snapshot.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/snapshot.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/stream_type.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/token_flow.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal/workspace.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal.egg-info/requires.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal.egg-info/top_level.txt +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/mdmd/types.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_docs/reference_intro.md +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/py.typed +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/modal_version/__main__.py +0 -0
- {modal-1.5.3.dev13 → modal-1.5.4}/setup.cfg +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import os
|
|
3
3
|
import socket
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from typing import cast
|
|
5
6
|
|
|
6
7
|
from modal._utils.async_utils import synchronize_api
|
|
7
8
|
from modal.client import _Client
|
|
@@ -15,6 +16,7 @@ class ClusterInfo:
|
|
|
15
16
|
cluster_id: str
|
|
16
17
|
container_ips: list[str]
|
|
17
18
|
container_ipv4_ips: list[str]
|
|
19
|
+
fabric_ids: list[str]
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
cluster_info: ClusterInfo | None = None
|
|
@@ -29,12 +31,22 @@ def get_cluster_info() -> ClusterInfo:
|
|
|
29
31
|
return cluster_info
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def get_fabric_peers() -> list[int]:
|
|
35
|
+
info = get_cluster_info()
|
|
36
|
+
if info.rank >= len(info.fabric_ids) or not info.fabric_ids[info.rank]:
|
|
37
|
+
return [info.rank]
|
|
38
|
+
own_fabric_id = info.fabric_ids[info.rank]
|
|
39
|
+
return [rank for rank, fabric_id in enumerate(info.fabric_ids) if fabric_id == own_fabric_id]
|
|
40
|
+
|
|
41
|
+
|
|
32
42
|
async def _initialize_clustered_function(client: _Client, task_id: str, world_size: int):
|
|
33
43
|
global cluster_info
|
|
34
44
|
|
|
35
|
-
def get_i6pn():
|
|
45
|
+
def get_i6pn() -> str:
|
|
36
46
|
"""Returns the ipv6 address assigned to this container."""
|
|
37
|
-
|
|
47
|
+
# An AF_INET6 result always carries a (host, port, flowinfo, scope_id) sockaddr,
|
|
48
|
+
# but getaddrinfo is typed for every address family, some of which lead with an int
|
|
49
|
+
return cast(str, socket.getaddrinfo("i6pn.modal.local", None, socket.AF_INET6)[0][4][0])
|
|
38
50
|
|
|
39
51
|
hostname = socket.gethostname()
|
|
40
52
|
container_ip = get_i6pn()
|
|
@@ -68,8 +80,9 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
68
80
|
cluster_info = ClusterInfo(
|
|
69
81
|
rank=resp.cluster_rank,
|
|
70
82
|
cluster_id=resp.cluster_id,
|
|
71
|
-
container_ips=resp.container_ips,
|
|
72
|
-
container_ipv4_ips=resp.container_ipv4_ips,
|
|
83
|
+
container_ips=list(resp.container_ips),
|
|
84
|
+
container_ipv4_ips=list(resp.container_ipv4_ips),
|
|
85
|
+
fabric_ids=list(resp.fabric_ids),
|
|
73
86
|
)
|
|
74
87
|
else:
|
|
75
88
|
cluster_info = ClusterInfo(
|
|
@@ -77,6 +90,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
77
90
|
cluster_id="", # No cluster ID for single-node # TODO(irfansharif): Is this right?
|
|
78
91
|
container_ips=[container_ip],
|
|
79
92
|
container_ipv4_ips=[], # No IPv4 IPs for single-node
|
|
93
|
+
fabric_ids=[],
|
|
80
94
|
)
|
|
81
95
|
|
|
82
96
|
|
|
@@ -3,14 +3,17 @@ import types
|
|
|
3
3
|
import typing_extensions
|
|
4
4
|
|
|
5
5
|
class ClusterInfo:
|
|
6
|
-
"""ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str])"""
|
|
6
|
+
"""ClusterInfo(rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str])"""
|
|
7
7
|
|
|
8
8
|
rank: int
|
|
9
9
|
cluster_id: str
|
|
10
10
|
container_ips: list[str]
|
|
11
11
|
container_ipv4_ips: list[str]
|
|
12
|
+
fabric_ids: list[str]
|
|
12
13
|
|
|
13
|
-
def __init__(
|
|
14
|
+
def __init__(
|
|
15
|
+
self, rank: int, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str], fabric_ids: list[str]
|
|
16
|
+
) -> None:
|
|
14
17
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
15
18
|
...
|
|
16
19
|
|
|
@@ -23,6 +26,7 @@ class ClusterInfo:
|
|
|
23
26
|
...
|
|
24
27
|
|
|
25
28
|
def get_cluster_info() -> ClusterInfo: ...
|
|
29
|
+
def get_fabric_peers() -> list[int]: ...
|
|
26
30
|
async def _initialize_clustered_function(client: modal.client._Client, task_id: str, world_size: int): ...
|
|
27
31
|
|
|
28
32
|
class __initialize_clustered_function_spec(typing_extensions.Protocol):
|
|
@@ -4,7 +4,7 @@ import builtins
|
|
|
4
4
|
from collections.abc import Iterable, Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
|
-
from typing import Literal
|
|
7
|
+
from typing import Any, Literal
|
|
8
8
|
|
|
9
9
|
from google.protobuf.empty_pb2 import Empty
|
|
10
10
|
from google.protobuf.message import Message
|
|
@@ -16,6 +16,7 @@ from modal_proto import api_pb2
|
|
|
16
16
|
from ._load_context import LoadContext
|
|
17
17
|
from ._object import _Object
|
|
18
18
|
from ._resolver import Resolver
|
|
19
|
+
from ._utils.deprecation import deprecation_warning
|
|
19
20
|
from ._utils.name_utils import check_environment_name
|
|
20
21
|
from ._utils.time_utils import is_utc_month_aligned, parse_billing_cycle
|
|
21
22
|
from .client import _Client
|
|
@@ -38,6 +39,7 @@ class _EnvironmentManager:
|
|
|
38
39
|
name: str, # Name to use for the new Environment
|
|
39
40
|
*,
|
|
40
41
|
restricted: bool = False, # If True, enable RBAC restrictions on the Environment
|
|
42
|
+
experimental_options: dict[str, Any] | None = None, # Experimental options for Environment creation
|
|
41
43
|
client: _Client | None = None, # Optional client with Modal credentials
|
|
42
44
|
) -> None:
|
|
43
45
|
"""Create a new Environment.
|
|
@@ -50,7 +52,10 @@ class _EnvironmentManager:
|
|
|
50
52
|
"""
|
|
51
53
|
check_environment_name(name)
|
|
52
54
|
client = await _Client.from_env() if client is None else client
|
|
53
|
-
|
|
55
|
+
request = api_pb2.EnvironmentCreateRequest(name=name, is_managed=restricted)
|
|
56
|
+
if (experimental_options or {}).get("is_public", False):
|
|
57
|
+
request.environment_type = api_pb2.ENVIRONMENT_TYPE_PUBLIC
|
|
58
|
+
await client.stub.EnvironmentCreate(request)
|
|
54
59
|
|
|
55
60
|
async def list(
|
|
56
61
|
self,
|
|
@@ -104,7 +109,7 @@ class _EnvironmentManager:
|
|
|
104
109
|
await client.stub.EnvironmentDelete(api_pb2.EnvironmentDeleteRequest(name=name))
|
|
105
110
|
|
|
106
111
|
|
|
107
|
-
class
|
|
112
|
+
class _EnvironmentRolesManager:
|
|
108
113
|
"""mdmd:namespace"""
|
|
109
114
|
|
|
110
115
|
def __init__(self, environment: "_Environment"):
|
|
@@ -112,13 +117,13 @@ class _EnvironmentMembersManager:
|
|
|
112
117
|
self._environment = environment
|
|
113
118
|
|
|
114
119
|
async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
|
|
115
|
-
"""Enumerate the
|
|
120
|
+
"""Enumerate the Environment Role for each user and service user in the workspace.
|
|
116
121
|
|
|
117
122
|
**Examples:**
|
|
118
123
|
|
|
119
124
|
```python notest
|
|
120
|
-
|
|
121
|
-
print(
|
|
125
|
+
roles = modal.Environment.from_name("my-env").roles.list()
|
|
126
|
+
print(roles)
|
|
122
127
|
# {
|
|
123
128
|
# "users": {"alice": "contributor", "bob": "viewer", "carol": "contributor"},
|
|
124
129
|
# "service_users": {"alice-bot": "contributor", "ops-bot": "viewer", "ci-bot": "no-access"},
|
|
@@ -145,16 +150,17 @@ class _EnvironmentMembersManager:
|
|
|
145
150
|
users: Mapping[str, str] | None = None,
|
|
146
151
|
service_users: Mapping[str, str] | None = None,
|
|
147
152
|
) -> None:
|
|
148
|
-
"""
|
|
153
|
+
"""Update the Environment Role of users and service users.
|
|
149
154
|
|
|
150
|
-
Each
|
|
151
|
-
|
|
155
|
+
Each role is one of 'contributor', 'viewer', or 'no-access'. Service users can be
|
|
156
|
+
assigned a role on any Environment, while workspace members can only be assigned a
|
|
157
|
+
role on restricted Environments.
|
|
152
158
|
|
|
153
159
|
**Examples:**
|
|
154
160
|
|
|
155
161
|
```python notest
|
|
156
162
|
env = modal.Environment.from_name("my-restricted-env")
|
|
157
|
-
env.
|
|
163
|
+
env.roles.update(
|
|
158
164
|
users={"alice": "contributor", "bob": "viewer"},
|
|
159
165
|
service_users={"alice-bot": "contributor"},
|
|
160
166
|
)
|
|
@@ -196,24 +202,59 @@ class _EnvironmentMembersManager:
|
|
|
196
202
|
|
|
197
203
|
await self._dispatch_role_updates(requests)
|
|
198
204
|
|
|
205
|
+
async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
|
|
206
|
+
"""Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
|
|
207
|
+
results = await asyncio.gather(
|
|
208
|
+
*(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
|
|
209
|
+
return_exceptions=True,
|
|
210
|
+
)
|
|
211
|
+
errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
|
|
212
|
+
if errors:
|
|
213
|
+
n = len(errors)
|
|
214
|
+
header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment roles:"
|
|
215
|
+
details = "\n".join(f" - {label}: {e}" for label, e in errors)
|
|
216
|
+
raise WorkspaceManagementError(f"{header}\n{details}")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Deprecated alias for `_EnvironmentRolesManager`; each method warns and delegates.
|
|
220
|
+
class _EnvironmentMembersManager:
|
|
221
|
+
"""mdmd:hidden"""
|
|
222
|
+
|
|
223
|
+
def __init__(self, environment: "_Environment"):
|
|
224
|
+
"""mdmd:hidden"""
|
|
225
|
+
self._environment = environment
|
|
226
|
+
|
|
227
|
+
async def list(self) -> dict[Literal["users", "service_users"], dict[str, str]]:
|
|
228
|
+
deprecation_warning(
|
|
229
|
+
(2026, 7, 23),
|
|
230
|
+
"`Environment.members.list()` is deprecated; use `Environment.roles.list()` instead.",
|
|
231
|
+
)
|
|
232
|
+
return await _EnvironmentRolesManager(self._environment).list()
|
|
233
|
+
|
|
234
|
+
async def update(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
users: Mapping[str, str] | None = None,
|
|
238
|
+
service_users: Mapping[str, str] | None = None,
|
|
239
|
+
) -> None:
|
|
240
|
+
deprecation_warning(
|
|
241
|
+
(2026, 7, 23),
|
|
242
|
+
"`Environment.members.update()` is deprecated; use `Environment.roles.update()` instead.",
|
|
243
|
+
)
|
|
244
|
+
await _EnvironmentRolesManager(self._environment).update(users=users, service_users=service_users)
|
|
245
|
+
|
|
199
246
|
async def remove(
|
|
200
247
|
self,
|
|
201
248
|
*,
|
|
202
249
|
users: Iterable[str] | None = None,
|
|
203
250
|
service_users: Iterable[str] | None = None,
|
|
204
251
|
) -> None:
|
|
205
|
-
"""Remove
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
env = modal.Environment.from_name("my-restricted-env")
|
|
211
|
-
env.members.remove(
|
|
212
|
-
users=["alice"],
|
|
213
|
-
service_users=["alice-bot"],
|
|
252
|
+
"""Remove the Environment Role of users and service users, reverting them to the default."""
|
|
253
|
+
deprecation_warning(
|
|
254
|
+
(2026, 7, 23),
|
|
255
|
+
"`Environment.members.remove()` is deprecated. Environment Roles are now explicit; "
|
|
256
|
+
"set a role (e.g. 'no-access') with `Environment.roles.update()` instead.",
|
|
214
257
|
)
|
|
215
|
-
```
|
|
216
|
-
"""
|
|
217
258
|
await self._environment.hydrate()
|
|
218
259
|
users = users or []
|
|
219
260
|
service_users = service_users or []
|
|
@@ -247,20 +288,7 @@ class _EnvironmentMembersManager:
|
|
|
247
288
|
role=api_pb2.ENVIRONMENT_ROLE_UNSPECIFIED,
|
|
248
289
|
)
|
|
249
290
|
|
|
250
|
-
await self._dispatch_role_updates(requests)
|
|
251
|
-
|
|
252
|
-
async def _dispatch_role_updates(self, requests: dict[str, api_pb2.EnvironmentRoleSetRequest]) -> None:
|
|
253
|
-
"""Send batched EnvironmentRoleSet RPCs and report all errors encountered."""
|
|
254
|
-
results = await asyncio.gather(
|
|
255
|
-
*(self._environment.client.stub.EnvironmentRoleSet(req) for req in requests.values()),
|
|
256
|
-
return_exceptions=True,
|
|
257
|
-
)
|
|
258
|
-
errors = [(label, result) for label, result in zip(requests.keys(), results) if isinstance(result, Exception)]
|
|
259
|
-
if errors:
|
|
260
|
-
n = len(errors)
|
|
261
|
-
header = f"{n} error{'s' if n != 1 else ''} occurred while updating Environment members:"
|
|
262
|
-
details = "\n".join(f" - {label}: {e}" for label, e in errors)
|
|
263
|
-
raise WorkspaceManagementError(f"{header}\n{details}")
|
|
291
|
+
await _EnvironmentRolesManager(self._environment)._dispatch_role_updates(requests)
|
|
264
292
|
|
|
265
293
|
|
|
266
294
|
class _Environment(_Object, type_prefix="en"):
|
|
@@ -284,11 +312,17 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
284
312
|
return _EnvironmentManager()
|
|
285
313
|
|
|
286
314
|
@property
|
|
287
|
-
def
|
|
288
|
-
"""Namespace with methods for managing the
|
|
315
|
+
def roles(self) -> "_EnvironmentRolesManager":
|
|
316
|
+
"""Namespace with methods for managing the Environment Roles of users and service users.
|
|
289
317
|
|
|
290
|
-
See https://modal.com/docs/guide/rbac for more information on
|
|
318
|
+
See https://modal.com/docs/guide/rbac for more information on Environment Roles.
|
|
291
319
|
"""
|
|
320
|
+
return _EnvironmentRolesManager(self)
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def members(self) -> "_EnvironmentMembersManager":
|
|
324
|
+
"""mdmd:hidden"""
|
|
325
|
+
# Deprecated alias for `Environment.roles`.
|
|
292
326
|
return _EnvironmentMembersManager(self)
|
|
293
327
|
|
|
294
328
|
# TODO(michael) Keeping this private for now until we decide what else should be in it
|
|
@@ -45,7 +45,7 @@ from ._utils.async_utils import (
|
|
|
45
45
|
synchronizer,
|
|
46
46
|
warn_if_generator_is_not_consumed,
|
|
47
47
|
)
|
|
48
|
-
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
48
|
+
from ._utils.blob_utils import MAX_ASYNC_OBJECT_SIZE_BYTES, MAX_OBJECT_SIZE_BYTES
|
|
49
49
|
from ._utils.function_utils import (
|
|
50
50
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
51
51
|
OUTPUTS_TIMEOUT,
|
|
@@ -56,7 +56,9 @@ from ._utils.function_utils import (
|
|
|
56
56
|
_stream_function_call_data,
|
|
57
57
|
get_function_type,
|
|
58
58
|
is_async,
|
|
59
|
+
normalize_fractional_target_concurrency,
|
|
59
60
|
parse_gpu_config,
|
|
61
|
+
validate_target_concurrency,
|
|
60
62
|
)
|
|
61
63
|
from ._utils.grpc_utils import Retry, RetryWarningMessage
|
|
62
64
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
|
|
@@ -93,7 +95,7 @@ from .proxy import _Proxy
|
|
|
93
95
|
from .retries import Retries, RetryManager
|
|
94
96
|
from .schedule import Schedule
|
|
95
97
|
from .secret import _Secret
|
|
96
|
-
from .types import FunctionStats
|
|
98
|
+
from .types import FunctionAutoscalerSettings, FunctionStats, ServerAutoscalerSettings
|
|
97
99
|
from .volume import _Volume, _volume_to_mount_proto
|
|
98
100
|
|
|
99
101
|
if TYPE_CHECKING:
|
|
@@ -424,6 +426,7 @@ class _InputPlaneInvocation:
|
|
|
424
426
|
client: _Client,
|
|
425
427
|
input_plane_url: str,
|
|
426
428
|
input_plane_region: str,
|
|
429
|
+
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
|
|
427
430
|
) -> "_InputPlaneInvocation":
|
|
428
431
|
stub = await client.get_stub(input_plane_url)
|
|
429
432
|
|
|
@@ -435,6 +438,7 @@ class _InputPlaneInvocation:
|
|
|
435
438
|
kwargs,
|
|
436
439
|
control_plane_stub,
|
|
437
440
|
function=function,
|
|
441
|
+
function_call_invocation_type=function_call_invocation_type,
|
|
438
442
|
)
|
|
439
443
|
|
|
440
444
|
request = api_pb2.AttemptStartRequest(
|
|
@@ -636,7 +640,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
636
640
|
await self.hydrate()
|
|
637
641
|
if not self._app_id:
|
|
638
642
|
raise ExecutionError("app_id should have been set during function hydration")
|
|
639
|
-
return _LogQueryData(
|
|
643
|
+
return _LogQueryData(
|
|
644
|
+
self.client,
|
|
645
|
+
self._app_id,
|
|
646
|
+
LogsFilters(function_id=self.object_id),
|
|
647
|
+
source_object_id=self.object_id,
|
|
648
|
+
)
|
|
640
649
|
|
|
641
650
|
@property
|
|
642
651
|
def logs(self) -> _FunctionLogsManager:
|
|
@@ -679,7 +688,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
679
688
|
scaleup_window: int | None = None,
|
|
680
689
|
scaledown_window: int | None = None,
|
|
681
690
|
max_concurrent_inputs: int | None = None,
|
|
682
|
-
target_concurrent_inputs:
|
|
691
|
+
target_concurrent_inputs: float | None = None,
|
|
683
692
|
batch_max_size: int | None = None,
|
|
684
693
|
batch_wait_ms: int | None = None,
|
|
685
694
|
cloud: str | None = None,
|
|
@@ -787,6 +796,17 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
787
796
|
scaledown_window=scaledown_window,
|
|
788
797
|
)
|
|
789
798
|
|
|
799
|
+
target_concurrent_inputs_int = 0
|
|
800
|
+
if target_concurrent_inputs is not None:
|
|
801
|
+
if is_server:
|
|
802
|
+
validate_target_concurrency(target_concurrent_inputs, "target_inputs", allow_fractional=True)
|
|
803
|
+
autoscaler_settings.target_concurrency_float = normalize_fractional_target_concurrency(
|
|
804
|
+
target_concurrent_inputs
|
|
805
|
+
)
|
|
806
|
+
else:
|
|
807
|
+
validate_target_concurrency(target_concurrent_inputs, "target_inputs", allow_fractional=False)
|
|
808
|
+
target_concurrent_inputs_int = int(target_concurrent_inputs)
|
|
809
|
+
|
|
790
810
|
# For clustered functions, container settings must be multiples of cluster_size
|
|
791
811
|
if cluster_size is not None and cluster_size > 1:
|
|
792
812
|
for field in ["min_containers", "max_containers", "buffer_containers"]:
|
|
@@ -1012,7 +1032,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1012
1032
|
app_name=app_name,
|
|
1013
1033
|
is_builder_function=is_builder_function,
|
|
1014
1034
|
max_concurrent_inputs=max_concurrent_inputs or 0,
|
|
1015
|
-
target_concurrent_inputs=
|
|
1035
|
+
target_concurrent_inputs=target_concurrent_inputs_int,
|
|
1016
1036
|
batch_max_size=batch_max_size or 0,
|
|
1017
1037
|
batch_linger_ms=batch_wait_ms or 0,
|
|
1018
1038
|
worker_id=config.get("worker_id"),
|
|
@@ -1172,6 +1192,25 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1172
1192
|
return obj
|
|
1173
1193
|
|
|
1174
1194
|
async def _update_autoscaler(
|
|
1195
|
+
self,
|
|
1196
|
+
*,
|
|
1197
|
+
min_containers: int | None = None,
|
|
1198
|
+
max_containers: int | None = None,
|
|
1199
|
+
buffer_containers: int | None = None,
|
|
1200
|
+
scaledown_window: int | None = None,
|
|
1201
|
+
) -> FunctionAutoscalerSettings:
|
|
1202
|
+
settings = api_pb2.AutoscalerSettings(
|
|
1203
|
+
min_containers=min_containers,
|
|
1204
|
+
max_containers=max_containers,
|
|
1205
|
+
buffer_containers=buffer_containers,
|
|
1206
|
+
scaledown_window=scaledown_window,
|
|
1207
|
+
)
|
|
1208
|
+
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1209
|
+
response = await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1210
|
+
|
|
1211
|
+
return FunctionAutoscalerSettings._from_proto(response.current_settings)
|
|
1212
|
+
|
|
1213
|
+
async def _update_autoscaler_server(
|
|
1175
1214
|
self,
|
|
1176
1215
|
*,
|
|
1177
1216
|
min_containers: int | None = None,
|
|
@@ -1179,21 +1218,22 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1179
1218
|
buffer_containers: int | None = None,
|
|
1180
1219
|
scaleup_window: int | None = None,
|
|
1181
1220
|
scaledown_window: int | None = None,
|
|
1182
|
-
target_concurrency:
|
|
1183
|
-
) ->
|
|
1221
|
+
target_concurrency: float | None = None,
|
|
1222
|
+
) -> ServerAutoscalerSettings:
|
|
1184
1223
|
settings = api_pb2.AutoscalerSettings(
|
|
1185
1224
|
min_containers=min_containers,
|
|
1186
1225
|
max_containers=max_containers,
|
|
1187
1226
|
buffer_containers=buffer_containers,
|
|
1188
1227
|
scaleup_window=scaleup_window,
|
|
1189
1228
|
scaledown_window=scaledown_window,
|
|
1190
|
-
target_concurrency=target_concurrency,
|
|
1191
1229
|
)
|
|
1230
|
+
if target_concurrency is not None:
|
|
1231
|
+
validate_target_concurrency(target_concurrency, "target_concurrency", allow_fractional=True)
|
|
1232
|
+
settings.target_concurrency_float = normalize_fractional_target_concurrency(target_concurrency)
|
|
1192
1233
|
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1193
|
-
await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1234
|
+
response = await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1194
1235
|
|
|
1195
|
-
|
|
1196
|
-
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
1236
|
+
return ServerAutoscalerSettings._from_proto(response.current_settings)
|
|
1197
1237
|
|
|
1198
1238
|
@live_method
|
|
1199
1239
|
async def update_autoscaler(
|
|
@@ -1203,7 +1243,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1203
1243
|
max_containers: int | None = None,
|
|
1204
1244
|
buffer_containers: int | None = None,
|
|
1205
1245
|
scaledown_window: int | None = None,
|
|
1206
|
-
) ->
|
|
1246
|
+
) -> FunctionAutoscalerSettings:
|
|
1207
1247
|
"""Override the current autoscaler behavior for this Function.
|
|
1208
1248
|
|
|
1209
1249
|
Unspecified parameters will retain their current value, i.e. either the static value
|
|
@@ -1218,6 +1258,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1218
1258
|
buffer_containers: Extra containers to keep warm beyond current demand.
|
|
1219
1259
|
scaledown_window: Maximum duration (in seconds) idle containers wait before scaling down.
|
|
1220
1260
|
|
|
1261
|
+
Returns:
|
|
1262
|
+
A `FunctionAutoscalerSettings` dataclass which contains the current autoscaler settings
|
|
1263
|
+
of this Function after the call.
|
|
1264
|
+
|
|
1221
1265
|
Examples:
|
|
1222
1266
|
```python notest
|
|
1223
1267
|
f = modal.Function.from_name("my-app", "function")
|
|
@@ -1237,7 +1281,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1237
1281
|
if self._is_method:
|
|
1238
1282
|
raise InvalidError("Cannot call .update_autoscaler() on a method. Call it on the class instance instead.")
|
|
1239
1283
|
|
|
1240
|
-
await self._update_autoscaler(
|
|
1284
|
+
return await self._update_autoscaler(
|
|
1241
1285
|
min_containers=min_containers,
|
|
1242
1286
|
max_containers=max_containers,
|
|
1243
1287
|
buffer_containers=buffer_containers,
|
|
@@ -1427,6 +1471,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1427
1471
|
self._max_object_size_bytes = (
|
|
1428
1472
|
metadata.max_object_size_bytes if metadata.HasField("max_object_size_bytes") else MAX_OBJECT_SIZE_BYTES
|
|
1429
1473
|
)
|
|
1474
|
+
self._max_async_object_size_bytes = (
|
|
1475
|
+
metadata.max_async_object_size_bytes
|
|
1476
|
+
if metadata.HasField("max_async_object_size_bytes")
|
|
1477
|
+
else MAX_ASYNC_OBJECT_SIZE_BYTES
|
|
1478
|
+
)
|
|
1430
1479
|
self._experimental_flash_urls = metadata._experimental_flash_urls
|
|
1431
1480
|
self._app_id = metadata.app_id or None
|
|
1432
1481
|
|
|
@@ -1451,6 +1500,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1451
1500
|
input_plane_url=self._input_plane_url,
|
|
1452
1501
|
input_plane_region=self._input_plane_region,
|
|
1453
1502
|
max_object_size_bytes=self._max_object_size_bytes,
|
|
1503
|
+
max_async_object_size_bytes=self._max_async_object_size_bytes,
|
|
1454
1504
|
_experimental_flash_urls=self._experimental_flash_urls,
|
|
1455
1505
|
supported_input_formats=self._metadata.supported_input_formats if self._metadata else [],
|
|
1456
1506
|
supported_output_formats=self._metadata.supported_output_formats if self._metadata else [],
|
|
@@ -1714,6 +1764,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1714
1764
|
client=self.client,
|
|
1715
1765
|
input_plane_url=self._input_plane_url,
|
|
1716
1766
|
input_plane_region=self._input_plane_region,
|
|
1767
|
+
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
|
|
1717
1768
|
)
|
|
1718
1769
|
else:
|
|
1719
1770
|
invocation = await _Invocation.create(
|
|
@@ -1755,6 +1806,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1755
1806
|
client=self.client,
|
|
1756
1807
|
input_plane_url=self._input_plane_url,
|
|
1757
1808
|
input_plane_region=self._input_plane_region,
|
|
1809
|
+
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY,
|
|
1758
1810
|
)
|
|
1759
1811
|
else:
|
|
1760
1812
|
invocation = await _Invocation.create(
|
|
@@ -2030,7 +2082,12 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2030
2082
|
await self._hydrate_from_id_metadata()
|
|
2031
2083
|
if not self._app_id:
|
|
2032
2084
|
raise ExecutionError("app_id should have been set during function call hydration")
|
|
2033
|
-
return _LogQueryData(
|
|
2085
|
+
return _LogQueryData(
|
|
2086
|
+
self.client,
|
|
2087
|
+
self._app_id,
|
|
2088
|
+
LogsFilters(function_call_id=self.object_id),
|
|
2089
|
+
source_object_id=self.object_id,
|
|
2090
|
+
)
|
|
2034
2091
|
|
|
2035
2092
|
@property
|
|
2036
2093
|
def logs(self) -> _FunctionCallLogsManager:
|