modAL 1.2.7.dev6__tar.gz → 1.2.7.dev9__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.7.dev6 → modal-1.2.7.dev9}/PKG-INFO +1 -1
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_functions.py +3 -10
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/container_io_manager.py +3 -2
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/blob_utils.py +5 -4
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/app.py +34 -8
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/app.pyi +8 -4
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/client.pyi +2 -2
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/file_io.py +22 -10
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/functions.pyi +1 -1
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/io_streams.py +10 -7
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/api.proto +5 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/api_pb2.py +656 -656
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/api_pb2.pyi +8 -1
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_version/__init__.py +1 -1
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/LICENSE +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/README.md +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/__main__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_billing.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_clustered_functions.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_grpc_client.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_ipython.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_load_context.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_location.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_object.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_output.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_partial_function.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_pty.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_resolver.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_resources.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_serialization.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_traceback.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_tunnel.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_tunnel.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_type_manager.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/logger.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/_watcher.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/billing.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/README.md +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/builder/base-images.json +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/call_graph.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/_download.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/_traceback.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/app.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/cluster.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/config.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/container.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/dict.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/entry_point.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/environment.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/import_refs.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/launch.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/profile.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/queues.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/run.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/secret.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/shell.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/token.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/utils.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cli/volume.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/client.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cls.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/cls.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/config.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/container_process.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/container_process.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/dict.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/dict.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/environments.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/environments.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/exception.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/experimental/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/experimental/flash.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/experimental/ipython.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/file_io.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/functions.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/gpu.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/image.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/image.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/io_streams.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/mount.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/mount.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/network_file_system.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/network_file_system.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/object.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/object.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/output.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/parallel_map.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/parallel_map.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/partial_function.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/partial_function.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/proxy.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/proxy.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/py.typed +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/queue.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/queue.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/retries.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/runner.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/runner.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/running_app.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/sandbox.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/sandbox.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/schedule.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/scheduler_placement.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/secret.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/secret.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/serving.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/serving.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/snapshot.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/snapshot.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/stream_type.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/token_flow.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/token_flow.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/volume.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal/volume.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_docs/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/__init__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/py.typed +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/modal_version/__main__.py +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/pyproject.toml +0 -0
- {modal-1.2.7.dev6 → modal-1.2.7.dev9}/setup.cfg +0 -0
|
@@ -694,7 +694,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
694
694
|
# Experimental: Clustered functions
|
|
695
695
|
cluster_size: Optional[int] = None,
|
|
696
696
|
rdma: Optional[bool] = None,
|
|
697
|
-
|
|
697
|
+
single_use_containers: bool = False,
|
|
698
698
|
ephemeral_disk: Optional[int] = None,
|
|
699
699
|
include_source: bool = True,
|
|
700
700
|
experimental_options: Optional[dict[str, str]] = None,
|
|
@@ -810,14 +810,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
810
810
|
if arg.default is not inspect.Parameter.empty:
|
|
811
811
|
raise InvalidError(f"Modal batched function {func_name} does not accept default arguments.")
|
|
812
812
|
|
|
813
|
-
if max_inputs is not None:
|
|
814
|
-
if not isinstance(max_inputs, int):
|
|
815
|
-
raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
|
|
816
|
-
if max_inputs <= 0:
|
|
817
|
-
raise InvalidError("`max_inputs` must be positive")
|
|
818
|
-
if max_inputs > 1:
|
|
819
|
-
raise InvalidError("Only `max_inputs=1` is currently supported")
|
|
820
|
-
|
|
821
813
|
# Validate volumes
|
|
822
814
|
validated_volumes = validate_volumes(volumes)
|
|
823
815
|
cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
|
|
@@ -1024,7 +1016,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1024
1016
|
object_dependencies=object_dependencies,
|
|
1025
1017
|
block_network=block_network,
|
|
1026
1018
|
untrusted=restrict_modal_access,
|
|
1027
|
-
|
|
1019
|
+
single_use_containers=single_use_containers,
|
|
1020
|
+
max_inputs=int(single_use_containers), # TODO(michael) remove after worker rollover
|
|
1028
1021
|
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
1029
1022
|
scheduler_placement=scheduler_placement,
|
|
1030
1023
|
is_class=info.is_service_class(),
|
|
@@ -845,8 +845,9 @@ class _ContainerIOManager:
|
|
|
845
845
|
yield inputs
|
|
846
846
|
yielded = True
|
|
847
847
|
|
|
848
|
-
#
|
|
849
|
-
|
|
848
|
+
# TODO(michael): Remove use of max_inputs after worker rollover
|
|
849
|
+
single_use_container = self.function_def.single_use_containers or self.function_def.max_inputs == 1
|
|
850
|
+
if final_input_received or single_use_container:
|
|
850
851
|
return
|
|
851
852
|
finally:
|
|
852
853
|
if not yielded:
|
|
@@ -152,12 +152,13 @@ async def perform_multipart_upload(
|
|
|
152
152
|
part_etags = await TaskContext.gather(*upload_coros)
|
|
153
153
|
|
|
154
154
|
# The body of the complete_multipart_upload command needs some data in xml format:
|
|
155
|
-
|
|
155
|
+
completion_parts = ["<CompleteMultipartUpload>"]
|
|
156
156
|
for part_number, etag in enumerate(part_etags, 1):
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
completion_parts.append(f"""<Part>\n<PartNumber>{part_number}</PartNumber>\n<ETag>"{etag}"</ETag>\n</Part>""")
|
|
158
|
+
completion_parts.append("</CompleteMultipartUpload>")
|
|
159
|
+
completion_body = "\n".join(completion_parts)
|
|
159
160
|
|
|
160
|
-
# etag of combined object should be md5 hex of
|
|
161
|
+
# etag of combined object should be md5 hex of concatenated md5 *bytes* from parts + `-{num_parts}`
|
|
161
162
|
bin_hash_parts = [bytes.fromhex(etag) for etag in part_etags]
|
|
162
163
|
|
|
163
164
|
expected_multipart_etag = hashlib.md5(b"".join(bin_hash_parts)).hexdigest() + f"-{len(part_etags)}"
|
|
@@ -725,9 +725,7 @@ class _App:
|
|
|
725
725
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
726
726
|
block_network: bool = False, # Whether to block network access
|
|
727
727
|
restrict_modal_access: bool = False, # Whether to allow this function access to other Modal resources
|
|
728
|
-
#
|
|
729
|
-
# With `max_inputs = 1`, containers will be single-use.
|
|
730
|
-
max_inputs: Optional[int] = None,
|
|
728
|
+
single_use_containers: bool = False, # When True, containers will shut down after handling a single input
|
|
731
729
|
i6pn: Optional[bool] = None, # Whether to enable IPv6 container networking within the region.
|
|
732
730
|
# Whether the file or directory containing the Function's source should automatically be included
|
|
733
731
|
# in the container. When unset, falls back to the App-level configuration, or is otherwise True by default.
|
|
@@ -742,6 +740,7 @@ class _App:
|
|
|
742
740
|
concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
|
|
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
|
|
743
|
+
max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
|
|
745
744
|
_experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
|
|
746
745
|
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
|
|
747
746
|
# using `region` and `nonpreemptible`
|
|
@@ -764,6 +763,20 @@ class _App:
|
|
|
764
763
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
765
764
|
)
|
|
766
765
|
|
|
766
|
+
if max_inputs is not None:
|
|
767
|
+
if not isinstance(max_inputs, int):
|
|
768
|
+
raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
|
|
769
|
+
if max_inputs <= 0:
|
|
770
|
+
raise InvalidError("`max_inputs` must be positive")
|
|
771
|
+
if max_inputs > 1:
|
|
772
|
+
raise InvalidError("Only `max_inputs=1` is currently supported")
|
|
773
|
+
deprecation_warning(
|
|
774
|
+
(2025, 12, 16),
|
|
775
|
+
"The `max_inputs` parameter is deprecated. Please set `single_use_containers=True` instead.",
|
|
776
|
+
pending=True,
|
|
777
|
+
)
|
|
778
|
+
single_use_containers = max_inputs == 1
|
|
779
|
+
|
|
767
780
|
if _experimental_scheduler_placement is not None:
|
|
768
781
|
deprecation_warning(
|
|
769
782
|
(2025, 11, 17),
|
|
@@ -912,7 +925,7 @@ class _App:
|
|
|
912
925
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
913
926
|
block_network=block_network,
|
|
914
927
|
restrict_modal_access=restrict_modal_access,
|
|
915
|
-
|
|
928
|
+
single_use_containers=single_use_containers,
|
|
916
929
|
i6pn_enabled=i6pn_enabled,
|
|
917
930
|
cluster_size=cluster_size, # Experimental: Clustered functions
|
|
918
931
|
rdma=rdma,
|
|
@@ -969,9 +982,7 @@ class _App:
|
|
|
969
982
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
970
983
|
block_network: bool = False, # Whether to block network access
|
|
971
984
|
restrict_modal_access: bool = False, # Whether to allow this class access to other Modal resources
|
|
972
|
-
#
|
|
973
|
-
# Use `max_inputs = 1` for single-use containers.
|
|
974
|
-
max_inputs: Optional[int] = None,
|
|
985
|
+
single_use_containers: bool = False, # When True, containers will shut down after handling a single input
|
|
975
986
|
i6pn: Optional[bool] = None, # Whether to enable IPv6 container networking within the region.
|
|
976
987
|
include_source: Optional[bool] = None, # When `False`, don't automatically add the App source to the container.
|
|
977
988
|
experimental_options: Optional[dict[str, Any]] = None,
|
|
@@ -984,6 +995,7 @@ class _App:
|
|
|
984
995
|
concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
|
|
985
996
|
container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
|
|
986
997
|
allow_concurrent_inputs: Optional[int] = None, # Replaced with the `@modal.concurrent` decorator
|
|
998
|
+
max_inputs: Optional[int] = None, # Replaced with `single_use_containers`
|
|
987
999
|
_experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
|
|
988
1000
|
_experimental_scheduler_placement: Optional[SchedulerPlacement] = None, # Replaced in favor of
|
|
989
1001
|
# using `region` and `nonpreemptible`
|
|
@@ -1002,6 +1014,20 @@ class _App:
|
|
|
1002
1014
|
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
1003
1015
|
)
|
|
1004
1016
|
|
|
1017
|
+
if max_inputs is not None:
|
|
1018
|
+
if not isinstance(max_inputs, int):
|
|
1019
|
+
raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
|
|
1020
|
+
if max_inputs <= 0:
|
|
1021
|
+
raise InvalidError("`max_inputs` must be positive")
|
|
1022
|
+
if max_inputs > 1:
|
|
1023
|
+
raise InvalidError("Only `max_inputs=1` is currently supported")
|
|
1024
|
+
deprecation_warning(
|
|
1025
|
+
(2025, 12, 16),
|
|
1026
|
+
"The `max_inputs` parameter is deprecated. Please set `single_use_containers=True` instead.",
|
|
1027
|
+
pending=True,
|
|
1028
|
+
)
|
|
1029
|
+
single_use_containers = max_inputs == 1
|
|
1030
|
+
|
|
1005
1031
|
if _experimental_scheduler_placement is not None:
|
|
1006
1032
|
deprecation_warning(
|
|
1007
1033
|
(2025, 11, 17),
|
|
@@ -1139,7 +1165,7 @@ class _App:
|
|
|
1139
1165
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
1140
1166
|
block_network=block_network,
|
|
1141
1167
|
restrict_modal_access=restrict_modal_access,
|
|
1142
|
-
|
|
1168
|
+
single_use_containers=single_use_containers,
|
|
1143
1169
|
http_config=http_config,
|
|
1144
1170
|
i6pn_enabled=i6pn_enabled,
|
|
1145
1171
|
cluster_size=cluster_size,
|
|
@@ -491,7 +491,7 @@ class _App:
|
|
|
491
491
|
enable_memory_snapshot: bool = False,
|
|
492
492
|
block_network: bool = False,
|
|
493
493
|
restrict_modal_access: bool = False,
|
|
494
|
-
|
|
494
|
+
single_use_containers: bool = False,
|
|
495
495
|
i6pn: typing.Optional[bool] = None,
|
|
496
496
|
include_source: typing.Optional[bool] = None,
|
|
497
497
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
@@ -502,6 +502,7 @@ class _App:
|
|
|
502
502
|
concurrency_limit: typing.Optional[int] = None,
|
|
503
503
|
container_idle_timeout: typing.Optional[int] = None,
|
|
504
504
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
505
|
+
max_inputs: typing.Optional[int] = None,
|
|
505
506
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
506
507
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
507
508
|
) -> _FunctionDecoratorType:
|
|
@@ -545,7 +546,7 @@ class _App:
|
|
|
545
546
|
enable_memory_snapshot: bool = False,
|
|
546
547
|
block_network: bool = False,
|
|
547
548
|
restrict_modal_access: bool = False,
|
|
548
|
-
|
|
549
|
+
single_use_containers: bool = False,
|
|
549
550
|
i6pn: typing.Optional[bool] = None,
|
|
550
551
|
include_source: typing.Optional[bool] = None,
|
|
551
552
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
@@ -556,6 +557,7 @@ class _App:
|
|
|
556
557
|
concurrency_limit: typing.Optional[int] = None,
|
|
557
558
|
container_idle_timeout: typing.Optional[int] = None,
|
|
558
559
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
560
|
+
max_inputs: typing.Optional[int] = None,
|
|
559
561
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
560
562
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
561
563
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]:
|
|
@@ -1154,7 +1156,7 @@ class App:
|
|
|
1154
1156
|
enable_memory_snapshot: bool = False,
|
|
1155
1157
|
block_network: bool = False,
|
|
1156
1158
|
restrict_modal_access: bool = False,
|
|
1157
|
-
|
|
1159
|
+
single_use_containers: bool = False,
|
|
1158
1160
|
i6pn: typing.Optional[bool] = None,
|
|
1159
1161
|
include_source: typing.Optional[bool] = None,
|
|
1160
1162
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
@@ -1165,6 +1167,7 @@ class App:
|
|
|
1165
1167
|
concurrency_limit: typing.Optional[int] = None,
|
|
1166
1168
|
container_idle_timeout: typing.Optional[int] = None,
|
|
1167
1169
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
1170
|
+
max_inputs: typing.Optional[int] = None,
|
|
1168
1171
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
1169
1172
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1170
1173
|
) -> _FunctionDecoratorType:
|
|
@@ -1208,7 +1211,7 @@ class App:
|
|
|
1208
1211
|
enable_memory_snapshot: bool = False,
|
|
1209
1212
|
block_network: bool = False,
|
|
1210
1213
|
restrict_modal_access: bool = False,
|
|
1211
|
-
|
|
1214
|
+
single_use_containers: bool = False,
|
|
1212
1215
|
i6pn: typing.Optional[bool] = None,
|
|
1213
1216
|
include_source: typing.Optional[bool] = None,
|
|
1214
1217
|
experimental_options: typing.Optional[dict[str, typing.Any]] = None,
|
|
@@ -1219,6 +1222,7 @@ class App:
|
|
|
1219
1222
|
concurrency_limit: typing.Optional[int] = None,
|
|
1220
1223
|
container_idle_timeout: typing.Optional[int] = None,
|
|
1221
1224
|
allow_concurrent_inputs: typing.Optional[int] = None,
|
|
1225
|
+
max_inputs: typing.Optional[int] = None,
|
|
1222
1226
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
|
1223
1227
|
_experimental_scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
|
1224
1228
|
) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]:
|
|
@@ -32,7 +32,7 @@ class _Client:
|
|
|
32
32
|
server_url: str,
|
|
33
33
|
client_type: int,
|
|
34
34
|
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.7.
|
|
35
|
+
version: str = "1.2.7.dev9",
|
|
36
36
|
):
|
|
37
37
|
"""mdmd:hidden
|
|
38
38
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -163,7 +163,7 @@ class Client:
|
|
|
163
163
|
server_url: str,
|
|
164
164
|
client_type: int,
|
|
165
165
|
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.2.7.
|
|
166
|
+
version: str = "1.2.7.dev9",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -190,7 +190,7 @@ class _FileIO(Generic[T]):
|
|
|
190
190
|
|
|
191
191
|
async def _wait(self, exec_id: str) -> bytes:
|
|
192
192
|
# The logic here is similar to how output is read from `exec`
|
|
193
|
-
|
|
193
|
+
output_buffer = io.BytesIO()
|
|
194
194
|
completed = False
|
|
195
195
|
retries_remaining = 10
|
|
196
196
|
while not completed:
|
|
@@ -201,7 +201,7 @@ class _FileIO(Generic[T]):
|
|
|
201
201
|
break
|
|
202
202
|
if isinstance(data, Exception):
|
|
203
203
|
raise data
|
|
204
|
-
|
|
204
|
+
output_buffer.write(data)
|
|
205
205
|
except (GRPCError, StreamTerminatedError) as exc:
|
|
206
206
|
if retries_remaining > 0:
|
|
207
207
|
retries_remaining -= 1
|
|
@@ -212,7 +212,7 @@ class _FileIO(Generic[T]):
|
|
|
212
212
|
elif isinstance(exc, StreamTerminatedError):
|
|
213
213
|
continue
|
|
214
214
|
raise
|
|
215
|
-
return
|
|
215
|
+
return output_buffer.getvalue()
|
|
216
216
|
|
|
217
217
|
def _validate_type(self, data: Union[bytes, str]) -> None:
|
|
218
218
|
if self._binary and isinstance(data, str):
|
|
@@ -421,10 +421,22 @@ class _FileIO(Generic[T]):
|
|
|
421
421
|
task_id=self._task_id,
|
|
422
422
|
),
|
|
423
423
|
)
|
|
424
|
+
|
|
425
|
+
def end_of_event(item: bytes, buffer: io.BytesIO, boundary_token: bytes) -> bool:
|
|
426
|
+
if not item.endswith(b"\n"):
|
|
427
|
+
return False
|
|
428
|
+
boundary_token_size = len(boundary_token)
|
|
429
|
+
if buffer.tell() < boundary_token_size:
|
|
430
|
+
return False
|
|
431
|
+
buffer.seek(-boundary_token_size, io.SEEK_END)
|
|
432
|
+
if buffer.read(boundary_token_size) == boundary_token:
|
|
433
|
+
return True
|
|
434
|
+
return False
|
|
435
|
+
|
|
424
436
|
async with TaskContext() as tc:
|
|
425
437
|
tc.create_task(self._consume_watch_output(resp.exec_id))
|
|
426
438
|
|
|
427
|
-
|
|
439
|
+
item_buffer = io.BytesIO()
|
|
428
440
|
while True:
|
|
429
441
|
if len(self._watch_output_buffer) > 0:
|
|
430
442
|
item = self._watch_output_buffer.pop(0)
|
|
@@ -432,12 +444,12 @@ class _FileIO(Generic[T]):
|
|
|
432
444
|
break
|
|
433
445
|
if isinstance(item, Exception):
|
|
434
446
|
raise item
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
#
|
|
438
|
-
if
|
|
447
|
+
item_buffer.write(item)
|
|
448
|
+
assert isinstance(item, bytes)
|
|
449
|
+
# Single events may span multiple messages so we need to check for a special event boundary token
|
|
450
|
+
if end_of_event(item, item_buffer, boundary_token=b"\n\n"):
|
|
439
451
|
try:
|
|
440
|
-
event_json = json.loads(
|
|
452
|
+
event_json = json.loads(item_buffer.getvalue().strip().decode())
|
|
441
453
|
event = FileWatchEvent(
|
|
442
454
|
type=FileWatchEventType(event_json["event_type"]),
|
|
443
455
|
paths=event_json["paths"],
|
|
@@ -447,7 +459,7 @@ class _FileIO(Generic[T]):
|
|
|
447
459
|
except (json.JSONDecodeError, KeyError, ValueError):
|
|
448
460
|
# skip invalid events
|
|
449
461
|
pass
|
|
450
|
-
|
|
462
|
+
item_buffer = io.BytesIO()
|
|
451
463
|
else:
|
|
452
464
|
await asyncio.sleep(0.1)
|
|
453
465
|
|
|
@@ -106,7 +106,7 @@ class Function(
|
|
|
106
106
|
i6pn_enabled: bool = False,
|
|
107
107
|
cluster_size: typing.Optional[int] = None,
|
|
108
108
|
rdma: typing.Optional[bool] = None,
|
|
109
|
-
|
|
109
|
+
single_use_containers: bool = False,
|
|
110
110
|
ephemeral_disk: typing.Optional[int] = None,
|
|
111
111
|
include_source: bool = True,
|
|
112
112
|
experimental_options: typing.Optional[dict[str, str]] = None,
|
|
@@ -23,7 +23,7 @@ from grpclib.exceptions import GRPCError, StreamTerminatedError
|
|
|
23
23
|
from modal.exception import ClientClosed, ExecTimeoutError, InvalidError
|
|
24
24
|
from modal_proto import api_pb2
|
|
25
25
|
|
|
26
|
-
from ._utils.async_utils import aclosing, synchronize_api
|
|
26
|
+
from ._utils.async_utils import aclosing, synchronize_api, synchronizer
|
|
27
27
|
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
28
28
|
from ._utils.task_command_router_client import TaskCommandRouterClient
|
|
29
29
|
from .client import _Client
|
|
@@ -153,17 +153,17 @@ class _StreamReaderThroughServer(Generic[T]):
|
|
|
153
153
|
"""Fetch the entire contents of the stream until EOF."""
|
|
154
154
|
logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read starting")
|
|
155
155
|
if self._text:
|
|
156
|
-
|
|
156
|
+
buffer = io.StringIO()
|
|
157
157
|
async for message in _decode_bytes_stream_to_str(self._get_logs()):
|
|
158
|
-
|
|
158
|
+
buffer.write(message)
|
|
159
159
|
logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
|
|
160
|
-
return cast(T,
|
|
160
|
+
return cast(T, buffer.getvalue())
|
|
161
161
|
else:
|
|
162
|
-
|
|
162
|
+
buffer = io.BytesIO()
|
|
163
163
|
async for message in self._get_logs():
|
|
164
|
-
|
|
164
|
+
buffer.write(message)
|
|
165
165
|
logger.debug(f"{self._object_id} StreamReader fd={self._file_descriptor} read completed after EOF")
|
|
166
|
-
return cast(T,
|
|
166
|
+
return cast(T, buffer.getvalue())
|
|
167
167
|
|
|
168
168
|
async def _consume_container_process_stream(self):
|
|
169
169
|
"""Consume the container process stream and store messages in the buffer."""
|
|
@@ -566,6 +566,9 @@ class _StreamReader(Generic[T]):
|
|
|
566
566
|
task_id: Optional[str] = None,
|
|
567
567
|
) -> None:
|
|
568
568
|
"""mdmd:hidden"""
|
|
569
|
+
# we can remove this once we ensure no constructors use async code
|
|
570
|
+
assert asyncio.get_running_loop() == synchronizer._get_loop(start=False)
|
|
571
|
+
|
|
569
572
|
if by_line and not text:
|
|
570
573
|
raise ValueError("line-buffering is only supported when text=True")
|
|
571
574
|
|
|
@@ -1469,6 +1469,8 @@ message Function {
|
|
|
1469
1469
|
|
|
1470
1470
|
bool block_network = 44;
|
|
1471
1471
|
|
|
1472
|
+
// In the SDK, we've deprecated `max_inputs` (which only every implemented `max_inputs=1`)
|
|
1473
|
+
// in favor of a boolean `single_use_containers` parameter.
|
|
1472
1474
|
uint32 max_inputs = 46;
|
|
1473
1475
|
|
|
1474
1476
|
repeated S3Mount s3_mounts = 47;
|
|
@@ -1556,6 +1558,9 @@ message Function {
|
|
|
1556
1558
|
// Attribute on the module with the implementation, which may differ from function_name
|
|
1557
1559
|
// when the user provided a custom name= for the Function inside the Application namespace
|
|
1558
1560
|
string implementation_name = 90;
|
|
1561
|
+
|
|
1562
|
+
bool single_use_containers = 91; // When True, containers will shut down after handling a single input
|
|
1563
|
+
|
|
1559
1564
|
}
|
|
1560
1565
|
|
|
1561
1566
|
message FunctionAsyncInvokeRequest {
|