modAL 1.3.6.dev13__tar.gz → 1.3.6.dev14__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.3.6.dev13 → modal-1.3.6.dev14}/PKG-INFO +1 -1
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_functions.py +7 -87
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_partial_function.py +7 -63
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_resources.py +3 -4
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/deprecation.py +23 -44
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/function_utils.py +17 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/app.py +11 -85
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/app.pyi +6 -36
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/entry_point.py +1 -1
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/import_refs.py +2 -5
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/client.pyi +2 -2
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cls.py +3 -48
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cls.pyi +2 -71
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/dict.py +3 -21
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/dict.pyi +5 -9
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/experimental/__init__.py +9 -87
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/functions.py +1 -2
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/functions.pyi +1 -66
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/image.py +37 -38
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/image.pyi +26 -27
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/mount.py +1 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/network_file_system.py +1 -5
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/network_file_system.pyi +0 -5
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/partial_function.pyi +2 -33
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/queue.py +1 -3
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/queue.pyi +0 -2
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/runner.py +7 -9
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/runner.pyi +8 -8
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/sandbox.py +3 -23
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/sandbox.pyi +8 -47
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/secret.py +3 -12
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/secret.pyi +0 -13
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/serving.py +1 -1
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/volume.py +2 -5
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/volume.pyi +0 -5
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal.egg-info/SOURCES.txt +0 -1
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/gen_reference_docs.py +0 -1
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/api_grpc.py +16 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/api_pb2.py +494 -453
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/api_pb2.pyi +101 -12
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_version/__init__.py +1 -1
- modal-1.3.6.dev13/modal/gpu.py +0 -225
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/LICENSE +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/README.md +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/__main__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_billing.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_clustered_functions.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_grpc_client.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_ipython.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_load_context.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_location.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_logs.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_object.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_output/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_output/manager.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_output/pty.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_output/rich.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_output/status.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_resolver.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_serialization.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_server.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_traceback.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_tunnel.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_tunnel.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_type_manager.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/browser_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/logger.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_vendor/version.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/_watcher.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/billing.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/README.md +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/builder/base-images.json +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/call_graph.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/_download.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/_traceback.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/app.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/billing.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/changelog.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/cluster.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/config.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/container.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/dashboard.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/dict.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/environment.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/launch.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/profile.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/queues.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/run.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/secret.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/selector.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/shell.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/token.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/utils.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cli/volume.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/client.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/config.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/container_process.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/container_process.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/environments.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/environments.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/exception.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/experimental/flash.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/experimental/ipython.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/file_io.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/file_io.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/io_streams.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/io_streams.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/mount.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/object.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/object.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/output.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/parallel_map.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/parallel_map.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/partial_function.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/proxy.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/proxy.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/py.typed +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/retries.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/running_app.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/sandbox_fs.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/sandbox_fs.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/schedule.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/scheduler_placement.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/server.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/server.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/serving.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/snapshot.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/snapshot.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/stream_type.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/token_flow.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal/token_flow.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/__init__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/py.typed +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/modal_version/__main__.py +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/pyproject.toml +0 -0
- {modal-1.3.6.dev13 → modal-1.3.6.dev14}/setup.cfg +0 -0
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import dataclasses
|
|
4
4
|
import inspect
|
|
5
|
-
import textwrap
|
|
6
5
|
import time
|
|
7
6
|
import typing
|
|
8
7
|
import warnings
|
|
@@ -44,7 +43,6 @@ from ._utils.async_utils import (
|
|
|
44
43
|
warn_if_generator_is_not_consumed,
|
|
45
44
|
)
|
|
46
45
|
from ._utils.blob_utils import MAX_OBJECT_SIZE_BYTES
|
|
47
|
-
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
48
46
|
from ._utils.function_utils import (
|
|
49
47
|
ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
50
48
|
OUTPUTS_TIMEOUT,
|
|
@@ -54,6 +52,7 @@ from ._utils.function_utils import (
|
|
|
54
52
|
_stream_function_call_data,
|
|
55
53
|
get_function_type,
|
|
56
54
|
is_async,
|
|
55
|
+
parse_gpu_config,
|
|
57
56
|
)
|
|
58
57
|
from ._utils.grpc_utils import Retry, RetryWarningMessage
|
|
59
58
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
|
|
@@ -67,7 +66,6 @@ from .exception import (
|
|
|
67
66
|
NotFoundError,
|
|
68
67
|
OutputExpiredError,
|
|
69
68
|
)
|
|
70
|
-
from .gpu import GPU_T, parse_gpu_config
|
|
71
69
|
from .image import _Image
|
|
72
70
|
from .mount import _get_client_mount, _Mount
|
|
73
71
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
|
@@ -587,7 +585,7 @@ class _FunctionSpec:
|
|
|
587
585
|
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem]
|
|
588
586
|
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
|
|
589
587
|
# TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
|
|
590
|
-
gpus:
|
|
588
|
+
gpus: Optional[str | list[str]]
|
|
591
589
|
cloud: Optional[str]
|
|
592
590
|
cpu: Optional[Union[float, tuple[float, float]]]
|
|
593
591
|
memory: Optional[Union[int, tuple[int, int]]]
|
|
@@ -666,7 +664,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
666
664
|
secrets: Optional[Collection[_Secret]] = None,
|
|
667
665
|
schedule: Optional[Schedule] = None,
|
|
668
666
|
is_generator: bool = False,
|
|
669
|
-
gpu:
|
|
667
|
+
gpu: Optional[str | list[str]] = None,
|
|
670
668
|
# TODO: maybe break this out into a separate decorator for notebooks.
|
|
671
669
|
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
|
|
672
670
|
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
|
|
@@ -703,7 +701,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
703
701
|
include_source: bool = True,
|
|
704
702
|
experimental_options: Optional[dict[str, str]] = None,
|
|
705
703
|
_experimental_proxy_ip: Optional[str] = None,
|
|
706
|
-
_experimental_custom_scaling_factor: Optional[float] = None,
|
|
707
704
|
restrict_output: bool = False,
|
|
708
705
|
http_config: Optional[api_pb2.HTTPConfig] = None,
|
|
709
706
|
) -> "_Function":
|
|
@@ -799,11 +796,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
799
796
|
f"for clustered Functions"
|
|
800
797
|
)
|
|
801
798
|
|
|
802
|
-
if _experimental_custom_scaling_factor is not None and (
|
|
803
|
-
_experimental_custom_scaling_factor < 0 or _experimental_custom_scaling_factor > 1
|
|
804
|
-
):
|
|
805
|
-
raise InvalidError("`_experimental_custom_scaling_factor` must be between 0.0 and 1.0 inclusive.")
|
|
806
|
-
|
|
807
799
|
if not cloud and not is_builder_function:
|
|
808
800
|
cloud = config.get("default_cloud")
|
|
809
801
|
|
|
@@ -1050,7 +1042,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1050
1042
|
_experimental_group_size=cluster_size or 0, # Experimental: Clustered functions
|
|
1051
1043
|
_experimental_concurrent_cancellations=True,
|
|
1052
1044
|
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
1053
|
-
_experimental_custom_scaling=_experimental_custom_scaling_factor is not None,
|
|
1054
1045
|
# --- These are deprecated in favor of autoscaler_settings
|
|
1055
1046
|
warm_pool_size=min_containers or 0,
|
|
1056
1047
|
concurrency_limit=max_containers or 0,
|
|
@@ -1122,8 +1113,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1122
1113
|
function_data.ranked_functions.extend(ranked_functions)
|
|
1123
1114
|
function_definition = None # function_definition is not used in this case
|
|
1124
1115
|
else:
|
|
1125
|
-
# TODO(irfansharif): Assert on this specific type once we get rid of python 3.9.
|
|
1126
|
-
# assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
|
|
1127
1116
|
function_definition.resources.CopyFrom(
|
|
1128
1117
|
convert_fn_config_to_resources_config(
|
|
1129
1118
|
cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk, rdma=rdma
|
|
@@ -1170,7 +1159,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1170
1159
|
obj._webhook_config = webhook_config # only set locally
|
|
1171
1160
|
|
|
1172
1161
|
# Used to check whether we should rebuild a modal.Image which uses `run_function`.
|
|
1173
|
-
gpus: list[
|
|
1162
|
+
gpus: list[str] = gpu if isinstance(gpu, list) else [gpu] if gpu else []
|
|
1174
1163
|
obj._build_args = dict( # See get_build_def
|
|
1175
1164
|
secrets=repr(secrets),
|
|
1176
1165
|
gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
|
|
@@ -1358,43 +1347,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1358
1347
|
# One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
|
|
1359
1348
|
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
1360
1349
|
|
|
1361
|
-
@live_method
|
|
1362
|
-
async def keep_warm(self, warm_pool_size: int) -> None:
|
|
1363
|
-
"""mdmd:hidden
|
|
1364
|
-
Set the warm pool size for the Function.
|
|
1365
|
-
|
|
1366
|
-
DEPRECATED: Please adapt your code to use the more general `update_autoscaler` method instead:
|
|
1367
|
-
|
|
1368
|
-
```python notest
|
|
1369
|
-
f = modal.Function.from_name("my-app", "function")
|
|
1370
|
-
|
|
1371
|
-
# Old pattern (deprecated)
|
|
1372
|
-
f.keep_warm(2)
|
|
1373
|
-
|
|
1374
|
-
# New pattern
|
|
1375
|
-
f.update_autoscaler(min_containers=2)
|
|
1376
|
-
```
|
|
1377
|
-
"""
|
|
1378
|
-
if self._is_method:
|
|
1379
|
-
raise InvalidError(
|
|
1380
|
-
textwrap.dedent(
|
|
1381
|
-
"""
|
|
1382
|
-
The `.keep_warm()` method can not be used on Modal class *methods*.
|
|
1383
|
-
|
|
1384
|
-
Call `.keep_warm()` on the class *instance* instead. All methods of a class are run by the same
|
|
1385
|
-
container pool, and this method applies to the size of that container pool.
|
|
1386
|
-
"""
|
|
1387
|
-
)
|
|
1388
|
-
)
|
|
1389
|
-
|
|
1390
|
-
deprecation_warning(
|
|
1391
|
-
(2025, 5, 5),
|
|
1392
|
-
"The .keep_warm() method has been deprecated in favor of the more general "
|
|
1393
|
-
".update_autoscaler(min_containers=...) method.",
|
|
1394
|
-
show_source=True,
|
|
1395
|
-
)
|
|
1396
|
-
await self.update_autoscaler(min_containers=warm_pool_size)
|
|
1397
|
-
|
|
1398
1350
|
@classmethod
|
|
1399
1351
|
def _from_name(
|
|
1400
1352
|
cls,
|
|
@@ -1444,7 +1396,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1444
1396
|
app_name: str,
|
|
1445
1397
|
name: str,
|
|
1446
1398
|
*,
|
|
1447
|
-
namespace=None, # mdmd:line-hidden
|
|
1448
1399
|
environment_name: Optional[str] = None,
|
|
1449
1400
|
client: Optional[_Client] = None,
|
|
1450
1401
|
) -> "_Function":
|
|
@@ -1459,17 +1410,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1459
1410
|
```
|
|
1460
1411
|
"""
|
|
1461
1412
|
if "." in name:
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
"Looking up class methods using Function.from_name will be deprecated"
|
|
1466
|
-
" in a future version of Modal.\nUse modal.Cls.from_name instead, e.g.\n\n"
|
|
1467
|
-
f'{class_name} = modal.Cls.from_name("{app_name}", "{class_name}")\n'
|
|
1468
|
-
f"instance = {class_name}(...)\n"
|
|
1469
|
-
f"instance.{method_name}.remote(...)\n",
|
|
1413
|
+
raise InvalidError(
|
|
1414
|
+
f"Invalid Function name: {name!r}. "
|
|
1415
|
+
"If you are trying to look up a class method, use `modal.Cls.from_name` instead."
|
|
1470
1416
|
)
|
|
1471
1417
|
|
|
1472
|
-
warn_if_passing_namespace(namespace, "modal.Function.from_name")
|
|
1473
1418
|
return cls._from_name(
|
|
1474
1419
|
app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
|
|
1475
1420
|
)
|
|
@@ -1587,21 +1532,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1587
1532
|
+ f"or call it locally: {self._function_name}.local()"
|
|
1588
1533
|
)
|
|
1589
1534
|
|
|
1590
|
-
# TODO (live_method on properties is not great, since it could be blocking the event loop from async contexts)
|
|
1591
|
-
@property
|
|
1592
|
-
@live_method
|
|
1593
|
-
async def web_url(self) -> Optional[str]:
|
|
1594
|
-
"""mdmd:hidden
|
|
1595
|
-
Deprecated. Use the `Function.get_web_url()` method instead.
|
|
1596
|
-
|
|
1597
|
-
URL of a Function running as a web endpoint.
|
|
1598
|
-
"""
|
|
1599
|
-
deprecation_msg = """The Function.web_url property will be removed in a future version of Modal.
|
|
1600
|
-
Use the `Function.get_web_url()` method instead.
|
|
1601
|
-
"""
|
|
1602
|
-
deprecation_warning((2025, 5, 6), deprecation_msg, pending=True)
|
|
1603
|
-
return self._web_url
|
|
1604
|
-
|
|
1605
1535
|
@live_method
|
|
1606
1536
|
async def get_web_url(self) -> Optional[str]:
|
|
1607
1537
|
"""URL of a Function running as a web endpoint."""
|
|
@@ -2110,13 +2040,3 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2110
2040
|
except Exception as exc:
|
|
2111
2041
|
# TODO: kill all running function calls
|
|
2112
2042
|
raise exc
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
async def _gather(*function_calls: _FunctionCall[T]) -> typing.Sequence[T]:
|
|
2116
|
-
"""mdmd:hidden
|
|
2117
|
-
Deprecated: Please use `modal.FunctionCall.gather()` instead."""
|
|
2118
|
-
deprecation_warning(
|
|
2119
|
-
(2025, 2, 24),
|
|
2120
|
-
"`modal.functions.gather()` is deprecated; please use `modal.FunctionCall.gather()` instead.",
|
|
2121
|
-
)
|
|
2122
|
-
return await _FunctionCall.gather(*function_calls)
|
|
@@ -17,7 +17,7 @@ from modal_proto import api_pb2
|
|
|
17
17
|
|
|
18
18
|
from ._functions import _Function
|
|
19
19
|
from ._utils.async_utils import synchronizer
|
|
20
|
-
from ._utils.deprecation import
|
|
20
|
+
from ._utils.deprecation import deprecation_error
|
|
21
21
|
from ._utils.function_utils import callable_has_non_self_params
|
|
22
22
|
from .config import config
|
|
23
23
|
from .exception import InvalidError
|
|
@@ -310,7 +310,7 @@ def _method(
|
|
|
310
310
|
def wrapper(obj: Union[Callable[..., Any], _PartialFunction]) -> _PartialFunction:
|
|
311
311
|
flags = _PartialFunctionFlags.CALLABLE_INTERFACE
|
|
312
312
|
|
|
313
|
-
nonlocal is_generator
|
|
313
|
+
nonlocal is_generator
|
|
314
314
|
if is_generator is None:
|
|
315
315
|
callable = obj.raw_f if isinstance(obj, _PartialFunction) else obj
|
|
316
316
|
is_generator = inspect.isgeneratorfunction(callable) or inspect.isasyncgenfunction(callable)
|
|
@@ -403,72 +403,16 @@ def _fastapi_endpoint(
|
|
|
403
403
|
|
|
404
404
|
def _web_endpoint(
|
|
405
405
|
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
custom_domains: Optional[
|
|
411
|
-
Iterable[str]
|
|
412
|
-
] = None, # Create an endpoint using a custom domain fully-qualified domain name (FQDN).
|
|
413
|
-
requires_proxy_auth: bool = False, # Require Modal-Key and Modal-Secret HTTP Headers on requests.
|
|
414
|
-
) -> Callable[
|
|
415
|
-
[Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]]],
|
|
416
|
-
_PartialFunction[P, ReturnType, ReturnType],
|
|
417
|
-
]:
|
|
418
|
-
"""Register a basic web endpoint with this application.
|
|
419
|
-
|
|
406
|
+
*args,
|
|
407
|
+
**kwargs,
|
|
408
|
+
) -> None:
|
|
409
|
+
"""mdmd:hidden
|
|
420
410
|
DEPRECATED: This decorator has been renamed to `@modal.fastapi_endpoint`.
|
|
421
|
-
|
|
422
|
-
This is the simple way to create a web endpoint on Modal. The function
|
|
423
|
-
behaves as a [FastAPI](https://fastapi.tiangolo.com/) handler and should
|
|
424
|
-
return a response object to the caller.
|
|
425
|
-
|
|
426
|
-
Endpoints created with `@modal.web_endpoint` are meant to be simple, single
|
|
427
|
-
request handlers and automatically have
|
|
428
|
-
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) enabled.
|
|
429
|
-
For more flexibility, use `@modal.asgi_app`.
|
|
430
|
-
|
|
431
|
-
To learn how to use Modal with popular web frameworks, see the
|
|
432
|
-
[guide on web endpoints](https://modal.com/docs/guide/webhooks).
|
|
433
411
|
"""
|
|
434
|
-
|
|
435
|
-
# Probably passing the method string as a positional argument.
|
|
436
|
-
raise InvalidError('Positional arguments are not allowed. Suggestion: `@modal.web_endpoint(method="GET")`.')
|
|
437
|
-
elif _warn_parentheses_missing is not None:
|
|
438
|
-
raise InvalidError(
|
|
439
|
-
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.web_endpoint()`."
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
deprecation_warning(
|
|
412
|
+
deprecation_error(
|
|
443
413
|
(2025, 3, 5), "The `@modal.web_endpoint` decorator has been renamed to `@modal.fastapi_endpoint`."
|
|
444
414
|
)
|
|
445
415
|
|
|
446
|
-
webhook_config = api_pb2.WebhookConfig(
|
|
447
|
-
type=api_pb2.WEBHOOK_TYPE_FUNCTION,
|
|
448
|
-
method=method,
|
|
449
|
-
web_endpoint_docs=docs,
|
|
450
|
-
requested_suffix=label or "",
|
|
451
|
-
ephemeral_suffix=config.get("dev_suffix"),
|
|
452
|
-
async_mode=api_pb2.WEBHOOK_ASYNC_MODE_AUTO,
|
|
453
|
-
custom_domains=_parse_custom_domains(custom_domains),
|
|
454
|
-
requires_proxy_auth=requires_proxy_auth,
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
flags = _PartialFunctionFlags.WEB_INTERFACE
|
|
458
|
-
params = _PartialFunctionParams(webhook_config=webhook_config)
|
|
459
|
-
|
|
460
|
-
def wrapper(
|
|
461
|
-
obj: Union[_PartialFunction[P, ReturnType, ReturnType], Callable[P, ReturnType]],
|
|
462
|
-
) -> _PartialFunction[P, ReturnType, ReturnType]:
|
|
463
|
-
if isinstance(obj, _PartialFunction):
|
|
464
|
-
pf = obj.stack(flags, params)
|
|
465
|
-
else:
|
|
466
|
-
pf = _PartialFunction(obj, flags, params)
|
|
467
|
-
pf.validate_obj_compatibility("web_endpoint")
|
|
468
|
-
return pf
|
|
469
|
-
|
|
470
|
-
return wrapper
|
|
471
|
-
|
|
472
416
|
|
|
473
417
|
def _asgi_app(
|
|
474
418
|
_warn_parentheses_missing=None, # mdmd:line-hidden
|
|
@@ -3,19 +3,18 @@ from typing import Optional, Union
|
|
|
3
3
|
|
|
4
4
|
from modal_proto import api_pb2
|
|
5
5
|
|
|
6
|
+
from ._utils.function_utils import parse_gpu_config
|
|
6
7
|
from .exception import InvalidError
|
|
7
|
-
from .gpu import GPU_T, parse_gpu_config
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def convert_fn_config_to_resources_config(
|
|
11
11
|
*,
|
|
12
12
|
cpu: Optional[Union[float, tuple[float, float]]],
|
|
13
13
|
memory: Optional[Union[int, tuple[int, int]]],
|
|
14
|
-
gpu:
|
|
14
|
+
gpu: Optional[str],
|
|
15
15
|
ephemeral_disk: Optional[int],
|
|
16
16
|
rdma: Optional[bool] = None,
|
|
17
17
|
) -> api_pb2.Resources:
|
|
18
|
-
gpu_config = parse_gpu_config(gpu)
|
|
19
18
|
if cpu and isinstance(cpu, tuple):
|
|
20
19
|
if not cpu[0]:
|
|
21
20
|
raise InvalidError("CPU request must be a positive number")
|
|
@@ -45,7 +44,7 @@ def convert_fn_config_to_resources_config(
|
|
|
45
44
|
return api_pb2.Resources(
|
|
46
45
|
milli_cpu=milli_cpu,
|
|
47
46
|
milli_cpu_max=milli_cpu_max,
|
|
48
|
-
gpu_config=
|
|
47
|
+
gpu_config=parse_gpu_config(gpu),
|
|
49
48
|
memory_mb=memory_mb,
|
|
50
49
|
memory_mb_max=memory_mb_max,
|
|
51
50
|
ephemeral_disk_mb=ephemeral_disk,
|
|
@@ -89,55 +89,34 @@ def renamed_parameter(
|
|
|
89
89
|
return decorator
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"container_idle_timeout": "scaledown_window",
|
|
98
|
-
}
|
|
92
|
+
def handle_deprecated_parameters(func: Callable[P, R]) -> Callable[P, R]:
|
|
93
|
+
"""Meta-decorator for modal.Function decorators that errors when deprecated parameters are passed.
|
|
94
|
+
|
|
95
|
+
We use this for a select parameters that might still be suggested by LLMs.
|
|
96
|
+
"""
|
|
99
97
|
|
|
100
98
|
@functools.wraps(func)
|
|
101
99
|
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
100
|
+
_kwargs: dict[str, Any] = locals()["kwargs"] # Avoid referencing kwargs directly due to bug in sigtools
|
|
101
|
+
|
|
102
|
+
# Basic autoscaler parameters
|
|
103
|
+
autoscaler_settings = {
|
|
104
|
+
"keep_warm": "min_containers",
|
|
105
|
+
"concurrency_limit": "max_containers",
|
|
106
|
+
"container_idle_timeout": "scaledown_window",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for old_name, new_name in autoscaler_settings.items():
|
|
110
|
+
if old_name in _kwargs:
|
|
111
|
+
deprecation_error((2025, 2, 24), f"The '{old_name}' parameter has been renamed to '{new_name}'.")
|
|
112
|
+
|
|
113
|
+
# Input concurrency
|
|
114
|
+
if "allow_concurrent_inputs" in _kwargs:
|
|
115
|
+
deprecation_error(
|
|
116
|
+
(2025, 4, 9),
|
|
117
|
+
"The 'allow_concurrent_inputs' parameter is deprecated. Use the '@modal.concurrent' decorator instead.",
|
|
119
118
|
)
|
|
120
|
-
deprecation_warning((2025, 2, 24), message, show_source=True)
|
|
121
119
|
|
|
122
120
|
return func(*args, **kwargs)
|
|
123
121
|
|
|
124
122
|
return wrapper
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def warn_if_passing_namespace(
|
|
128
|
-
namespace: Any,
|
|
129
|
-
resource_name: str,
|
|
130
|
-
) -> None:
|
|
131
|
-
"""Issue deprecation warning for namespace parameter if non-None value is passed.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
namespace: The namespace parameter value (may be None or actual value)
|
|
135
|
-
resource_name: Name of the resource type for the warning message
|
|
136
|
-
"""
|
|
137
|
-
if namespace is not None:
|
|
138
|
-
deprecation_warning(
|
|
139
|
-
(2025, 6, 30),
|
|
140
|
-
f"The `namespace` parameter for `{resource_name}` is deprecated and will be"
|
|
141
|
-
" removed in a future release. It is no longer needed, so can be removed"
|
|
142
|
-
" from your code.",
|
|
143
|
-
)
|
|
@@ -602,3 +602,20 @@ async def _create_input(
|
|
|
602
602
|
),
|
|
603
603
|
idx=idx,
|
|
604
604
|
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def parse_gpu_config(value: Optional[str]) -> api_pb2.GPUConfig:
|
|
608
|
+
if value is None:
|
|
609
|
+
return api_pb2.GPUConfig()
|
|
610
|
+
elif isinstance(value, str):
|
|
611
|
+
count = 1
|
|
612
|
+
if ":" in value:
|
|
613
|
+
value, count_str = value.split(":", 1)
|
|
614
|
+
try:
|
|
615
|
+
count = int(count_str)
|
|
616
|
+
except ValueError:
|
|
617
|
+
raise InvalidError(f"Invalid GPU count: {count_str}. Value must be an integer.")
|
|
618
|
+
gpu_type = value.upper()
|
|
619
|
+
return api_pb2.GPUConfig(gpu_type=gpu_type, count=count)
|
|
620
|
+
else:
|
|
621
|
+
raise InvalidError(f"Invalid GPU config: {value!r}. Value must be a string or `None`")
|