modAL 1.1.2__tar.gz → 1.1.2.dev1__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.1.2 → modal-1.1.2.dev1}/PKG-INFO +2 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/__main__.py +2 -1
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_container_entrypoint.py +7 -18
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_functions.py +13 -135
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_object.py +2 -13
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_partial_function.py +8 -8
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/asgi.py +2 -3
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/container_io_manager.py +14 -20
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/container_io_manager.pyi +13 -38
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/execution_context.py +2 -18
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/execution_context.pyi +1 -4
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/blob_utils.py +24 -83
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/function_utils.py +3 -4
- modal-1.1.2.dev1/modal/_utils/time_utils.py +19 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/app.py +4 -8
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/app.pyi +8 -8
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/dict.py +11 -14
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/entry_point.py +3 -9
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/launch.py +4 -89
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/profile.py +0 -1
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/queues.py +19 -49
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/secret.py +18 -45
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/volume.py +16 -14
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/client.pyi +10 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cls.py +2 -12
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cls.pyi +1 -9
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/config.py +7 -7
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/dict.py +12 -206
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/dict.pyi +4 -358
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/experimental/__init__.py +0 -130
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/file_io.py +1 -1
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/file_io.pyi +2 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/file_pattern_matcher.py +16 -25
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/functions.pyi +5 -105
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/image.py +1 -1
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/image.pyi +7 -7
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/mount.py +13 -20
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/mount.pyi +3 -16
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/network_file_system.py +2 -8
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/object.pyi +0 -3
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/parallel_map.py +101 -346
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/parallel_map.pyi +0 -108
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/proxy.py +1 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/queue.py +9 -199
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/queue.pyi +3 -357
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/sandbox.py +5 -6
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/sandbox.pyi +14 -17
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/secret.py +3 -196
- modal-1.1.2.dev1/modal/secret.pyi +297 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/volume.py +22 -238
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/volume.pyi +7 -402
- {modal-1.1.2 → modal-1.1.2.dev1}/modal.egg-info/PKG-INFO +2 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal.egg-info/SOURCES.txt +0 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal.egg-info/requires.txt +1 -1
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_docs/mdmd/mdmd.py +1 -11
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/api.proto +10 -36
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/api_grpc.py +0 -32
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/api_pb2.py +597 -627
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/api_pb2.pyi +17 -101
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/api_pb2_grpc.py +2 -67
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/api_pb2_grpc.pyi +8 -24
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/modal_api_grpc.py +0 -2
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_version/__init__.py +1 -1
- {modal-1.1.2 → modal-1.1.2.dev1}/pyproject.toml +1 -1
- modal-1.1.2/modal/_utils/time_utils.py +0 -43
- modal-1.1.2/modal/cli/programs/launch_instance_ssh.py +0 -94
- modal-1.1.2/modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.2/modal/secret.pyi +0 -669
- {modal-1.1.2 → modal-1.1.2.dev1}/LICENSE +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/README.md +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_clustered_functions.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_ipython.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_location.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_output.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_pty.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_resolver.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_resources.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_serialization.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_traceback.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_tunnel.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_tunnel.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_type_manager.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/logger.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/name_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/_watcher.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/README.md +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/builder/base-images.json +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/call_graph.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/_download.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/_traceback.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/app.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/cluster.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/config.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/container.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/environment.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/import_refs.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/run.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/token.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cli/utils.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/client.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/container_process.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/container_process.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/environments.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/environments.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/exception.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/experimental/flash.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/experimental/flash.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/experimental/ipython.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/functions.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/gpu.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/io_streams.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/io_streams.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/network_file_system.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/object.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/output.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/partial_function.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/partial_function.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/proxy.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/py.typed +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/retries.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/runner.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/runner.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/running_app.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/schedule.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/scheduler_placement.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/serving.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/serving.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/snapshot.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/snapshot.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/stream_type.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/token_flow.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal/token_flow.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_docs/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/__init__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/options.proto +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_proto/py.typed +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/modal_version/__main__.py +0 -0
- {modal-1.1.2 → modal-1.1.2.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.1.2
|
|
3
|
+
Version: 1.1.2.dev1
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: click~=8.1
|
|
|
22
22
|
Requires-Dist: grpclib<0.4.9,>=0.4.7
|
|
23
23
|
Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
|
|
24
24
|
Requires-Dist: rich>=12.0.0
|
|
25
|
-
Requires-Dist: synchronicity~=0.10.
|
|
25
|
+
Requires-Dist: synchronicity~=0.10.1
|
|
26
26
|
Requires-Dist: toml
|
|
27
27
|
Requires-Dist: typer>=0.9
|
|
28
28
|
Requires-Dist: types-certifi
|
|
@@ -37,6 +37,7 @@ def main():
|
|
|
37
37
|
|
|
38
38
|
from grpclib import GRPCError, Status
|
|
39
39
|
from rich.panel import Panel
|
|
40
|
+
from rich.text import Text
|
|
40
41
|
|
|
41
42
|
if isinstance(exc, GRPCError):
|
|
42
43
|
status_map = {
|
|
@@ -68,7 +69,7 @@ def main():
|
|
|
68
69
|
content = f"{content}\n\nNote: {' '.join(notes)}"
|
|
69
70
|
|
|
70
71
|
console = make_console(stderr=True)
|
|
71
|
-
panel = Panel(content, title=title, title_align="left", border_style="red")
|
|
72
|
+
panel = Panel(Text(content), title=title, title_align="left", border_style="red")
|
|
72
73
|
console.print(panel, highlight=False)
|
|
73
74
|
sys.exit(1)
|
|
74
75
|
|
|
@@ -185,9 +185,8 @@ def call_function(
|
|
|
185
185
|
):
|
|
186
186
|
async def run_input_async(io_context: IOContext) -> None:
|
|
187
187
|
started_at = time.time()
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
)
|
|
188
|
+
input_ids, function_call_ids = io_context.input_ids, io_context.function_call_ids
|
|
189
|
+
reset_context = execution_context._set_current_context_ids(input_ids, function_call_ids)
|
|
191
190
|
async with container_io_manager.handle_input_exception.aio(io_context, started_at):
|
|
192
191
|
res = io_context.call_finalized_function()
|
|
193
192
|
# TODO(erikbern): any exception below shouldn't be considered a user exception
|
|
@@ -196,14 +195,9 @@ def call_function(
|
|
|
196
195
|
raise InvalidError(f"Async generator function returned value of type {type(res)}")
|
|
197
196
|
|
|
198
197
|
# Send up to this many outputs at a time.
|
|
199
|
-
current_function_call_id = execution_context.current_function_call_id()
|
|
200
|
-
assert current_function_call_id is not None # Set above.
|
|
201
|
-
current_attempt_token = execution_context.current_attempt_token()
|
|
202
|
-
assert current_attempt_token is not None # Set above, but can be empty string.
|
|
203
198
|
generator_queue: asyncio.Queue[Any] = await container_io_manager._queue_create.aio(1024)
|
|
204
199
|
async with container_io_manager.generator_output_sender(
|
|
205
|
-
|
|
206
|
-
current_attempt_token,
|
|
200
|
+
function_call_ids[0],
|
|
207
201
|
io_context.finalized_function.data_format,
|
|
208
202
|
generator_queue,
|
|
209
203
|
):
|
|
@@ -236,9 +230,8 @@ def call_function(
|
|
|
236
230
|
|
|
237
231
|
def run_input_sync(io_context: IOContext) -> None:
|
|
238
232
|
started_at = time.time()
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
233
|
+
input_ids, function_call_ids = io_context.input_ids, io_context.function_call_ids
|
|
234
|
+
reset_context = execution_context._set_current_context_ids(input_ids, function_call_ids)
|
|
242
235
|
with container_io_manager.handle_input_exception(io_context, started_at):
|
|
243
236
|
res = io_context.call_finalized_function()
|
|
244
237
|
|
|
@@ -248,14 +241,10 @@ def call_function(
|
|
|
248
241
|
raise InvalidError(f"Generator function returned value of type {type(res)}")
|
|
249
242
|
|
|
250
243
|
# Send up to this many outputs at a time.
|
|
251
|
-
current_function_call_id = execution_context.current_function_call_id()
|
|
252
|
-
assert current_function_call_id is not None # Set above.
|
|
253
|
-
current_attempt_token = execution_context.current_attempt_token()
|
|
254
|
-
assert current_attempt_token is not None # Set above, but can be empty string.
|
|
255
244
|
generator_queue: asyncio.Queue[Any] = container_io_manager._queue_create(1024)
|
|
245
|
+
|
|
256
246
|
with container_io_manager.generator_output_sender(
|
|
257
|
-
|
|
258
|
-
current_attempt_token,
|
|
247
|
+
function_call_ids[0],
|
|
259
248
|
io_context.finalized_function.data_format,
|
|
260
249
|
generator_queue,
|
|
261
250
|
):
|
|
@@ -9,7 +9,7 @@ import warnings
|
|
|
9
9
|
from collections.abc import AsyncGenerator, Sequence, Sized
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import PurePosixPath
|
|
12
|
-
from typing import TYPE_CHECKING, Any,
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
|
13
13
|
|
|
14
14
|
import typing_extensions
|
|
15
15
|
from google.protobuf.message import Message
|
|
@@ -71,8 +71,6 @@ from .mount import _get_client_mount, _Mount
|
|
|
71
71
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
|
72
72
|
from .output import _get_output_manager
|
|
73
73
|
from .parallel_map import (
|
|
74
|
-
_experimental_spawn_map_async,
|
|
75
|
-
_experimental_spawn_map_sync,
|
|
76
74
|
_for_each_async,
|
|
77
75
|
_for_each_sync,
|
|
78
76
|
_map_async,
|
|
@@ -80,7 +78,6 @@ from .parallel_map import (
|
|
|
80
78
|
_map_invocation_inputplane,
|
|
81
79
|
_map_sync,
|
|
82
80
|
_spawn_map_async,
|
|
83
|
-
_spawn_map_invocation,
|
|
84
81
|
_spawn_map_sync,
|
|
85
82
|
_starmap_async,
|
|
86
83
|
_starmap_sync,
|
|
@@ -217,11 +214,7 @@ class _Invocation:
|
|
|
217
214
|
return _Invocation(stub, function_call_id, client, retry_context)
|
|
218
215
|
|
|
219
216
|
async def pop_function_call_outputs(
|
|
220
|
-
self,
|
|
221
|
-
index: int = 0,
|
|
222
|
-
timeout: Optional[float] = None,
|
|
223
|
-
clear_on_success: bool = False,
|
|
224
|
-
input_jwts: Optional[list[str]] = None,
|
|
217
|
+
self, timeout: Optional[float], clear_on_success: bool, input_jwts: Optional[list[str]] = None
|
|
225
218
|
) -> api_pb2.FunctionGetOutputsResponse:
|
|
226
219
|
t0 = time.time()
|
|
227
220
|
if timeout is None:
|
|
@@ -239,8 +232,6 @@ class _Invocation:
|
|
|
239
232
|
clear_on_success=clear_on_success,
|
|
240
233
|
requested_at=time.time(),
|
|
241
234
|
input_jwts=input_jwts,
|
|
242
|
-
start_idx=index,
|
|
243
|
-
end_idx=index,
|
|
244
235
|
)
|
|
245
236
|
response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
|
|
246
237
|
self.stub.FunctionGetOutputs,
|
|
@@ -274,7 +265,6 @@ class _Invocation:
|
|
|
274
265
|
# waits indefinitely for a single result for the function, and clear the outputs buffer after
|
|
275
266
|
item: api_pb2.FunctionGetOutputsItem = (
|
|
276
267
|
await self.pop_function_call_outputs(
|
|
277
|
-
index=0,
|
|
278
268
|
timeout=None,
|
|
279
269
|
clear_on_success=True,
|
|
280
270
|
input_jwts=[expected_jwt] if expected_jwt else None,
|
|
@@ -318,16 +308,14 @@ class _Invocation:
|
|
|
318
308
|
|
|
319
309
|
await self._retry_input()
|
|
320
310
|
|
|
321
|
-
async def poll_function(self, timeout: Optional[float] = None
|
|
311
|
+
async def poll_function(self, timeout: Optional[float] = None):
|
|
322
312
|
"""Waits up to timeout for a result from a function.
|
|
323
313
|
|
|
324
314
|
If timeout is `None`, waits indefinitely. This function is not
|
|
325
315
|
cancellation-safe.
|
|
326
316
|
"""
|
|
327
317
|
response: api_pb2.FunctionGetOutputsResponse = await self.pop_function_call_outputs(
|
|
328
|
-
|
|
329
|
-
timeout=timeout,
|
|
330
|
-
clear_on_success=False,
|
|
318
|
+
timeout=timeout, clear_on_success=False
|
|
331
319
|
)
|
|
332
320
|
if len(response.outputs) == 0 and response.num_unfinished_inputs == 0:
|
|
333
321
|
# if no unfinished inputs and no outputs, then function expired
|
|
@@ -360,47 +348,11 @@ class _Invocation:
|
|
|
360
348
|
if items_total is not None and items_received >= items_total:
|
|
361
349
|
break
|
|
362
350
|
|
|
363
|
-
async def enumerate(self, start_index: int, end_index: int):
|
|
364
|
-
"""Iterate over the results of the function call in the range [start_index, end_index)."""
|
|
365
|
-
limit = 49
|
|
366
|
-
current_index = start_index
|
|
367
|
-
while current_index < end_index:
|
|
368
|
-
# batch_end_indx is inclusive, so we subtract 1 to get the last index in the batch.
|
|
369
|
-
batch_end_index = min(current_index + limit, end_index) - 1
|
|
370
|
-
request = api_pb2.FunctionGetOutputsRequest(
|
|
371
|
-
function_call_id=self.function_call_id,
|
|
372
|
-
timeout=0,
|
|
373
|
-
last_entry_id="0-0",
|
|
374
|
-
clear_on_success=False,
|
|
375
|
-
requested_at=time.time(),
|
|
376
|
-
start_idx=current_index,
|
|
377
|
-
end_idx=batch_end_index,
|
|
378
|
-
)
|
|
379
|
-
response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
|
|
380
|
-
self.stub.FunctionGetOutputs,
|
|
381
|
-
request,
|
|
382
|
-
attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
outputs = list(response.outputs)
|
|
386
|
-
outputs.sort(key=lambda x: x.idx)
|
|
387
|
-
for output in outputs:
|
|
388
|
-
if output.idx != current_index:
|
|
389
|
-
break
|
|
390
|
-
result = await _process_result(output.result, output.data_format, self.stub, self.client)
|
|
391
|
-
yield output.idx, result
|
|
392
|
-
current_index += 1
|
|
393
|
-
|
|
394
|
-
# We're missing current_index, so we need to poll the function for the next result
|
|
395
|
-
if len(outputs) < (batch_end_index - current_index + 1):
|
|
396
|
-
result = await self.poll_function(index=current_index)
|
|
397
|
-
yield current_index, result
|
|
398
|
-
current_index += 1
|
|
399
|
-
|
|
400
351
|
|
|
401
352
|
class _InputPlaneInvocation:
|
|
402
353
|
"""Internal client representation of a single-input call to a Modal Function using the input
|
|
403
|
-
plane server API.
|
|
354
|
+
plane server API. As of 4/22/2025, this class is experimental and not used in production.
|
|
355
|
+
It is OK to make breaking changes to this class."""
|
|
404
356
|
|
|
405
357
|
stub: ModalClientModal
|
|
406
358
|
|
|
@@ -510,7 +462,7 @@ class _InputPlaneInvocation:
|
|
|
510
462
|
_stream_function_call_data(
|
|
511
463
|
self.client,
|
|
512
464
|
self.stub,
|
|
513
|
-
|
|
465
|
+
"",
|
|
514
466
|
variant="data_out",
|
|
515
467
|
attempt_token=self.attempt_token,
|
|
516
468
|
),
|
|
@@ -651,7 +603,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
651
603
|
memory: Optional[Union[int, tuple[int, int]]] = None,
|
|
652
604
|
proxy: Optional[_Proxy] = None,
|
|
653
605
|
retries: Optional[Union[int, Retries]] = None,
|
|
654
|
-
timeout: int =
|
|
606
|
+
timeout: Optional[int] = None,
|
|
655
607
|
min_containers: Optional[int] = None,
|
|
656
608
|
max_containers: Optional[int] = None,
|
|
657
609
|
buffer_containers: Optional[int] = None,
|
|
@@ -1178,8 +1130,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1178
1130
|
target_concurrent_inputs=options.target_concurrent_inputs,
|
|
1179
1131
|
batch_max_size=options.batch_max_size,
|
|
1180
1132
|
batch_linger_ms=options.batch_wait_ms,
|
|
1181
|
-
scheduler_placement=options.scheduler_placement,
|
|
1182
|
-
cloud_provider_str=options.cloud,
|
|
1183
1133
|
)
|
|
1184
1134
|
else:
|
|
1185
1135
|
options_pb = None
|
|
@@ -1324,8 +1274,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1324
1274
|
|
|
1325
1275
|
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
|
1326
1276
|
|
|
1327
|
-
|
|
1328
|
-
rep = f"modal.Function.from_name('{app_name}', '{name}'{environment_rep})"
|
|
1277
|
+
rep = f"Function.from_name('{app_name}', '{name}')"
|
|
1329
1278
|
return cls._from_loader(_load_remote, rep, is_another_app=True, hydrate_lazily=True)
|
|
1330
1279
|
|
|
1331
1280
|
@classmethod
|
|
@@ -1594,22 +1543,6 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1594
1543
|
async for item in stream:
|
|
1595
1544
|
yield item
|
|
1596
1545
|
|
|
1597
|
-
@live_method
|
|
1598
|
-
async def _spawn_map(self, input_queue: _SynchronizedQueue) -> "_FunctionCall[ReturnType]":
|
|
1599
|
-
self._check_no_web_url("spawn_map")
|
|
1600
|
-
if self._is_generator:
|
|
1601
|
-
raise InvalidError("A generator function cannot be called with `.spawn_map(...)`.")
|
|
1602
|
-
|
|
1603
|
-
assert self._function_name
|
|
1604
|
-
function_call_id, num_inputs = await _spawn_map_invocation(
|
|
1605
|
-
self,
|
|
1606
|
-
input_queue,
|
|
1607
|
-
self.client,
|
|
1608
|
-
)
|
|
1609
|
-
metadata = api_pb2.FunctionCallFromIdResponse(function_call_id=function_call_id, num_inputs=num_inputs)
|
|
1610
|
-
fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, metadata)
|
|
1611
|
-
return fc
|
|
1612
|
-
|
|
1613
1546
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
|
1614
1547
|
invocation: Union[_Invocation, _InputPlaneInvocation]
|
|
1615
1548
|
if self._input_plane_url:
|
|
@@ -1856,7 +1789,6 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1856
1789
|
starmap = MethodWithAio(_starmap_sync, _starmap_async, synchronizer)
|
|
1857
1790
|
for_each = MethodWithAio(_for_each_sync, _for_each_async, synchronizer)
|
|
1858
1791
|
spawn_map = MethodWithAio(_spawn_map_sync, _spawn_map_async, synchronizer)
|
|
1859
|
-
experimental_spawn_map = MethodWithAio(_experimental_spawn_map_sync, _experimental_spawn_map_async, synchronizer)
|
|
1860
1792
|
|
|
1861
1793
|
|
|
1862
1794
|
class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
@@ -1871,28 +1803,12 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1871
1803
|
"""
|
|
1872
1804
|
|
|
1873
1805
|
_is_generator: bool = False
|
|
1874
|
-
_num_inputs: Optional[int] = None
|
|
1875
1806
|
|
|
1876
1807
|
def _invocation(self):
|
|
1877
1808
|
return _Invocation(self.client.stub, self.object_id, self.client)
|
|
1878
1809
|
|
|
1879
|
-
def
|
|
1880
|
-
|
|
1881
|
-
return
|
|
1882
|
-
assert isinstance(metadata, api_pb2.FunctionCallFromIdResponse)
|
|
1883
|
-
self._num_inputs = metadata.num_inputs
|
|
1884
|
-
|
|
1885
|
-
@live_method
|
|
1886
|
-
async def num_inputs(self) -> int:
|
|
1887
|
-
"""Get the number of inputs in the function call."""
|
|
1888
|
-
# Should have been hydrated.
|
|
1889
|
-
assert self._num_inputs is not None
|
|
1890
|
-
return self._num_inputs
|
|
1891
|
-
|
|
1892
|
-
async def get(self, timeout: Optional[float] = None, *, index: int = 0) -> ReturnType:
|
|
1893
|
-
"""Get the result of the index-th input of the function call.
|
|
1894
|
-
`.spawn()` calls have a single output, so only specifying `index=0` is valid.
|
|
1895
|
-
A non-zero index is useful when your function has multiple outputs, like via `.spawn_map()`.
|
|
1810
|
+
async def get(self, timeout: Optional[float] = None) -> ReturnType:
|
|
1811
|
+
"""Get the result of the function call.
|
|
1896
1812
|
|
|
1897
1813
|
This function waits indefinitely by default. It takes an optional
|
|
1898
1814
|
`timeout` argument that specifies the maximum number of seconds to wait,
|
|
@@ -1900,37 +1816,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1900
1816
|
|
|
1901
1817
|
The returned coroutine is not cancellation-safe.
|
|
1902
1818
|
"""
|
|
1903
|
-
return await self._invocation().poll_function(timeout=timeout
|
|
1904
|
-
|
|
1905
|
-
@live_method_gen
|
|
1906
|
-
async def iter(self, *, start: int = 0, end: Optional[int] = None) -> AsyncIterator[ReturnType]:
|
|
1907
|
-
"""Iterate in-order over the results of the function call.
|
|
1908
|
-
|
|
1909
|
-
Optionally, specify a range [start, end) to iterate over.
|
|
1910
|
-
|
|
1911
|
-
Example:
|
|
1912
|
-
```python
|
|
1913
|
-
@app.function()
|
|
1914
|
-
def my_func(a):
|
|
1915
|
-
return a ** 2
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
@app.local_entrypoint()
|
|
1919
|
-
def main():
|
|
1920
|
-
fc = my_func.spawn_map([1, 2, 3, 4])
|
|
1921
|
-
assert list(fc.iter()) == [1, 4, 9, 16]
|
|
1922
|
-
assert list(fc.iter(start=1, end=3)) == [4, 9]
|
|
1923
|
-
```
|
|
1924
|
-
|
|
1925
|
-
If `end` is not provided, it will iterate over all results.
|
|
1926
|
-
"""
|
|
1927
|
-
num_inputs = await self.num_inputs()
|
|
1928
|
-
if end is None:
|
|
1929
|
-
end = num_inputs
|
|
1930
|
-
if start < 0 or end > num_inputs:
|
|
1931
|
-
raise ValueError(f"Invalid index range: {start} to {end} for {num_inputs} inputs")
|
|
1932
|
-
async for _, item in self._invocation().enumerate(start_index=start, end_index=end):
|
|
1933
|
-
yield item
|
|
1819
|
+
return await self._invocation().poll_function(timeout=timeout)
|
|
1934
1820
|
|
|
1935
1821
|
async def get_call_graph(self) -> list[InputInfo]:
|
|
1936
1822
|
"""Returns a structure representing the call graph from a given root
|
|
@@ -1984,15 +1870,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1984
1870
|
if client is None:
|
|
1985
1871
|
client = await _Client.from_env()
|
|
1986
1872
|
|
|
1987
|
-
|
|
1988
|
-
request = api_pb2.FunctionCallFromIdRequest(function_call_id=function_call_id)
|
|
1989
|
-
resp = await retry_transient_errors(resolver.client.stub.FunctionCallFromId, request)
|
|
1990
|
-
self._hydrate(function_call_id, resolver.client, resp)
|
|
1991
|
-
|
|
1992
|
-
rep = f"FunctionCall.from_id({function_call_id!r})"
|
|
1993
|
-
fc: _FunctionCall[Any] = _FunctionCall._from_loader(_load, rep, hydrate_lazily=True)
|
|
1994
|
-
# We already know the object ID, so we can set it directly
|
|
1995
|
-
fc._object_id = function_call_id
|
|
1873
|
+
fc: _FunctionCall[Any] = _FunctionCall._new_hydrated(function_call_id, client, None)
|
|
1996
1874
|
return fc
|
|
1997
1875
|
|
|
1998
1876
|
@staticmethod
|
|
@@ -191,20 +191,9 @@ class _Object:
|
|
|
191
191
|
def _is_id_type(cls, object_id) -> bool:
|
|
192
192
|
return cls._get_type_from_id(object_id) == cls
|
|
193
193
|
|
|
194
|
-
@classmethod
|
|
195
|
-
def _repr(cls, name: str, environment_name: Optional[str] = None) -> str:
|
|
196
|
-
public_cls = cls.__name__.strip("_")
|
|
197
|
-
environment_repr = f", environment_name={environment_name!r}" if environment_name else ""
|
|
198
|
-
return f"modal.{public_cls}.from_name({name!r}{environment_repr})"
|
|
199
|
-
|
|
200
194
|
@classmethod
|
|
201
195
|
def _new_hydrated(
|
|
202
|
-
cls,
|
|
203
|
-
object_id: str,
|
|
204
|
-
client: _Client,
|
|
205
|
-
handle_metadata: Optional[Message],
|
|
206
|
-
is_another_app: bool = False,
|
|
207
|
-
rep: Optional[str] = None,
|
|
196
|
+
cls, object_id: str, client: _Client, handle_metadata: Optional[Message], is_another_app: bool = False
|
|
208
197
|
) -> Self:
|
|
209
198
|
obj_cls: type[Self]
|
|
210
199
|
if cls._type_prefix is not None:
|
|
@@ -221,7 +210,7 @@ class _Object:
|
|
|
221
210
|
|
|
222
211
|
# Instantiate provider
|
|
223
212
|
obj = _Object.__new__(obj_cls)
|
|
224
|
-
rep =
|
|
213
|
+
rep = f"Object({object_id})" # TODO(erikbern): dumb
|
|
225
214
|
obj._init(rep, is_another_app=is_another_app)
|
|
226
215
|
obj._hydrate(object_id, client, handle_metadata)
|
|
227
216
|
|
|
@@ -282,7 +282,7 @@ class _MethodDecoratorType:
|
|
|
282
282
|
|
|
283
283
|
# TODO(elias): fix support for coroutine type unwrapping for methods (static typing)
|
|
284
284
|
def _method(
|
|
285
|
-
_warn_parentheses_missing=None,
|
|
285
|
+
_warn_parentheses_missing=None,
|
|
286
286
|
*,
|
|
287
287
|
# Set this to True if it's a non-generator function returning
|
|
288
288
|
# a [sync/async] generator object
|
|
@@ -337,7 +337,7 @@ def _parse_custom_domains(custom_domains: Optional[Iterable[str]] = None) -> lis
|
|
|
337
337
|
|
|
338
338
|
|
|
339
339
|
def _fastapi_endpoint(
|
|
340
|
-
_warn_parentheses_missing=None,
|
|
340
|
+
_warn_parentheses_missing=None,
|
|
341
341
|
*,
|
|
342
342
|
method: str = "GET", # REST method for the created endpoint.
|
|
343
343
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
@@ -400,7 +400,7 @@ def _fastapi_endpoint(
|
|
|
400
400
|
|
|
401
401
|
|
|
402
402
|
def _web_endpoint(
|
|
403
|
-
_warn_parentheses_missing=None,
|
|
403
|
+
_warn_parentheses_missing=None,
|
|
404
404
|
*,
|
|
405
405
|
method: str = "GET", # REST method for the created endpoint.
|
|
406
406
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
@@ -468,7 +468,7 @@ def _web_endpoint(
|
|
|
468
468
|
|
|
469
469
|
|
|
470
470
|
def _asgi_app(
|
|
471
|
-
_warn_parentheses_missing=None,
|
|
471
|
+
_warn_parentheses_missing=None,
|
|
472
472
|
*,
|
|
473
473
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
474
474
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
|
@@ -525,7 +525,7 @@ def _asgi_app(
|
|
|
525
525
|
|
|
526
526
|
|
|
527
527
|
def _wsgi_app(
|
|
528
|
-
_warn_parentheses_missing=None,
|
|
528
|
+
_warn_parentheses_missing=None,
|
|
529
529
|
*,
|
|
530
530
|
label: Optional[str] = None, # Label for created endpoint. Final subdomain will be <workspace>--<label>.modal.run.
|
|
531
531
|
custom_domains: Optional[Iterable[str]] = None, # Deploy this endpoint on a custom domain.
|
|
@@ -645,7 +645,7 @@ def _web_server(
|
|
|
645
645
|
|
|
646
646
|
|
|
647
647
|
def _enter(
|
|
648
|
-
_warn_parentheses_missing=None,
|
|
648
|
+
_warn_parentheses_missing=None,
|
|
649
649
|
*,
|
|
650
650
|
snap: bool = False,
|
|
651
651
|
) -> Callable[[Union[_PartialFunction, NullaryMethod]], _PartialFunction]:
|
|
@@ -696,7 +696,7 @@ def _exit(_warn_parentheses_missing=None) -> Callable[[NullaryMethod], _PartialF
|
|
|
696
696
|
|
|
697
697
|
|
|
698
698
|
def _batched(
|
|
699
|
-
_warn_parentheses_missing=None,
|
|
699
|
+
_warn_parentheses_missing=None,
|
|
700
700
|
*,
|
|
701
701
|
max_batch_size: int,
|
|
702
702
|
wait_ms: int,
|
|
@@ -758,7 +758,7 @@ def _batched(
|
|
|
758
758
|
|
|
759
759
|
|
|
760
760
|
def _concurrent(
|
|
761
|
-
_warn_parentheses_missing=None,
|
|
761
|
+
_warn_parentheses_missing=None,
|
|
762
762
|
*,
|
|
763
763
|
max_inputs: int, # Hard limit on each container's input concurrency
|
|
764
764
|
target_inputs: Optional[int] = None, # Input concurrency that Modal's autoscaler should target
|
|
@@ -16,7 +16,7 @@ from modal.config import logger
|
|
|
16
16
|
from modal.exception import ExecutionError, InvalidError
|
|
17
17
|
from modal.experimental import stop_fetching_inputs
|
|
18
18
|
|
|
19
|
-
from .execution_context import
|
|
19
|
+
from .execution_context import current_function_call_id
|
|
20
20
|
|
|
21
21
|
FIRST_MESSAGE_TIMEOUT_SECONDS = 5.0
|
|
22
22
|
|
|
@@ -106,7 +106,6 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., Asyn
|
|
|
106
106
|
raise ExecutionError("Unpexected state in ASGI scope")
|
|
107
107
|
scope["state"] = state
|
|
108
108
|
function_call_id = current_function_call_id()
|
|
109
|
-
attempt_token = current_attempt_token()
|
|
110
109
|
assert function_call_id, "internal error: function_call_id not set in asgi_app() scope"
|
|
111
110
|
|
|
112
111
|
messages_from_app: asyncio.Queue[dict[str, Any]] = asyncio.Queue(1)
|
|
@@ -143,7 +142,7 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., Asyn
|
|
|
143
142
|
# This initial message, "http.request" or "websocket.connect", should be sent
|
|
144
143
|
# immediately after starting the ASGI app's function call. If it is not received, that
|
|
145
144
|
# indicates a request cancellation or other abnormal circumstance.
|
|
146
|
-
message_gen = container_io_manager.get_data_in.aio(function_call_id
|
|
145
|
+
message_gen = container_io_manager.get_data_in.aio(function_call_id)
|
|
147
146
|
first_message_task = asyncio.create_task(message_gen.__anext__())
|
|
148
147
|
|
|
149
148
|
try:
|
|
@@ -39,6 +39,7 @@ from modal.exception import ClientClosed, InputCancellation, InvalidError, Seria
|
|
|
39
39
|
from modal_proto import api_pb2
|
|
40
40
|
|
|
41
41
|
if TYPE_CHECKING:
|
|
42
|
+
import modal._runtime.asgi
|
|
42
43
|
import modal._runtime.user_code_imports
|
|
43
44
|
|
|
44
45
|
|
|
@@ -65,7 +66,6 @@ class IOContext:
|
|
|
65
66
|
input_ids: list[str]
|
|
66
67
|
retry_counts: list[int]
|
|
67
68
|
function_call_ids: list[str]
|
|
68
|
-
attempt_tokens: list[str]
|
|
69
69
|
function_inputs: list[api_pb2.FunctionInput]
|
|
70
70
|
finalized_function: "modal._runtime.user_code_imports.FinalizedFunction"
|
|
71
71
|
|
|
@@ -77,7 +77,6 @@ class IOContext:
|
|
|
77
77
|
input_ids: list[str],
|
|
78
78
|
retry_counts: list[int],
|
|
79
79
|
function_call_ids: list[str],
|
|
80
|
-
attempt_tokens: list[str],
|
|
81
80
|
finalized_function: "modal._runtime.user_code_imports.FinalizedFunction",
|
|
82
81
|
function_inputs: list[api_pb2.FunctionInput],
|
|
83
82
|
is_batched: bool,
|
|
@@ -86,7 +85,6 @@ class IOContext:
|
|
|
86
85
|
self.input_ids = input_ids
|
|
87
86
|
self.retry_counts = retry_counts
|
|
88
87
|
self.function_call_ids = function_call_ids
|
|
89
|
-
self.attempt_tokens = attempt_tokens
|
|
90
88
|
self.finalized_function = finalized_function
|
|
91
89
|
self.function_inputs = function_inputs
|
|
92
90
|
self._is_batched = is_batched
|
|
@@ -97,11 +95,11 @@ class IOContext:
|
|
|
97
95
|
cls,
|
|
98
96
|
client: _Client,
|
|
99
97
|
finalized_functions: dict[str, "modal._runtime.user_code_imports.FinalizedFunction"],
|
|
100
|
-
inputs: list[tuple[str, int, str,
|
|
98
|
+
inputs: list[tuple[str, int, str, api_pb2.FunctionInput]],
|
|
101
99
|
is_batched: bool,
|
|
102
100
|
) -> "IOContext":
|
|
103
101
|
assert len(inputs) >= 1 if is_batched else len(inputs) == 1
|
|
104
|
-
input_ids, retry_counts, function_call_ids,
|
|
102
|
+
input_ids, retry_counts, function_call_ids, function_inputs = zip(*inputs)
|
|
105
103
|
|
|
106
104
|
async def _populate_input_blobs(client: _Client, input: api_pb2.FunctionInput) -> api_pb2.FunctionInput:
|
|
107
105
|
# If we got a pointer to a blob, download it from S3.
|
|
@@ -123,7 +121,6 @@ class IOContext:
|
|
|
123
121
|
cast(list[str], input_ids),
|
|
124
122
|
cast(list[int], retry_counts),
|
|
125
123
|
cast(list[str], function_call_ids),
|
|
126
|
-
cast(list[str], attempt_tokens),
|
|
127
124
|
finalized_function,
|
|
128
125
|
cast(list[api_pb2.FunctionInput], function_inputs),
|
|
129
126
|
is_batched,
|
|
@@ -303,7 +300,11 @@ class _ContainerIOManager:
|
|
|
303
300
|
self.function_def = container_args.function_def
|
|
304
301
|
self.checkpoint_id = container_args.checkpoint_id or None
|
|
305
302
|
|
|
306
|
-
|
|
303
|
+
# We could also have the worker pass this in explicitly.
|
|
304
|
+
self.input_plane_server_url = None
|
|
305
|
+
for obj in container_args.app_layout.objects:
|
|
306
|
+
if obj.object_id == self.function_id:
|
|
307
|
+
self.input_plane_server_url = obj.function_handle_metadata.input_plane_url
|
|
307
308
|
|
|
308
309
|
self.calls_completed = 0
|
|
309
310
|
self.total_user_time = 0.0
|
|
@@ -483,21 +484,18 @@ class _ContainerIOManager:
|
|
|
483
484
|
else {"data": data}
|
|
484
485
|
)
|
|
485
486
|
|
|
486
|
-
async def get_data_in(self, function_call_id: str
|
|
487
|
+
async def get_data_in(self, function_call_id: str) -> AsyncIterator[Any]:
|
|
487
488
|
"""Read from the `data_in` stream of a function call."""
|
|
488
489
|
stub = self._client.stub
|
|
489
490
|
if self.input_plane_server_url:
|
|
490
491
|
stub = await self._client.get_stub(self.input_plane_server_url)
|
|
491
492
|
|
|
492
|
-
async for data in _stream_function_call_data(
|
|
493
|
-
self._client, stub, function_call_id, variant="data_in", attempt_token=attempt_token
|
|
494
|
-
):
|
|
493
|
+
async for data in _stream_function_call_data(self._client, stub, function_call_id, "data_in"):
|
|
495
494
|
yield data
|
|
496
495
|
|
|
497
496
|
async def put_data_out(
|
|
498
497
|
self,
|
|
499
498
|
function_call_id: str,
|
|
500
|
-
attempt_token: str,
|
|
501
499
|
start_index: int,
|
|
502
500
|
data_format: int,
|
|
503
501
|
serialized_messages: list[Any],
|
|
@@ -518,8 +516,6 @@ class _ContainerIOManager:
|
|
|
518
516
|
data_chunks.append(chunk)
|
|
519
517
|
|
|
520
518
|
req = api_pb2.FunctionCallPutDataRequest(function_call_id=function_call_id, data_chunks=data_chunks)
|
|
521
|
-
if attempt_token:
|
|
522
|
-
req.attempt_token = attempt_token # oneof clears function_call_id.
|
|
523
519
|
|
|
524
520
|
if self.input_plane_server_url:
|
|
525
521
|
stub = await self._client.get_stub(self.input_plane_server_url)
|
|
@@ -529,7 +525,7 @@ class _ContainerIOManager:
|
|
|
529
525
|
|
|
530
526
|
@asynccontextmanager
|
|
531
527
|
async def generator_output_sender(
|
|
532
|
-
self, function_call_id: str,
|
|
528
|
+
self, function_call_id: str, data_format: int, message_rx: asyncio.Queue
|
|
533
529
|
) -> AsyncGenerator[None, None]:
|
|
534
530
|
"""Runs background task that feeds generator outputs into a function call's `data_out` stream."""
|
|
535
531
|
GENERATOR_STOP_SENTINEL = Sentinel()
|
|
@@ -558,7 +554,7 @@ class _ContainerIOManager:
|
|
|
558
554
|
else:
|
|
559
555
|
serialized_messages.append(serialize_data_format(message, data_format))
|
|
560
556
|
total_size += len(serialized_messages[-1]) + 512 # 512 bytes for estimated framing overhead
|
|
561
|
-
await self.put_data_out(function_call_id,
|
|
557
|
+
await self.put_data_out(function_call_id, index, data_format, serialized_messages)
|
|
562
558
|
index += len(serialized_messages)
|
|
563
559
|
|
|
564
560
|
task = asyncio.create_task(generator_output_task())
|
|
@@ -594,7 +590,7 @@ class _ContainerIOManager:
|
|
|
594
590
|
self,
|
|
595
591
|
batch_max_size: int,
|
|
596
592
|
batch_wait_ms: int,
|
|
597
|
-
) -> AsyncIterator[list[tuple[str, int, str,
|
|
593
|
+
) -> AsyncIterator[list[tuple[str, int, str, api_pb2.FunctionInput]]]:
|
|
598
594
|
request = api_pb2.FunctionGetInputsRequest(function_id=self.function_id)
|
|
599
595
|
iteration = 0
|
|
600
596
|
while self._fetching_inputs:
|
|
@@ -629,9 +625,7 @@ class _ContainerIOManager:
|
|
|
629
625
|
if item.kill_switch:
|
|
630
626
|
logger.debug(f"Task {self.task_id} input kill signal input.")
|
|
631
627
|
return
|
|
632
|
-
inputs.append(
|
|
633
|
-
(item.input_id, item.retry_count, item.function_call_id, item.attempt_token, item.input)
|
|
634
|
-
)
|
|
628
|
+
inputs.append((item.input_id, item.retry_count, item.function_call_id, item.input))
|
|
635
629
|
if item.input.final_input:
|
|
636
630
|
if request.batch_max_size > 0:
|
|
637
631
|
logger.debug(f"Task {self.task_id} Final input not expected in batch input stream")
|