modAL 1.3.6.dev12__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.dev12 → modal-1.3.6.dev14}/PKG-INFO +1 -1
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_functions.py +16 -88
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_partial_function.py +7 -63
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_resources.py +3 -4
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/deprecation.py +23 -44
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/function_utils.py +17 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/mount_utils.py +20 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/app.py +11 -85
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/app.pyi +6 -36
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/app.py +4 -1
- modal-1.3.6.dev14/modal/cli/container.py +264 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/entry_point.py +1 -1
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/import_refs.py +2 -5
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/client.pyi +2 -2
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cls.py +3 -48
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cls.pyi +2 -71
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/dict.py +3 -21
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/dict.pyi +5 -9
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/__init__.py +9 -87
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/functions.py +1 -2
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/functions.pyi +7 -72
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/image.py +72 -46
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/image.pyi +66 -31
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/mount.py +1 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/network_file_system.py +1 -5
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/network_file_system.pyi +0 -5
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/partial_function.pyi +2 -33
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/queue.py +1 -3
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/queue.pyi +0 -2
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/runner.py +26 -18
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/runner.pyi +8 -8
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox.py +7 -24
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox.pyi +8 -47
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/secret.py +3 -12
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/secret.pyi +0 -13
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/serving.py +1 -1
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/volume.py +2 -5
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/volume.pyi +0 -5
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/SOURCES.txt +0 -1
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_reference_docs.py +0 -1
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_grpc.py +16 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2.py +494 -453
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2.pyi +101 -12
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2_grpc.py +33 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/api_pb2_grpc.pyi +10 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/modal_api_grpc.py +1 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_version/__init__.py +1 -1
- modal-1.3.6.dev12/modal/cli/container.py +0 -117
- modal-1.3.6.dev12/modal/gpu.py +0 -225
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/LICENSE +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/README.md +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/__main__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_billing.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_clustered_functions.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_grpc_client.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_ipython.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_load_context.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_location.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_logs.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_object.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/manager.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/pty.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/rich.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_output/status.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_resolver.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_serialization.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_server.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_traceback.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_tunnel.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_tunnel.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_type_manager.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/async_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/browser_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/logger.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_vendor/version.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/_watcher.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/billing.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/README.md +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/builder/base-images.json +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/call_graph.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/_download.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/_traceback.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/billing.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/changelog.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/cluster.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/config.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/dashboard.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/dict.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/environment.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/launch.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/profile.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/queues.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/run.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/secret.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/selector.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/shell.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/token.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/utils.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cli/volume.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/client.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/config.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/container_process.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/container_process.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/environments.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/environments.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/exception.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/flash.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/experimental/ipython.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/file_io.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/file_io.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/io_streams.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/io_streams.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/mount.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/object.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/object.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/output.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/parallel_map.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/parallel_map.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/partial_function.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/proxy.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/proxy.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/py.typed +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/retries.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/running_app.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox_fs.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/sandbox_fs.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/schedule.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/scheduler_placement.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/server.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/server.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/serving.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/snapshot.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/snapshot.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/stream_type.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/token_flow.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal/token_flow.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/__init__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/py.typed +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/modal_version/__main__.py +0 -0
- {modal-1.3.6.dev12 → modal-1.3.6.dev14}/pyproject.toml +0 -0
- {modal-1.3.6.dev12 → 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,9 +52,10 @@ 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
|
-
from ._utils.mount_utils import validate_network_file_systems, validate_volumes
|
|
58
|
+
from ._utils.mount_utils import validate_network_file_systems, validate_volumes, validate_volumes_by_object_id
|
|
60
59
|
from .call_graph import InputInfo, _reconstruct_call_graph
|
|
61
60
|
from .client import _Client
|
|
62
61
|
from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
@@ -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
|
|
|
@@ -966,6 +958,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
966
958
|
if image._metadata is not None:
|
|
967
959
|
mount_client_dependencies = image._metadata.image_builder_version > "2024.10"
|
|
968
960
|
|
|
961
|
+
# Validate that the same volume (by object_id) isn't mounted at multiple paths
|
|
962
|
+
# This validation happens here (at load time) because volumes need to be hydrated
|
|
963
|
+
# to have their object_id set.
|
|
964
|
+
validate_volumes_by_object_id(validated_volumes_no_cloud_buckets)
|
|
965
|
+
|
|
969
966
|
# Relies on dicts being ordered (true as of Python 3.6).
|
|
970
967
|
volume_mounts = [
|
|
971
968
|
api_pb2.VolumeMount(
|
|
@@ -1045,7 +1042,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1045
1042
|
_experimental_group_size=cluster_size or 0, # Experimental: Clustered functions
|
|
1046
1043
|
_experimental_concurrent_cancellations=True,
|
|
1047
1044
|
_experimental_proxy_ip=_experimental_proxy_ip,
|
|
1048
|
-
_experimental_custom_scaling=_experimental_custom_scaling_factor is not None,
|
|
1049
1045
|
# --- These are deprecated in favor of autoscaler_settings
|
|
1050
1046
|
warm_pool_size=min_containers or 0,
|
|
1051
1047
|
concurrency_limit=max_containers or 0,
|
|
@@ -1117,8 +1113,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1117
1113
|
function_data.ranked_functions.extend(ranked_functions)
|
|
1118
1114
|
function_definition = None # function_definition is not used in this case
|
|
1119
1115
|
else:
|
|
1120
|
-
# TODO(irfansharif): Assert on this specific type once we get rid of python 3.9.
|
|
1121
|
-
# assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
|
|
1122
1116
|
function_definition.resources.CopyFrom(
|
|
1123
1117
|
convert_fn_config_to_resources_config(
|
|
1124
1118
|
cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk, rdma=rdma
|
|
@@ -1165,7 +1159,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1165
1159
|
obj._webhook_config = webhook_config # only set locally
|
|
1166
1160
|
|
|
1167
1161
|
# Used to check whether we should rebuild a modal.Image which uses `run_function`.
|
|
1168
|
-
gpus: list[
|
|
1162
|
+
gpus: list[str] = gpu if isinstance(gpu, list) else [gpu] if gpu else []
|
|
1169
1163
|
obj._build_args = dict( # See get_build_def
|
|
1170
1164
|
secrets=repr(secrets),
|
|
1171
1165
|
gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
|
|
@@ -1235,6 +1229,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1235
1229
|
assert parent is not None and parent.is_hydrated
|
|
1236
1230
|
|
|
1237
1231
|
if options:
|
|
1232
|
+
# Validate that the same volume (by object_id) isn't mounted at multiple paths
|
|
1233
|
+
validate_volumes_by_object_id(options.validated_volumes)
|
|
1234
|
+
|
|
1238
1235
|
volume_mounts = [
|
|
1239
1236
|
api_pb2.VolumeMount(
|
|
1240
1237
|
mount_path=path,
|
|
@@ -1350,43 +1347,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1350
1347
|
# One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
|
|
1351
1348
|
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
1352
1349
|
|
|
1353
|
-
@live_method
|
|
1354
|
-
async def keep_warm(self, warm_pool_size: int) -> None:
|
|
1355
|
-
"""mdmd:hidden
|
|
1356
|
-
Set the warm pool size for the Function.
|
|
1357
|
-
|
|
1358
|
-
DEPRECATED: Please adapt your code to use the more general `update_autoscaler` method instead:
|
|
1359
|
-
|
|
1360
|
-
```python notest
|
|
1361
|
-
f = modal.Function.from_name("my-app", "function")
|
|
1362
|
-
|
|
1363
|
-
# Old pattern (deprecated)
|
|
1364
|
-
f.keep_warm(2)
|
|
1365
|
-
|
|
1366
|
-
# New pattern
|
|
1367
|
-
f.update_autoscaler(min_containers=2)
|
|
1368
|
-
```
|
|
1369
|
-
"""
|
|
1370
|
-
if self._is_method:
|
|
1371
|
-
raise InvalidError(
|
|
1372
|
-
textwrap.dedent(
|
|
1373
|
-
"""
|
|
1374
|
-
The `.keep_warm()` method can not be used on Modal class *methods*.
|
|
1375
|
-
|
|
1376
|
-
Call `.keep_warm()` on the class *instance* instead. All methods of a class are run by the same
|
|
1377
|
-
container pool, and this method applies to the size of that container pool.
|
|
1378
|
-
"""
|
|
1379
|
-
)
|
|
1380
|
-
)
|
|
1381
|
-
|
|
1382
|
-
deprecation_warning(
|
|
1383
|
-
(2025, 5, 5),
|
|
1384
|
-
"The .keep_warm() method has been deprecated in favor of the more general "
|
|
1385
|
-
".update_autoscaler(min_containers=...) method.",
|
|
1386
|
-
show_source=True,
|
|
1387
|
-
)
|
|
1388
|
-
await self.update_autoscaler(min_containers=warm_pool_size)
|
|
1389
|
-
|
|
1390
1350
|
@classmethod
|
|
1391
1351
|
def _from_name(
|
|
1392
1352
|
cls,
|
|
@@ -1436,7 +1396,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1436
1396
|
app_name: str,
|
|
1437
1397
|
name: str,
|
|
1438
1398
|
*,
|
|
1439
|
-
namespace=None, # mdmd:line-hidden
|
|
1440
1399
|
environment_name: Optional[str] = None,
|
|
1441
1400
|
client: Optional[_Client] = None,
|
|
1442
1401
|
) -> "_Function":
|
|
@@ -1451,17 +1410,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1451
1410
|
```
|
|
1452
1411
|
"""
|
|
1453
1412
|
if "." in name:
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
"Looking up class methods using Function.from_name will be deprecated"
|
|
1458
|
-
" in a future version of Modal.\nUse modal.Cls.from_name instead, e.g.\n\n"
|
|
1459
|
-
f'{class_name} = modal.Cls.from_name("{app_name}", "{class_name}")\n'
|
|
1460
|
-
f"instance = {class_name}(...)\n"
|
|
1461
|
-
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."
|
|
1462
1416
|
)
|
|
1463
1417
|
|
|
1464
|
-
warn_if_passing_namespace(namespace, "modal.Function.from_name")
|
|
1465
1418
|
return cls._from_name(
|
|
1466
1419
|
app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
|
|
1467
1420
|
)
|
|
@@ -1579,21 +1532,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1579
1532
|
+ f"or call it locally: {self._function_name}.local()"
|
|
1580
1533
|
)
|
|
1581
1534
|
|
|
1582
|
-
# TODO (live_method on properties is not great, since it could be blocking the event loop from async contexts)
|
|
1583
|
-
@property
|
|
1584
|
-
@live_method
|
|
1585
|
-
async def web_url(self) -> Optional[str]:
|
|
1586
|
-
"""mdmd:hidden
|
|
1587
|
-
Deprecated. Use the `Function.get_web_url()` method instead.
|
|
1588
|
-
|
|
1589
|
-
URL of a Function running as a web endpoint.
|
|
1590
|
-
"""
|
|
1591
|
-
deprecation_msg = """The Function.web_url property will be removed in a future version of Modal.
|
|
1592
|
-
Use the `Function.get_web_url()` method instead.
|
|
1593
|
-
"""
|
|
1594
|
-
deprecation_warning((2025, 5, 6), deprecation_msg, pending=True)
|
|
1595
|
-
return self._web_url
|
|
1596
|
-
|
|
1597
1535
|
@live_method
|
|
1598
1536
|
async def get_web_url(self) -> Optional[str]:
|
|
1599
1537
|
"""URL of a Function running as a web endpoint."""
|
|
@@ -2102,13 +2040,3 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2102
2040
|
except Exception as exc:
|
|
2103
2041
|
# TODO: kill all running function calls
|
|
2104
2042
|
raise exc
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
async def _gather(*function_calls: _FunctionCall[T]) -> typing.Sequence[T]:
|
|
2108
|
-
"""mdmd:hidden
|
|
2109
|
-
Deprecated: Please use `modal.FunctionCall.gather()` instead."""
|
|
2110
|
-
deprecation_warning(
|
|
2111
|
-
(2025, 2, 24),
|
|
2112
|
-
"`modal.functions.gather()` is deprecated; please use `modal.FunctionCall.gather()` instead.",
|
|
2113
|
-
)
|
|
2114
|
-
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`")
|
|
@@ -101,3 +101,23 @@ def validate_only_modal_volumes(
|
|
|
101
101
|
raise InvalidError(f"{caller_name} only supports volumes that are modal.Volume")
|
|
102
102
|
|
|
103
103
|
return validated_volumes
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def validate_volumes_by_object_id(validated_volumes: Sequence[tuple[str, _Volume]]) -> None:
|
|
107
|
+
"""Validate that the same volume (by object_id) is not mounted at multiple paths.
|
|
108
|
+
|
|
109
|
+
This validation happens at load time after volumes are hydrated and have object_ids.
|
|
110
|
+
"""
|
|
111
|
+
object_id_to_paths: dict[str, list[str]] = {}
|
|
112
|
+
for path, volume in validated_volumes:
|
|
113
|
+
if not volume.is_hydrated:
|
|
114
|
+
# This should never happen since this function is only called at load time
|
|
115
|
+
raise RuntimeError(f"Internal error: Volume at '{path}' is not hydrated when validating mounts")
|
|
116
|
+
object_id_to_paths.setdefault(volume.object_id, []).append(path)
|
|
117
|
+
|
|
118
|
+
for object_id, paths in object_id_to_paths.items():
|
|
119
|
+
if len(paths) > 1:
|
|
120
|
+
conflicting = ", ".join(sorted(paths))
|
|
121
|
+
raise InvalidError(
|
|
122
|
+
f"The same Volume cannot be mounted in multiple locations for the same function: {conflicting}"
|
|
123
|
+
)
|