modal 1.2.3.dev8__tar.gz → 1.2.3.dev10__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.2.3.dev8 → modal-1.2.3.dev10}/PKG-INFO +1 -1
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_functions.py +9 -4
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/grpc_utils.py +57 -4
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/app.py +47 -21
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/app.pyi +8 -4
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/run.py +1 -1
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/client.pyi +2 -2
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cls.py +5 -3
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/functions.pyi +8 -8
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/image.py +1 -4
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/sandbox.py +6 -15
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/sandbox.pyi +0 -9
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api.proto +13 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_grpc.py +1 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2.py +1032 -1021
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2.pyi +29 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_version/__init__.py +1 -1
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/LICENSE +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/README.md +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/__main__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_billing.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_clustered_functions.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_grpc_client.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_ipython.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_load_context.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_location.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_object.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_output.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_partial_function.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_pty.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_resolver.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_resources.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_serialization.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_traceback.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_tunnel.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_tunnel.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_type_manager.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/logger.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/_watcher.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/billing.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/README.md +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/builder/base-images.json +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/call_graph.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/_download.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/_traceback.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/app.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/cluster.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/config.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/container.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/dict.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/entry_point.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/environment.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/import_refs.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/launch.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/profile.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/queues.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/secret.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/token.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/utils.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cli/volume.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/client.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/cls.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/config.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/container_process.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/container_process.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/dict.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/dict.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/environments.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/environments.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/exception.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/flash.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/experimental/ipython.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/file_io.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/file_io.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/functions.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/gpu.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/image.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/io_streams.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/io_streams.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/mount.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/mount.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/network_file_system.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/network_file_system.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/object.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/object.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/output.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/parallel_map.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/parallel_map.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/partial_function.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/partial_function.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/proxy.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/proxy.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/py.typed +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/queue.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/queue.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/retries.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/runner.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/runner.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/running_app.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/schedule.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/scheduler_placement.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/secret.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/secret.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/serving.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/serving.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/snapshot.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/snapshot.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/stream_type.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/token_flow.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/token_flow.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/volume.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal/volume.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/__init__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/py.typed +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/modal_version/__main__.py +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/pyproject.toml +0 -0
- {modal-1.2.3.dev8 → modal-1.2.3.dev10}/setup.cfg +0 -0
|
@@ -90,7 +90,6 @@ from .parallel_map import (
|
|
|
90
90
|
from .proxy import _Proxy
|
|
91
91
|
from .retries import Retries, RetryManager
|
|
92
92
|
from .schedule import Schedule
|
|
93
|
-
from .scheduler_placement import SchedulerPlacement
|
|
94
93
|
from .secret import _Secret
|
|
95
94
|
from .volume import _Volume
|
|
96
95
|
|
|
@@ -589,7 +588,7 @@ class _FunctionSpec:
|
|
|
589
588
|
cpu: Optional[Union[float, tuple[float, float]]]
|
|
590
589
|
memory: Optional[Union[int, tuple[int, int]]]
|
|
591
590
|
ephemeral_disk: Optional[int]
|
|
592
|
-
scheduler_placement: Optional[SchedulerPlacement]
|
|
591
|
+
scheduler_placement: Optional[api_pb2.SchedulerPlacement]
|
|
593
592
|
proxy: Optional[_Proxy]
|
|
594
593
|
|
|
595
594
|
|
|
@@ -683,7 +682,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
683
682
|
batch_max_size: Optional[int] = None,
|
|
684
683
|
batch_wait_ms: Optional[int] = None,
|
|
685
684
|
cloud: Optional[str] = None,
|
|
686
|
-
|
|
685
|
+
region: Optional[Union[str, Sequence[str]]] = None,
|
|
686
|
+
nonpreemptible: bool = False,
|
|
687
687
|
is_builder_function: bool = False,
|
|
688
688
|
is_auto_snapshot: bool = False,
|
|
689
689
|
enable_memory_snapshot: bool = False,
|
|
@@ -746,6 +746,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
746
746
|
if env:
|
|
747
747
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
748
748
|
|
|
749
|
+
scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
|
|
750
|
+
if region or nonpreemptible:
|
|
751
|
+
regions = [region] if isinstance(region, str) else (list(region) if region else None)
|
|
752
|
+
scheduler_placement = api_pb2.SchedulerPlacement(regions=regions, nonpreemptible=nonpreemptible)
|
|
753
|
+
|
|
749
754
|
function_spec = _FunctionSpec(
|
|
750
755
|
mounts=all_mounts,
|
|
751
756
|
secrets=secrets,
|
|
@@ -1018,7 +1023,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1018
1023
|
untrusted=restrict_modal_access,
|
|
1019
1024
|
max_inputs=max_inputs or 0,
|
|
1020
1025
|
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
1021
|
-
scheduler_placement=scheduler_placement
|
|
1026
|
+
scheduler_placement=scheduler_placement,
|
|
1022
1027
|
is_class=info.is_service_class(),
|
|
1023
1028
|
class_parameter_info=info.class_parameter_info(),
|
|
1024
1029
|
i6pn_enabled=i6pn_enabled,
|
|
@@ -9,7 +9,8 @@ import urllib.parse
|
|
|
9
9
|
import uuid
|
|
10
10
|
from collections.abc import AsyncIterator
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
|
-
from
|
|
12
|
+
from functools import cache
|
|
13
|
+
from typing import Any, Optional, Sequence, TypeVar
|
|
13
14
|
|
|
14
15
|
import grpclib.client
|
|
15
16
|
import grpclib.config
|
|
@@ -17,11 +18,14 @@ import grpclib.events
|
|
|
17
18
|
import grpclib.protocol
|
|
18
19
|
import grpclib.stream
|
|
19
20
|
from google.protobuf.message import Message
|
|
21
|
+
from google.protobuf.symbol_database import SymbolDatabase
|
|
20
22
|
from grpclib import GRPCError, Status
|
|
23
|
+
from grpclib.encoding.base import StatusDetailsCodecBase
|
|
21
24
|
from grpclib.exceptions import StreamTerminatedError
|
|
22
25
|
from grpclib.protocol import H2Protocol
|
|
23
26
|
|
|
24
27
|
from modal.exception import AuthError, ConnectionError
|
|
28
|
+
from modal_proto import api_pb2
|
|
25
29
|
from modal_version import __version__
|
|
26
30
|
|
|
27
31
|
from .._traceback import suppress_tb_frames
|
|
@@ -107,6 +111,56 @@ class ConnectionManager:
|
|
|
107
111
|
self._channels.clear()
|
|
108
112
|
|
|
109
113
|
|
|
114
|
+
@cache
|
|
115
|
+
def _sym_db() -> SymbolDatabase:
|
|
116
|
+
from google.protobuf.symbol_database import Default
|
|
117
|
+
|
|
118
|
+
return Default()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class CustomProtoStatusDetailsCodec(StatusDetailsCodecBase):
|
|
122
|
+
"""grpclib compatible details codec.
|
|
123
|
+
|
|
124
|
+
The server can encode the details using `google.rpc.Status` using grpclib's default codec and this custom codec
|
|
125
|
+
can decode it into a `api_pb2.RPCStatus`.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def encode(
|
|
129
|
+
self,
|
|
130
|
+
status: Status,
|
|
131
|
+
message: Optional[str],
|
|
132
|
+
details: Optional[Sequence[Message]],
|
|
133
|
+
) -> bytes:
|
|
134
|
+
details_proto = api_pb2.RPCStatus(code=status.value, message=message or "")
|
|
135
|
+
if details is not None:
|
|
136
|
+
for detail in details:
|
|
137
|
+
detail_container = details_proto.details.add()
|
|
138
|
+
detail_container.Pack(detail)
|
|
139
|
+
return details_proto.SerializeToString()
|
|
140
|
+
|
|
141
|
+
def decode(
|
|
142
|
+
self,
|
|
143
|
+
status: Status,
|
|
144
|
+
message: Optional[str],
|
|
145
|
+
data: bytes,
|
|
146
|
+
) -> Any:
|
|
147
|
+
sym_db = _sym_db()
|
|
148
|
+
details_proto = api_pb2.RPCStatus.FromString(data)
|
|
149
|
+
|
|
150
|
+
details = []
|
|
151
|
+
for detail_container in details_proto.details:
|
|
152
|
+
# If we do not know how to decode an emssage, we'll ignore it.
|
|
153
|
+
with contextlib.suppress(Exception):
|
|
154
|
+
msg_type = sym_db.GetSymbol(detail_container.TypeName())
|
|
155
|
+
detail = msg_type()
|
|
156
|
+
detail_container.Unpack(detail)
|
|
157
|
+
details.append(detail)
|
|
158
|
+
return details
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
custom_detail_codec = CustomProtoStatusDetailsCodec()
|
|
162
|
+
|
|
163
|
+
|
|
110
164
|
def create_channel(
|
|
111
165
|
server_url: str,
|
|
112
166
|
metadata: dict[str, str] = {},
|
|
@@ -125,7 +179,7 @@ def create_channel(
|
|
|
125
179
|
)
|
|
126
180
|
|
|
127
181
|
if o.scheme == "unix":
|
|
128
|
-
channel = grpclib.client.Channel(path=o.path, config=config)
|
|
182
|
+
channel = grpclib.client.Channel(path=o.path, config=config, status_details_codec=custom_detail_codec)
|
|
129
183
|
elif o.scheme in ("http", "https"):
|
|
130
184
|
target = o.netloc
|
|
131
185
|
parts = target.split(":")
|
|
@@ -133,7 +187,7 @@ def create_channel(
|
|
|
133
187
|
ssl = o.scheme.endswith("s")
|
|
134
188
|
host = parts[0]
|
|
135
189
|
port = int(parts[1]) if len(parts) == 2 else 443 if ssl else 80
|
|
136
|
-
channel = grpclib.client.Channel(host, port, ssl=ssl, config=config)
|
|
190
|
+
channel = grpclib.client.Channel(host, port, ssl=ssl, config=config, status_details_codec=custom_detail_codec)
|
|
137
191
|
else:
|
|
138
192
|
raise Exception(f"Unknown scheme: {o.scheme}")
|
|
139
193
|
|
|
@@ -258,7 +312,6 @@ async def _retry_transient_errors(
|
|
|
258
312
|
raise AuthError(exc.message)
|
|
259
313
|
else:
|
|
260
314
|
raise exc
|
|
261
|
-
|
|
262
315
|
if retry.max_retries is not None and n_retries >= retry.max_retries:
|
|
263
316
|
final_attempt = True
|
|
264
317
|
elif total_deadline is not None and time.time() + delay + retry.attempt_timeout_floor >= total_deadline:
|
|
@@ -719,6 +719,7 @@ class _App:
|
|
|
719
719
|
] = None, # Set this to True if it's a non-generator function returning a [sync/async] generator object
|
|
720
720
|
cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
|
|
721
721
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
|
|
722
|
+
nonpreemptible: bool = False, # Whether to run the function on a nonpreemptible instance.
|
|
722
723
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
723
724
|
block_network: bool = False, # Whether to block network access
|
|
724
725
|
restrict_modal_access: bool = False, # Whether to allow this function access to other Modal resources
|
|
@@ -731,9 +732,6 @@ class _App:
|
|
|
731
732
|
include_source: Optional[bool] = None,
|
|
732
733
|
experimental_options: Optional[dict[str, Any]] = None,
|
|
733
734
|
# Parameters below here are experimental. Use with caution!
|
|
734
|
-
_experimental_scheduler_placement: Optional[
|
|
735
|
-
SchedulerPlacement
|
|
736
|
-
] = None, # Experimental controls over fine-grained scheduling (alpha).
|
|
737
735
|
_experimental_proxy_ip: Optional[str] = None, # IP address of proxy
|
|
738
736
|
_experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
|
|
739
737
|
_experimental_restrict_output: bool = False, # Don't use pickle for return values
|
|
@@ -743,6 +741,8 @@ class _App:
|
|
|
743
741
|
container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
|
|
744
742
|
allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
|
|
745
743
|
_experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
|
|
744
|
+
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
|
|
745
|
+
# using `region` and `nonpreemptible`
|
|
746
746
|
) -> _FunctionDecoratorType:
|
|
747
747
|
"""Decorator to register a new Modal Function with this App."""
|
|
748
748
|
if isinstance(_warn_parentheses_missing, _Image):
|
|
@@ -762,6 +762,24 @@ class _App:
|
|
|
762
762
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
763
763
|
)
|
|
764
764
|
|
|
765
|
+
if _experimental_scheduler_placement is not None:
|
|
766
|
+
deprecation_warning(
|
|
767
|
+
(2025, 11, 17),
|
|
768
|
+
"The `_experimental_scheduler_placement` parameter is deprecated."
|
|
769
|
+
" Please use the `region` and `nonpreemptible` parameters instead.",
|
|
770
|
+
)
|
|
771
|
+
if region is not None or nonpreemptible:
|
|
772
|
+
raise InvalidError(
|
|
773
|
+
"Cannot use `_experimental_scheduler_placement` together with "
|
|
774
|
+
"`region` or `nonpreemptible` parameters."
|
|
775
|
+
)
|
|
776
|
+
# Extract regions and lifecycle from scheduler placement
|
|
777
|
+
if _experimental_scheduler_placement.proto.regions:
|
|
778
|
+
region = list(_experimental_scheduler_placement.proto.regions)
|
|
779
|
+
if _experimental_scheduler_placement.proto._lifecycle:
|
|
780
|
+
# Convert lifecycle to nonpreemptible: "on-demand" -> True, "spot" -> False
|
|
781
|
+
nonpreemptible = _experimental_scheduler_placement.proto._lifecycle == "on-demand"
|
|
782
|
+
|
|
765
783
|
secrets = secrets or []
|
|
766
784
|
if env:
|
|
767
785
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
@@ -771,7 +789,7 @@ class _App:
|
|
|
771
789
|
def wrapped(
|
|
772
790
|
f: Union[_PartialFunction, Callable[..., Any], None],
|
|
773
791
|
) -> _Function:
|
|
774
|
-
nonlocal is_generator, cloud, serialized
|
|
792
|
+
nonlocal is_generator, cloud, serialized, region, nonpreemptible
|
|
775
793
|
|
|
776
794
|
# Check if the decorated object is a class
|
|
777
795
|
if inspect.isclass(f):
|
|
@@ -860,12 +878,6 @@ class _App:
|
|
|
860
878
|
if is_generator is None:
|
|
861
879
|
is_generator = inspect.isgeneratorfunction(raw_f) or inspect.isasyncgenfunction(raw_f)
|
|
862
880
|
|
|
863
|
-
scheduler_placement: Optional[SchedulerPlacement] = _experimental_scheduler_placement
|
|
864
|
-
if region:
|
|
865
|
-
if scheduler_placement:
|
|
866
|
-
raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
|
|
867
|
-
scheduler_placement = SchedulerPlacement(region=region)
|
|
868
|
-
|
|
869
881
|
function = _Function.from_local(
|
|
870
882
|
info,
|
|
871
883
|
app=self,
|
|
@@ -892,12 +904,13 @@ class _App:
|
|
|
892
904
|
timeout=timeout,
|
|
893
905
|
startup_timeout=startup_timeout or timeout,
|
|
894
906
|
cloud=cloud,
|
|
907
|
+
region=region,
|
|
908
|
+
nonpreemptible=nonpreemptible,
|
|
895
909
|
webhook_config=webhook_config,
|
|
896
910
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
897
911
|
block_network=block_network,
|
|
898
912
|
restrict_modal_access=restrict_modal_access,
|
|
899
913
|
max_inputs=max_inputs,
|
|
900
|
-
scheduler_placement=scheduler_placement,
|
|
901
914
|
i6pn_enabled=i6pn_enabled,
|
|
902
915
|
cluster_size=cluster_size, # Experimental: Clustered functions
|
|
903
916
|
rdma=rdma,
|
|
@@ -950,6 +963,7 @@ class _App:
|
|
|
950
963
|
startup_timeout: Optional[int] = None, # Maximum startup time in seconds with higher precedence than `timeout`.
|
|
951
964
|
cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
|
|
952
965
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
|
|
966
|
+
nonpreemptible: bool = False, # Whether to run the function on a non-preemptible instance.
|
|
953
967
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
954
968
|
block_network: bool = False, # Whether to block network access
|
|
955
969
|
restrict_modal_access: bool = False, # Whether to allow this class access to other Modal resources
|
|
@@ -960,9 +974,6 @@ class _App:
|
|
|
960
974
|
include_source: Optional[bool] = None, # When `False`, don't automatically add the App source to the container.
|
|
961
975
|
experimental_options: Optional[dict[str, Any]] = None,
|
|
962
976
|
# Parameters below here are experimental. Use with caution!
|
|
963
|
-
_experimental_scheduler_placement: Optional[
|
|
964
|
-
SchedulerPlacement
|
|
965
|
-
] = None, # Experimental controls over fine-grained scheduling (alpha).
|
|
966
977
|
_experimental_proxy_ip: Optional[str] = None, # IP address of proxy
|
|
967
978
|
_experimental_custom_scaling_factor: Optional[float] = None, # Custom scaling factor
|
|
968
979
|
_experimental_restrict_output: bool = False, # Don't use pickle for return values
|
|
@@ -972,6 +983,8 @@ class _App:
|
|
|
972
983
|
container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
|
|
973
984
|
allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
|
|
974
985
|
_experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
|
|
986
|
+
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
|
|
987
|
+
# using `region` and `nonpreemptible`
|
|
975
988
|
) -> Callable[[Union[CLS_T, _PartialFunction]], CLS_T]:
|
|
976
989
|
"""
|
|
977
990
|
Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App.
|
|
@@ -979,12 +992,6 @@ class _App:
|
|
|
979
992
|
if _warn_parentheses_missing:
|
|
980
993
|
raise InvalidError("Did you forget parentheses? Suggestion: `@app.cls()`.")
|
|
981
994
|
|
|
982
|
-
scheduler_placement = _experimental_scheduler_placement
|
|
983
|
-
if region:
|
|
984
|
-
if scheduler_placement:
|
|
985
|
-
raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
|
|
986
|
-
scheduler_placement = SchedulerPlacement(region=region)
|
|
987
|
-
|
|
988
995
|
if allow_concurrent_inputs is not None:
|
|
989
996
|
deprecation_warning(
|
|
990
997
|
(2025, 4, 9),
|
|
@@ -993,6 +1000,24 @@ class _App:
|
|
|
993
1000
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
994
1001
|
)
|
|
995
1002
|
|
|
1003
|
+
if _experimental_scheduler_placement is not None:
|
|
1004
|
+
deprecation_warning(
|
|
1005
|
+
(2025, 11, 17),
|
|
1006
|
+
"The `_experimental_scheduler_placement` parameter is deprecated."
|
|
1007
|
+
" Please use the `region` and `nonpreemptible` parameters instead.",
|
|
1008
|
+
)
|
|
1009
|
+
if region is not None or nonpreemptible:
|
|
1010
|
+
raise InvalidError(
|
|
1011
|
+
"Cannot use `_experimental_scheduler_placement` together with "
|
|
1012
|
+
"`region` or `nonpreemptible` parameters."
|
|
1013
|
+
)
|
|
1014
|
+
# Extract regions and lifecycle from scheduler placement
|
|
1015
|
+
if _experimental_scheduler_placement.proto.regions:
|
|
1016
|
+
region = list(_experimental_scheduler_placement.proto.regions)
|
|
1017
|
+
if _experimental_scheduler_placement.proto._lifecycle:
|
|
1018
|
+
# Convert lifecycle to nonpreemptible: "on-demand" -> True, "spot" -> False
|
|
1019
|
+
nonpreemptible = _experimental_scheduler_placement.proto._lifecycle == "on-demand"
|
|
1020
|
+
|
|
996
1021
|
secrets = secrets or []
|
|
997
1022
|
if env:
|
|
998
1023
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
@@ -1086,11 +1111,12 @@ class _App:
|
|
|
1086
1111
|
timeout=timeout,
|
|
1087
1112
|
startup_timeout=startup_timeout or timeout,
|
|
1088
1113
|
cloud=cloud,
|
|
1114
|
+
region=region,
|
|
1115
|
+
nonpreemptible=nonpreemptible,
|
|
1089
1116
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
1090
1117
|
block_network=block_network,
|
|
1091
1118
|
restrict_modal_access=restrict_modal_access,
|
|
1092
1119
|
max_inputs=max_inputs,
|
|
1093
|
-
scheduler_placement=scheduler_placement,
|
|
1094
1120
|
i6pn_enabled=i6pn_enabled,
|
|
1095
1121
|
cluster_size=cluster_size,
|
|
1096
1122
|
rdma=rdma,
|
|
@@ -487,6 +487,7 @@ class _App:
|
|
|
487
487
|
is_generator: typing.Optional[bool] = None,
|
|
488
488
|
cloud: typing.Optional[str] = None,
|
|
489
489
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
490
|
+
nonpreemptible: bool = False,
|
|
490
491
|
enable_memory_snapshot: bool = False,
|
|
491
492
|
block_network: bool = False,
|
|
492
493
|
restrict_modal_access: bool = False,
|
|
@@ -494,7 +495,6 @@ class _App:
|
|
|
494
495
|
i6pn: typing.Optional[bool] = None,
|
|
495
496
|
include_source: typing.Optional[bool] = None,
|
|
496
497
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
497
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
498
498
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
499
499
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
500
500
|
_experimental_restrict_output: bool = False,
|
|
@@ -503,6 +503,7 @@ class _App:
|
|
|
503
503
|
container_idle_timeout: typing.Optional[int] = None,
|
|
504
504
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
505
505
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
506
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
506
507
|
) -> _FunctionDecoratorType:
|
|
507
508
|
"""Decorator to register a new Modal Function with this App."""
|
|
508
509
|
...
|
|
@@ -540,6 +541,7 @@ class _App:
|
|
|
540
541
|
startup_timeout: typing.Optional[int] = None,
|
|
541
542
|
cloud: typing.Optional[str] = None,
|
|
542
543
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
544
|
+
nonpreemptible: bool = False,
|
|
543
545
|
enable_memory_snapshot: bool = False,
|
|
544
546
|
block_network: bool = False,
|
|
545
547
|
restrict_modal_access: bool = False,
|
|
@@ -547,7 +549,6 @@ class _App:
|
|
|
547
549
|
i6pn: typing.Optional[bool] = None,
|
|
548
550
|
include_source: typing.Optional[bool] = None,
|
|
549
551
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
550
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
551
552
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
552
553
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
553
554
|
_experimental_restrict_output: bool = False,
|
|
@@ -556,6 +557,7 @@ class _App:
|
|
|
556
557
|
container_idle_timeout: typing.Optional[int] = None,
|
|
557
558
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
558
559
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
560
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
559
561
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
|
|
560
562
|
"""Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
|
|
561
563
|
...
|
|
@@ -1148,6 +1150,7 @@ class App:
|
|
|
1148
1150
|
is_generator: typing.Optional[bool] = None,
|
|
1149
1151
|
cloud: typing.Optional[str] = None,
|
|
1150
1152
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1153
|
+
nonpreemptible: bool = False,
|
|
1151
1154
|
enable_memory_snapshot: bool = False,
|
|
1152
1155
|
block_network: bool = False,
|
|
1153
1156
|
restrict_modal_access: bool = False,
|
|
@@ -1155,7 +1158,6 @@ class App:
|
|
|
1155
1158
|
i6pn: typing.Optional[bool] = None,
|
|
1156
1159
|
include_source: typing.Optional[bool] = None,
|
|
1157
1160
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1158
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1159
1161
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
1160
1162
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
1161
1163
|
_experimental_restrict_output: bool = False,
|
|
@@ -1164,6 +1166,7 @@ class App:
|
|
|
1164
1166
|
container_idle_timeout: typing.Optional[int] = None,
|
|
1165
1167
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
1166
1168
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
1169
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1167
1170
|
) -> _FunctionDecoratorType:
|
|
1168
1171
|
"""Decorator to register a new Modal Function with this App."""
|
|
1169
1172
|
...
|
|
@@ -1201,6 +1204,7 @@ class App:
|
|
|
1201
1204
|
startup_timeout: typing.Optional[int] = None,
|
|
1202
1205
|
cloud: typing.Optional[str] = None,
|
|
1203
1206
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1207
|
+
nonpreemptible: bool = False,
|
|
1204
1208
|
enable_memory_snapshot: bool = False,
|
|
1205
1209
|
block_network: bool = False,
|
|
1206
1210
|
restrict_modal_access: bool = False,
|
|
@@ -1208,7 +1212,6 @@ class App:
|
|
|
1208
1212
|
i6pn: typing.Optional[bool] = None,
|
|
1209
1213
|
include_source: typing.Optional[bool] = None,
|
|
1210
1214
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
1211
|
-
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1212
1215
|
_experimental_proxy_ip: typing.Optional[str] = None,
|
|
1213
1216
|
_experimental_custom_scaling_factor: typing.Optional[float] = None,
|
|
1214
1217
|
_experimental_restrict_output: bool = False,
|
|
@@ -1217,6 +1220,7 @@ class App:
|
|
|
1217
1220
|
container_idle_timeout: typing.Optional[int] = None,
|
|
1218
1221
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
1219
1222
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
1223
|
+
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1220
1224
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
|
|
1221
1225
|
"""Decorator to register a new Modal [Cls](https://modal.com/docs/reference/modal.Cls) with this App."""
|
|
1222
1226
|
...
|
|
@@ -693,7 +693,7 @@ def shell(
|
|
|
693
693
|
cpu=function_spec.cpu,
|
|
694
694
|
memory=function_spec.memory,
|
|
695
695
|
volumes=function_spec.volumes,
|
|
696
|
-
region=function_spec.scheduler_placement.
|
|
696
|
+
region=function_spec.scheduler_placement.regions if function_spec.scheduler_placement else None,
|
|
697
697
|
pty=pty,
|
|
698
698
|
proxy=function_spec.proxy,
|
|
699
699
|
)
|
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.2.3.
|
|
36
|
+
version: str = "1.2.3.dev10",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -164,7 +164,7 @@ class Client:
|
|
|
164
164
|
server_url: str,
|
|
165
165
|
client_type: int,
|
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
-
version: str = "1.2.3.
|
|
167
|
+
version: str = "1.2.3.dev10",
|
|
168
168
|
):
|
|
169
169
|
"""mdmd:hidden
|
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -37,7 +37,6 @@ from .cloud_bucket_mount import _CloudBucketMount
|
|
|
37
37
|
from .exception import ExecutionError, InvalidError, NotFoundError
|
|
38
38
|
from .gpu import GPU_T
|
|
39
39
|
from .retries import Retries
|
|
40
|
-
from .scheduler_placement import SchedulerPlacement
|
|
41
40
|
from .secret import _Secret
|
|
42
41
|
from .volume import _Volume
|
|
43
42
|
|
|
@@ -746,8 +745,6 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
746
745
|
else:
|
|
747
746
|
resources = None
|
|
748
747
|
|
|
749
|
-
scheduler_placement = SchedulerPlacement(region=region).proto if region else None
|
|
750
|
-
|
|
751
748
|
if allow_concurrent_inputs is not None:
|
|
752
749
|
deprecation_warning(
|
|
753
750
|
(2025, 5, 9),
|
|
@@ -790,6 +787,11 @@ More information on class parameterization can be found here: https://modal.com/
|
|
|
790
787
|
if env:
|
|
791
788
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
792
789
|
|
|
790
|
+
scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
|
|
791
|
+
if region:
|
|
792
|
+
regions = [region] if isinstance(region, str) else list(region)
|
|
793
|
+
scheduler_placement = api_pb2.SchedulerPlacement(regions=regions)
|
|
794
|
+
|
|
793
795
|
new_options = _ServiceOptions(
|
|
794
796
|
secrets=secrets,
|
|
795
797
|
validated_volumes=validated_volumes_no_cloud_buckets,
|
|
@@ -18,7 +18,6 @@ import modal.parallel_map
|
|
|
18
18
|
import modal.proxy
|
|
19
19
|
import modal.retries
|
|
20
20
|
import modal.schedule
|
|
21
|
-
import modal.scheduler_placement
|
|
22
21
|
import modal.secret
|
|
23
22
|
import modal.volume
|
|
24
23
|
import modal_proto.api_pb2
|
|
@@ -97,7 +96,8 @@ class Function(
|
|
|
97
96
|
batch_max_size: typing.Optional[int] = None,
|
|
98
97
|
batch_wait_ms: typing.Optional[int] = None,
|
|
99
98
|
cloud: typing.Optional[str] = None,
|
|
100
|
-
|
|
99
|
+
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
100
|
+
nonpreemptible: bool = False,
|
|
101
101
|
is_builder_function: bool = False,
|
|
102
102
|
is_auto_snapshot: bool = False,
|
|
103
103
|
enable_memory_snapshot: bool = False,
|
|
@@ -408,7 +408,7 @@ class Function(
|
|
|
408
408
|
|
|
409
409
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
|
410
410
|
|
|
411
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
411
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
|
412
412
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
413
413
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
414
414
|
...
|
|
@@ -417,7 +417,7 @@ class Function(
|
|
|
417
417
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
418
418
|
...
|
|
419
419
|
|
|
420
|
-
remote: __remote_spec[modal._functions.
|
|
420
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
|
421
421
|
|
|
422
422
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
423
423
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -444,7 +444,7 @@ class Function(
|
|
|
444
444
|
"""
|
|
445
445
|
...
|
|
446
446
|
|
|
447
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
447
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
|
448
448
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
449
449
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
450
450
|
|
|
@@ -468,7 +468,7 @@ class Function(
|
|
|
468
468
|
...
|
|
469
469
|
|
|
470
470
|
_experimental_spawn: ___experimental_spawn_spec[
|
|
471
|
-
modal._functions.
|
|
471
|
+
modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
|
|
472
472
|
]
|
|
473
473
|
|
|
474
474
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
|
@@ -477,7 +477,7 @@ class Function(
|
|
|
477
477
|
|
|
478
478
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
|
479
479
|
|
|
480
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
480
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
|
481
481
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
482
482
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
483
483
|
|
|
@@ -498,7 +498,7 @@ class Function(
|
|
|
498
498
|
"""
|
|
499
499
|
...
|
|
500
500
|
|
|
501
|
-
spawn: __spawn_spec[modal._functions.
|
|
501
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
|
502
502
|
|
|
503
503
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
504
504
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -51,7 +51,6 @@ from .gpu import GPU_T, parse_gpu_config
|
|
|
51
51
|
from .mount import _Mount, python_standalone_mount_name
|
|
52
52
|
from .network_file_system import _NetworkFileSystem
|
|
53
53
|
from .output import _get_output_manager
|
|
54
|
-
from .scheduler_placement import SchedulerPlacement
|
|
55
54
|
from .secret import _Secret
|
|
56
55
|
from .volume import _Volume
|
|
57
56
|
|
|
@@ -2300,8 +2299,6 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2300
2299
|
# It may be possible to support lambdas eventually, but for now we don't handle them well, so reject quickly
|
|
2301
2300
|
raise InvalidError("Image.run_function does not support lambda functions.")
|
|
2302
2301
|
|
|
2303
|
-
scheduler_placement = SchedulerPlacement(region=region) if region else None
|
|
2304
|
-
|
|
2305
2302
|
info = FunctionInfo(raw_f)
|
|
2306
2303
|
|
|
2307
2304
|
function = _Function.from_local(
|
|
@@ -2313,7 +2310,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
2313
2310
|
volumes=volumes,
|
|
2314
2311
|
network_file_systems=network_file_systems,
|
|
2315
2312
|
cloud=cloud,
|
|
2316
|
-
|
|
2313
|
+
region=region,
|
|
2317
2314
|
memory=memory,
|
|
2318
2315
|
timeout=timeout,
|
|
2319
2316
|
cpu=cpu,
|
|
@@ -41,7 +41,6 @@ from .image import _Image
|
|
|
41
41
|
from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
|
|
42
42
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
|
43
43
|
from .proxy import _Proxy
|
|
44
|
-
from .scheduler_placement import SchedulerPlacement
|
|
45
44
|
from .secret import _Secret
|
|
46
45
|
from .snapshot import _SandboxSnapshot
|
|
47
46
|
from .stream_type import StreamType
|
|
@@ -148,7 +147,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
148
147
|
unencrypted_ports: Sequence[int] = [],
|
|
149
148
|
proxy: Optional[_Proxy] = None,
|
|
150
149
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
151
|
-
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None,
|
|
152
150
|
enable_snapshot: bool = False,
|
|
153
151
|
verbose: bool = False,
|
|
154
152
|
) -> "_Sandbox":
|
|
@@ -156,12 +154,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
156
154
|
|
|
157
155
|
validated_network_file_systems = validate_network_file_systems(network_file_systems)
|
|
158
156
|
|
|
159
|
-
scheduler_placement: Optional[SchedulerPlacement] = _experimental_scheduler_placement
|
|
160
|
-
if region:
|
|
161
|
-
if scheduler_placement:
|
|
162
|
-
raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
|
|
163
|
-
scheduler_placement = SchedulerPlacement(region=region)
|
|
164
|
-
|
|
165
157
|
if isinstance(gpu, list):
|
|
166
158
|
raise InvalidError(
|
|
167
159
|
"Sandboxes do not support configuring a list of GPUs. "
|
|
@@ -176,6 +168,11 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
176
168
|
cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
|
|
177
169
|
validated_volumes = [(k, v) for k, v in validated_volumes if isinstance(v, _Volume)]
|
|
178
170
|
|
|
171
|
+
scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
|
|
172
|
+
if region:
|
|
173
|
+
regions = [region] if isinstance(region, str) else (list(region) if region else None)
|
|
174
|
+
scheduler_placement = api_pb2.SchedulerPlacement(regions=regions)
|
|
175
|
+
|
|
179
176
|
if pty:
|
|
180
177
|
pty_info = _Sandbox._default_pty_info()
|
|
181
178
|
|
|
@@ -252,7 +249,7 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
252
249
|
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
253
250
|
volume_mounts=volume_mounts,
|
|
254
251
|
pty_info=pty_info,
|
|
255
|
-
scheduler_placement=scheduler_placement
|
|
252
|
+
scheduler_placement=scheduler_placement,
|
|
256
253
|
worker_id=config.get("worker_id"),
|
|
257
254
|
open_ports=api_pb2.PortSpecs(ports=open_ports),
|
|
258
255
|
network_access=network_access,
|
|
@@ -320,9 +317,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
320
317
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
321
318
|
# Enable memory snapshots.
|
|
322
319
|
_experimental_enable_snapshot: bool = False,
|
|
323
|
-
_experimental_scheduler_placement: Optional[
|
|
324
|
-
SchedulerPlacement
|
|
325
|
-
] = None, # Experimental controls over fine-grained scheduling (alpha).
|
|
326
320
|
client: Optional[_Client] = None,
|
|
327
321
|
environment_name: Optional[str] = None, # *DEPRECATED* Optionally override the default environment
|
|
328
322
|
pty_info: Optional[api_pb2.PTYInfo] = None, # *DEPRECATED* Use `pty` instead. `pty` will override `pty_info`.
|
|
@@ -384,7 +378,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
384
378
|
proxy=proxy,
|
|
385
379
|
experimental_options=experimental_options,
|
|
386
380
|
_experimental_enable_snapshot=_experimental_enable_snapshot,
|
|
387
|
-
_experimental_scheduler_placement=_experimental_scheduler_placement,
|
|
388
381
|
client=client,
|
|
389
382
|
verbose=verbose,
|
|
390
383
|
pty_info=pty_info,
|
|
@@ -418,7 +411,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
418
411
|
proxy: Optional[_Proxy] = None,
|
|
419
412
|
experimental_options: Optional[dict[str, bool]] = None,
|
|
420
413
|
_experimental_enable_snapshot: bool = False,
|
|
421
|
-
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None,
|
|
422
414
|
client: Optional[_Client] = None,
|
|
423
415
|
verbose: bool = False,
|
|
424
416
|
pty_info: Optional[api_pb2.PTYInfo] = None,
|
|
@@ -468,7 +460,6 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
|
468
460
|
unencrypted_ports=unencrypted_ports,
|
|
469
461
|
proxy=proxy,
|
|
470
462
|
experimental_options=experimental_options,
|
|
471
|
-
_experimental_scheduler_placement=_experimental_scheduler_placement,
|
|
472
463
|
enable_snapshot=_experimental_enable_snapshot,
|
|
473
464
|
verbose=verbose,
|
|
474
465
|
)
|