modAL 1.3.6.dev14__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.3.6.dev14 → modal-1.4.0}/PKG-INFO +1 -1
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_functions.py +0 -3
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/pty.py +5 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/async_utils.py +27 -9
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/grpc_utils.py +2 -3
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/task_command_router_client.py +16 -4
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/app.py +7 -1
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/container.py +11 -1
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/client.pyi +2 -10
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/functions.pyi +7 -17
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/output.py +2 -5
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/parallel_map.py +18 -59
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/parallel_map.pyi +6 -13
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/runner.py +14 -4
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/runner.pyi +1 -1
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox.py +201 -10
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox.pyi +105 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/serving.py +2 -2
- {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_version/__init__.py +1 -1
- {modal-1.3.6.dev14 → modal-1.4.0}/LICENSE +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/README.md +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/__main__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_billing.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_clustered_functions.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_container_entrypoint.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_grpc_client.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_ipython.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_load_context.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_location.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_logs.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_object.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/manager.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/rich.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_output/status.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_partial_function.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_resolver.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_resources.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/asgi.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/user_code_event_loop.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_serialization.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_server.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_traceback.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_tunnel.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_tunnel.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_type_manager.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/app_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/browser_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/deprecation.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/function_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/git_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/http_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/logger.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/name_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/package_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/sandbox_fs_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_utils/time_utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/tblib.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_vendor/version.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/_watcher.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/app.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/app.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/billing.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2023.12.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2024.04.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2024.10.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/2025.06.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/README.md +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/builder/base-images.json +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/call_graph.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/_download.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/_traceback.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/billing.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/changelog.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/cluster.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/config.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/dashboard.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/dict.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/entry_point.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/environment.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/import_refs.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/launch.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/network_file_system.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/profile.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/queues.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/run.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/secret.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/selector.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/shell.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/token.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/utils.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cli/volume.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/client.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cls.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/cls.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/config.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/container_process.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/container_process.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/dict.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/dict.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/environments.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/environments.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/exception.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/flash.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/flash.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/experimental/ipython.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/file_io.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/file_io.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/functions.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/image.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/image.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/io_streams.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/io_streams.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/mount.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/mount.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/network_file_system.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/network_file_system.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/object.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/object.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/partial_function.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/partial_function.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/proxy.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/proxy.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/py.typed +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/queue.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/queue.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/retries.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/running_app.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox_fs.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/sandbox_fs.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/schedule.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/scheduler_placement.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/secret.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/secret.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/server.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/server.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/serving.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/snapshot.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/snapshot.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/stream_type.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/token_flow.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/token_flow.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/volume.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal/volume.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/requires.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_cli_docs_main.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/gen_reference_docs_main.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/__init__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_grpc.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/py.typed +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/modal_version/__main__.py +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/pyproject.toml +0 -0
- {modal-1.3.6.dev14 → modal-1.4.0}/setup.cfg +0 -0
|
@@ -1562,7 +1562,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1562
1562
|
input_queue: _SynchronizedQueue,
|
|
1563
1563
|
order_outputs: bool,
|
|
1564
1564
|
return_exceptions: bool,
|
|
1565
|
-
wrap_returned_exceptions: bool,
|
|
1566
1565
|
) -> AsyncGenerator[Any, None]:
|
|
1567
1566
|
"""mdmd:hidden
|
|
1568
1567
|
|
|
@@ -1588,7 +1587,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1588
1587
|
self.client,
|
|
1589
1588
|
order_outputs,
|
|
1590
1589
|
return_exceptions,
|
|
1591
|
-
wrap_returned_exceptions,
|
|
1592
1590
|
count_update_callback,
|
|
1593
1591
|
)
|
|
1594
1592
|
) as stream:
|
|
@@ -1602,7 +1600,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1602
1600
|
self.client,
|
|
1603
1601
|
order_outputs,
|
|
1604
1602
|
return_exceptions,
|
|
1605
|
-
wrap_returned_exceptions,
|
|
1606
1603
|
count_update_callback,
|
|
1607
1604
|
api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
|
|
1608
1605
|
)
|
|
@@ -147,6 +147,11 @@ async def get_app_logs_loop(
|
|
|
147
147
|
# doesn't support server-side search)
|
|
148
148
|
if search_text and search_text not in log.data:
|
|
149
149
|
return
|
|
150
|
+
# Search results may lack trailing newlines; ensure each
|
|
151
|
+
# match is a complete line so the LineBufferedOutput buffer
|
|
152
|
+
# doesn't merge it with the next match.
|
|
153
|
+
if search_text and not log.data.endswith("\n"):
|
|
154
|
+
log.data += "\n"
|
|
150
155
|
if pty_shell_finish_event:
|
|
151
156
|
await output_mgr.put_pty_content(log)
|
|
152
157
|
else:
|
|
@@ -349,19 +349,18 @@ def retry(
|
|
|
349
349
|
direct_fn=None,
|
|
350
350
|
*,
|
|
351
351
|
n_attempts=3,
|
|
352
|
-
base_delay=0,
|
|
352
|
+
base_delay=0.0,
|
|
353
353
|
delay_factor=2,
|
|
354
|
+
max_delay: Optional[float] = None,
|
|
354
355
|
attempt_timeout: Optional[float] = 90,
|
|
355
|
-
|
|
356
|
+
total_timeout: Optional[float] = None,
|
|
356
357
|
):
|
|
357
358
|
"""Decorator that calls an async function multiple times, with a given timeout.
|
|
358
359
|
|
|
359
360
|
If a `base_delay` is provided, the function is given an exponentially
|
|
360
361
|
increasing delay on each run, up until the maximum number of attempts.
|
|
361
362
|
|
|
362
|
-
If `
|
|
363
|
-
increases exponentially (starting from `attempt_timeout`, multiplied by
|
|
364
|
-
`attempt_timeout_factor` after each failed attempt).
|
|
363
|
+
If `max_delay` is set, the delay between attempts is capped at this value.
|
|
365
364
|
|
|
366
365
|
Usage:
|
|
367
366
|
|
|
@@ -377,6 +376,7 @@ def retry(
|
|
|
377
376
|
pass
|
|
378
377
|
```
|
|
379
378
|
"""
|
|
379
|
+
ATTEMPT_TIMEOUT_FLOOR = 2.0
|
|
380
380
|
|
|
381
381
|
def decorator(fn):
|
|
382
382
|
@functools.wraps(fn)
|
|
@@ -387,17 +387,35 @@ def retry(
|
|
|
387
387
|
local_n_attempts = n_attempts
|
|
388
388
|
|
|
389
389
|
delay = base_delay
|
|
390
|
-
|
|
390
|
+
if total_timeout is not None:
|
|
391
|
+
total_deadline = time.time() + total_timeout
|
|
392
|
+
else:
|
|
393
|
+
total_deadline = None
|
|
394
|
+
|
|
391
395
|
for i in range(local_n_attempts):
|
|
392
396
|
t0 = time.time()
|
|
397
|
+
|
|
398
|
+
timeouts = []
|
|
399
|
+
if attempt_timeout is not None:
|
|
400
|
+
timeouts.append(attempt_timeout)
|
|
401
|
+
if total_deadline is not None:
|
|
402
|
+
timeouts.append(max(total_deadline - time.time(), ATTEMPT_TIMEOUT_FLOOR))
|
|
403
|
+
timeout = min(timeouts) if timeouts else None
|
|
404
|
+
|
|
393
405
|
try:
|
|
394
|
-
return await asyncio.wait_for(fn(*args, **kwargs), timeout=
|
|
406
|
+
return await asyncio.wait_for(fn(*args, **kwargs), timeout=timeout)
|
|
395
407
|
except asyncio.CancelledError:
|
|
396
408
|
logger.debug(f"Function {fn} was cancelled")
|
|
397
409
|
raise
|
|
398
410
|
except Exception as e:
|
|
399
411
|
if i >= local_n_attempts - 1:
|
|
400
412
|
raise
|
|
413
|
+
|
|
414
|
+
# We check if the timeout will be reached **after** the sleep, so we can raise an error early
|
|
415
|
+
# without needing to actually sleep.
|
|
416
|
+
if total_deadline is not None and time.time() + delay + ATTEMPT_TIMEOUT_FLOOR >= total_deadline:
|
|
417
|
+
raise
|
|
418
|
+
|
|
401
419
|
logger.debug(
|
|
402
420
|
f"Failed invoking function {fn}: {e}"
|
|
403
421
|
f" (took {time.time() - t0}s, sleeping {delay}s"
|
|
@@ -405,8 +423,8 @@ def retry(
|
|
|
405
423
|
)
|
|
406
424
|
await asyncio.sleep(delay)
|
|
407
425
|
delay *= delay_factor
|
|
408
|
-
if
|
|
409
|
-
|
|
426
|
+
if max_delay is not None:
|
|
427
|
+
delay = min(delay, max_delay)
|
|
410
428
|
|
|
411
429
|
return f_wrapped
|
|
412
430
|
|
|
@@ -90,8 +90,6 @@ RETRYABLE_GRPC_STATUS_CODES = [
|
|
|
90
90
|
]
|
|
91
91
|
SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
|
|
92
92
|
# Initial per-attempt timeout for TCP/TLS handshake in connect_channel.
|
|
93
|
-
# Doubles each attempt (attempt_timeout_factor=2): 0.5 + 1 + 2 + 4 + 8 = 15.5s + ~1.5s inter-attempt delays.
|
|
94
|
-
CONNECT_TIMEOUT = 0.5
|
|
95
93
|
DEFAULT_MAX_RETRIES = 3
|
|
96
94
|
|
|
97
95
|
|
|
@@ -225,7 +223,8 @@ def create_channel(
|
|
|
225
223
|
return channel
|
|
226
224
|
|
|
227
225
|
|
|
228
|
-
|
|
226
|
+
# With 18 attempts, the max delays between calls is: 0.1 + 0.2 + 0.4 + 0.8 + 1.6 + 3.2 + 5*11 ~ 62
|
|
227
|
+
@retry(n_attempts=18, base_delay=0.1, attempt_timeout=10.0, max_delay=5.0, total_timeout=63.0)
|
|
229
228
|
async def connect_channel(channel: grpclib.client.Channel):
|
|
230
229
|
"""Connect to socket and raise exceptions when there is a connection issue."""
|
|
231
230
|
await channel.__connect__()
|
|
@@ -11,7 +11,6 @@ from typing import AsyncGenerator, Optional
|
|
|
11
11
|
|
|
12
12
|
import grpclib.client
|
|
13
13
|
import grpclib.config
|
|
14
|
-
import grpclib.events
|
|
15
14
|
from grpclib import GRPCError, Status
|
|
16
15
|
from grpclib.exceptions import StreamTerminatedError
|
|
17
16
|
|
|
@@ -22,8 +21,21 @@ from modal_proto.task_command_router_grpc import TaskCommandRouterStub
|
|
|
22
21
|
|
|
23
22
|
from .._grpc_client import grpc_error_converter
|
|
24
23
|
from .._utils.grpc_utils import PermanentCloseableChannel
|
|
25
|
-
from .async_utils import aclosing
|
|
26
|
-
from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
24
|
+
from .async_utils import aclosing, retry
|
|
25
|
+
from .grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@retry(n_attempts=34, base_delay=1, max_delay=10, attempt_timeout=10, total_timeout=310)
|
|
29
|
+
async def _connect_channel(channel: grpclib.client.Channel):
|
|
30
|
+
"""Connect to the command router channel.
|
|
31
|
+
|
|
32
|
+
Uses a longer retry budget than grpc_utils.connect_channel. In rare cases the sandbox
|
|
33
|
+
may take a long time to start on the worker after scheduling.
|
|
34
|
+
|
|
35
|
+
Retries with exponential backoff (1, 2, 4, 8, 10, 10, ...) capped at 10s per delay.
|
|
36
|
+
Total sleep between attempts: 1 + 2 + 4 + 8 + 10*29 = 305s (~5 min).
|
|
37
|
+
"""
|
|
38
|
+
await channel.__connect__()
|
|
27
39
|
|
|
28
40
|
|
|
29
41
|
def _b64url_decode(data: str) -> bytes:
|
|
@@ -169,7 +181,7 @@ class TaskCommandRouterClient:
|
|
|
169
181
|
closed_error_message="Unable to perform operation on a detached sandbox",
|
|
170
182
|
)
|
|
171
183
|
|
|
172
|
-
await
|
|
184
|
+
await _connect_channel(channel)
|
|
173
185
|
loop = asyncio.get_running_loop()
|
|
174
186
|
jwt_refresh_lock = asyncio.Lock()
|
|
175
187
|
|
|
@@ -164,7 +164,7 @@ def logs(
|
|
|
164
164
|
Get recent logs based on an app ID:
|
|
165
165
|
|
|
166
166
|
```
|
|
167
|
-
modal app logs ap-123456
|
|
167
|
+
modal app logs ap-123456
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
Get recent logs for a currently deployed App based on its name:
|
|
@@ -179,6 +179,12 @@ def logs(
|
|
|
179
179
|
modal app logs my-app -f
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
Fetch the last 1000 entries:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
modal app logs my-app --tail 1000
|
|
186
|
+
```
|
|
187
|
+
|
|
182
188
|
Fetch logs from the last 2 hours:
|
|
183
189
|
|
|
184
190
|
```
|
|
@@ -27,7 +27,11 @@ container_cli = typer.Typer(name="container", help="Manage and connect to runnin
|
|
|
27
27
|
|
|
28
28
|
@container_cli.command("list")
|
|
29
29
|
@synchronizer.create_blocking
|
|
30
|
-
async def list_(
|
|
30
|
+
async def list_(
|
|
31
|
+
app_id: str = typer.Option("", "--app-id", help="List containers running for a specific App."),
|
|
32
|
+
env: Optional[str] = ENV_OPTION,
|
|
33
|
+
json: bool = False,
|
|
34
|
+
):
|
|
31
35
|
"""List all containers that are currently running."""
|
|
32
36
|
env = ensure_env(env)
|
|
33
37
|
client = await _Client.from_env()
|
|
@@ -113,6 +117,12 @@ async def logs(
|
|
|
113
117
|
modal container logs ta-123456 --since 2026-03-01T05:00:00 --until 2026-03-01T08:00:00
|
|
114
118
|
```
|
|
115
119
|
|
|
120
|
+
Fetch the last 1000 entries:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
modal container logs ta-123456 --tail 1000
|
|
124
|
+
```
|
|
125
|
+
|
|
116
126
|
Fetch all container logs:
|
|
117
127
|
|
|
118
128
|
```
|
|
@@ -31,11 +31,7 @@ class _Client:
|
|
|
31
31
|
client_type: int
|
|
32
32
|
|
|
33
33
|
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
server_url: str,
|
|
36
|
-
client_type: int,
|
|
37
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
38
|
-
version: str = "1.3.6.dev14",
|
|
34
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.4.0"
|
|
39
35
|
):
|
|
40
36
|
"""mdmd:hidden
|
|
41
37
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -167,11 +163,7 @@ class Client:
|
|
|
167
163
|
client_type: int
|
|
168
164
|
|
|
169
165
|
def __init__(
|
|
170
|
-
self,
|
|
171
|
-
server_url: str,
|
|
172
|
-
client_type: int,
|
|
173
|
-
credentials: typing.Optional[tuple[str, str]],
|
|
174
|
-
version: str = "1.3.6.dev14",
|
|
166
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.4.0"
|
|
175
167
|
):
|
|
176
168
|
"""mdmd:hidden
|
|
177
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -291,12 +291,7 @@ class Function(
|
|
|
291
291
|
|
|
292
292
|
class ___map_spec(typing_extensions.Protocol):
|
|
293
293
|
def __call__(
|
|
294
|
-
self,
|
|
295
|
-
/,
|
|
296
|
-
input_queue: modal.parallel_map.SynchronizedQueue,
|
|
297
|
-
order_outputs: bool,
|
|
298
|
-
return_exceptions: bool,
|
|
299
|
-
wrap_returned_exceptions: bool,
|
|
294
|
+
self, /, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
|
|
300
295
|
) -> typing.Generator[typing.Any, None, None]:
|
|
301
296
|
"""mdmd:hidden
|
|
302
297
|
|
|
@@ -310,12 +305,7 @@ class Function(
|
|
|
310
305
|
...
|
|
311
306
|
|
|
312
307
|
def aio(
|
|
313
|
-
self,
|
|
314
|
-
/,
|
|
315
|
-
input_queue: modal.parallel_map.SynchronizedQueue,
|
|
316
|
-
order_outputs: bool,
|
|
317
|
-
return_exceptions: bool,
|
|
318
|
-
wrap_returned_exceptions: bool,
|
|
308
|
+
self, /, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
|
|
319
309
|
) -> collections.abc.AsyncGenerator[typing.Any, None]:
|
|
320
310
|
"""mdmd:hidden
|
|
321
311
|
|
|
@@ -482,7 +472,7 @@ class Function(
|
|
|
482
472
|
kwargs={},
|
|
483
473
|
order_outputs: bool = True,
|
|
484
474
|
return_exceptions: bool = False,
|
|
485
|
-
wrap_returned_exceptions: bool =
|
|
475
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
486
476
|
) -> modal._utils.async_utils.AsyncOrSyncIterable:
|
|
487
477
|
"""Parallel map over a set of inputs.
|
|
488
478
|
|
|
@@ -516,7 +506,7 @@ class Function(
|
|
|
516
506
|
|
|
517
507
|
@app.local_entrypoint()
|
|
518
508
|
def main():
|
|
519
|
-
# [0, 1,
|
|
509
|
+
# [0, 1, Exception('ohno')]
|
|
520
510
|
print(list(my_func.map(range(3), return_exceptions=True)))
|
|
521
511
|
```
|
|
522
512
|
"""
|
|
@@ -529,7 +519,7 @@ class Function(
|
|
|
529
519
|
kwargs={},
|
|
530
520
|
order_outputs: bool = True,
|
|
531
521
|
return_exceptions: bool = False,
|
|
532
|
-
wrap_returned_exceptions: bool =
|
|
522
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
533
523
|
) -> typing.AsyncGenerator[typing.Any, None]: ...
|
|
534
524
|
|
|
535
525
|
map: __map_spec
|
|
@@ -543,7 +533,7 @@ class Function(
|
|
|
543
533
|
kwargs={},
|
|
544
534
|
order_outputs: bool = True,
|
|
545
535
|
return_exceptions: bool = False,
|
|
546
|
-
wrap_returned_exceptions: bool =
|
|
536
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
547
537
|
) -> modal._utils.async_utils.AsyncOrSyncIterable:
|
|
548
538
|
"""Like `map`, but spreads arguments over multiple function arguments.
|
|
549
539
|
|
|
@@ -573,7 +563,7 @@ class Function(
|
|
|
573
563
|
kwargs={},
|
|
574
564
|
order_outputs: bool = True,
|
|
575
565
|
return_exceptions: bool = False,
|
|
576
|
-
wrap_returned_exceptions: bool =
|
|
566
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
577
567
|
) -> typing.AsyncIterable[typing.Any]: ...
|
|
578
568
|
|
|
579
569
|
starmap: __starmap_spec
|
|
@@ -16,7 +16,7 @@ __all__ = ["enable_output", "OutputManager"]
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@contextlib.contextmanager
|
|
19
|
-
def enable_output() -> Generator[
|
|
19
|
+
def enable_output() -> Generator[None, None, None]:
|
|
20
20
|
"""Context manager that enable output when using the Python SDK.
|
|
21
21
|
|
|
22
22
|
This will print to stdout and stderr things such as
|
|
@@ -31,9 +31,6 @@ def enable_output() -> Generator[OutputManager, None, None]:
|
|
|
31
31
|
with app.run():
|
|
32
32
|
...
|
|
33
33
|
```
|
|
34
|
-
|
|
35
|
-
To suppress progress indicators, use `output_manager.set_quiet_mode(True)`.
|
|
36
|
-
To enable timestamps, use `output_manager.set_timestamps(True)`.
|
|
37
34
|
"""
|
|
38
35
|
previous_output_manager = OutputManager.get()
|
|
39
36
|
|
|
@@ -42,6 +39,6 @@ def enable_output() -> Generator[OutputManager, None, None]:
|
|
|
42
39
|
new_manager = RichOutputManager()
|
|
43
40
|
OutputManager._set(new_manager)
|
|
44
41
|
try:
|
|
45
|
-
yield
|
|
42
|
+
yield
|
|
46
43
|
finally:
|
|
47
44
|
OutputManager._set(previous_output_manager)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Copyright Modal Labs 2024
|
|
2
2
|
import asyncio
|
|
3
3
|
import enum
|
|
4
|
-
import inspect
|
|
5
4
|
import time
|
|
6
5
|
import typing
|
|
7
6
|
from asyncio import FIRST_COMPLETED
|
|
@@ -10,7 +9,6 @@ from typing import Any, Callable, Optional, Union
|
|
|
10
9
|
|
|
11
10
|
from grpclib import Status
|
|
12
11
|
|
|
13
|
-
import modal.exception
|
|
14
12
|
from modal._runtime.execution_context import current_input_id
|
|
15
13
|
from modal._utils.async_utils import (
|
|
16
14
|
AsyncOrSyncIterable,
|
|
@@ -365,7 +363,6 @@ async def _map_invocation(
|
|
|
365
363
|
client: "modal.client._Client",
|
|
366
364
|
order_outputs: bool,
|
|
367
365
|
return_exceptions: bool,
|
|
368
|
-
wrap_returned_exceptions: bool,
|
|
369
366
|
count_update_callback: Optional[Callable[[int, int], None]],
|
|
370
367
|
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
|
|
371
368
|
):
|
|
@@ -545,13 +542,7 @@ async def _map_invocation(
|
|
|
545
542
|
output = await _process_result(item.result, item.data_format, client.stub, client)
|
|
546
543
|
except Exception as e:
|
|
547
544
|
if return_exceptions:
|
|
548
|
-
|
|
549
|
-
# Prior to client 1.0.4 there was a bug where return_exceptions would wrap
|
|
550
|
-
# any returned exceptions in a synchronicity.UserCodeException. This adds
|
|
551
|
-
# deprecated non-breaking compatibility bandaid for migrating away from that:
|
|
552
|
-
output = modal.exception.UserCodeException(e)
|
|
553
|
-
else:
|
|
554
|
-
output = e
|
|
545
|
+
output = e
|
|
555
546
|
else:
|
|
556
547
|
raise e
|
|
557
548
|
return (item.idx, output)
|
|
@@ -631,7 +622,6 @@ async def _map_invocation_inputplane(
|
|
|
631
622
|
client: "modal.client._Client",
|
|
632
623
|
order_outputs: bool,
|
|
633
624
|
return_exceptions: bool,
|
|
634
|
-
wrap_returned_exceptions: bool,
|
|
635
625
|
count_update_callback: Optional[Callable[[int, int], None]],
|
|
636
626
|
) -> typing.AsyncGenerator[Any, None]:
|
|
637
627
|
"""Input-plane implementation of a function map invocation.
|
|
@@ -916,13 +906,7 @@ async def _map_invocation_inputplane(
|
|
|
916
906
|
output = await _process_result(item.result, item.data_format, input_plane_stub, client)
|
|
917
907
|
except Exception as e:
|
|
918
908
|
if return_exceptions:
|
|
919
|
-
|
|
920
|
-
# Prior to client 1.0.4 there was a bug where return_exceptions would wrap
|
|
921
|
-
# any returned exceptions in a synchronicity.UserCodeException. This adds
|
|
922
|
-
# deprecated non-breaking compatibility bandaid for migrating away from that:
|
|
923
|
-
output = modal.exception.UserCodeException(e)
|
|
924
|
-
else:
|
|
925
|
-
output = e
|
|
909
|
+
output = e
|
|
926
910
|
else:
|
|
927
911
|
raise e
|
|
928
912
|
return (item.idx, output)
|
|
@@ -992,7 +976,6 @@ async def _map_helper(
|
|
|
992
976
|
kwargs={}, # any extra keyword arguments for the function
|
|
993
977
|
order_outputs: bool = True, # return outputs in order
|
|
994
978
|
return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
|
|
995
|
-
wrap_returned_exceptions: bool = True,
|
|
996
979
|
) -> typing.AsyncGenerator[Any, None]:
|
|
997
980
|
"""Core implementation that supports `_map_async()`, `_starmap_async()` and `_for_each_async()`.
|
|
998
981
|
|
|
@@ -1022,41 +1005,24 @@ async def _map_helper(
|
|
|
1022
1005
|
# synchronicity-wrapped, since they accept executable code in the form of iterators that we don't want to run inside
|
|
1023
1006
|
# the synchronicity thread. Instead, we delegate to `._map()` with a safer Queue as input.
|
|
1024
1007
|
async with aclosing(
|
|
1025
|
-
async_merge(
|
|
1026
|
-
self._map.aio(raw_input_queue, order_outputs, return_exceptions, wrap_returned_exceptions), feed_queue()
|
|
1027
|
-
)
|
|
1008
|
+
async_merge(self._map.aio(raw_input_queue, order_outputs, return_exceptions), feed_queue())
|
|
1028
1009
|
) as map_output_stream:
|
|
1029
1010
|
async for output in map_output_stream:
|
|
1030
1011
|
yield output
|
|
1031
1012
|
|
|
1032
1013
|
|
|
1033
|
-
def
|
|
1034
|
-
if
|
|
1014
|
+
def _maybe_warn_about_wrap_exceptions(func_name: str, wrap_returned_exceptions: Optional[bool]):
|
|
1015
|
+
if wrap_returned_exceptions is not None:
|
|
1035
1016
|
deprecation_warning(
|
|
1036
|
-
(
|
|
1017
|
+
(2026, 3, 24),
|
|
1037
1018
|
(
|
|
1038
|
-
f"Function.{func_name}
|
|
1039
|
-
"
|
|
1040
|
-
"
|
|
1041
|
-
"To opt into the future behavior and silence this warning, add `wrap_returned_exceptions=False`:\n\n"
|
|
1042
|
-
f" f.{func_name}(..., return_exceptions=True, wrap_returned_exceptions=False)"
|
|
1019
|
+
f"The `wrap_returned_exceptions` parameter of `Function.{func_name}` no longer has any "
|
|
1020
|
+
"effect and can be removed from your code. Exceptions are no longer wrapped before being "
|
|
1021
|
+
f"returned from `Function.{func_name}`. "
|
|
1043
1022
|
),
|
|
1044
1023
|
)
|
|
1045
1024
|
|
|
1046
1025
|
|
|
1047
|
-
def _invoked_from_sync_wrapper() -> bool:
|
|
1048
|
-
"""Check whether the calling function was called from a sync wrapper."""
|
|
1049
|
-
# This is temporary: we only need it to avoind double-firing the wrap_returned_exceptions warning.
|
|
1050
|
-
# (We don't want to push the warning lower in the stack beacuse then we can't attribute to the user's code.)
|
|
1051
|
-
try:
|
|
1052
|
-
frame = inspect.currentframe()
|
|
1053
|
-
caller_function_name = frame.f_back.f_back.f_code.co_name
|
|
1054
|
-
# Embeds some assumptions about how the current calling stack works, but this is just temporary.
|
|
1055
|
-
return caller_function_name == "asend"
|
|
1056
|
-
except Exception:
|
|
1057
|
-
return False
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
1026
|
@warn_if_generator_is_not_consumed(function_name="Function.map.aio")
|
|
1061
1027
|
async def _map_async(
|
|
1062
1028
|
self: "modal.functions.Function",
|
|
@@ -1066,10 +1032,9 @@ async def _map_async(
|
|
|
1066
1032
|
kwargs={}, # any extra keyword arguments for the function
|
|
1067
1033
|
order_outputs: bool = True, # return outputs in order
|
|
1068
1034
|
return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
|
|
1069
|
-
wrap_returned_exceptions: bool =
|
|
1035
|
+
wrap_returned_exceptions: Optional[bool] = None,
|
|
1070
1036
|
) -> typing.AsyncGenerator[Any, None]:
|
|
1071
|
-
|
|
1072
|
-
_maybe_warn_about_exceptions("map.aio", return_exceptions, wrap_returned_exceptions)
|
|
1037
|
+
_maybe_warn_about_wrap_exceptions("map.aio", wrap_returned_exceptions)
|
|
1073
1038
|
async_input_gen = async_zip(*[sync_or_async_iter(it) for it in input_iterators])
|
|
1074
1039
|
async for output in _map_helper(
|
|
1075
1040
|
self,
|
|
@@ -1077,7 +1042,6 @@ async def _map_async(
|
|
|
1077
1042
|
kwargs=kwargs,
|
|
1078
1043
|
order_outputs=order_outputs,
|
|
1079
1044
|
return_exceptions=return_exceptions,
|
|
1080
|
-
wrap_returned_exceptions=wrap_returned_exceptions,
|
|
1081
1045
|
):
|
|
1082
1046
|
yield output
|
|
1083
1047
|
|
|
@@ -1090,17 +1054,15 @@ async def _starmap_async(
|
|
|
1090
1054
|
kwargs={},
|
|
1091
1055
|
order_outputs: bool = True,
|
|
1092
1056
|
return_exceptions: bool = False,
|
|
1093
|
-
wrap_returned_exceptions: bool =
|
|
1057
|
+
wrap_returned_exceptions: Optional[bool] = None,
|
|
1094
1058
|
) -> typing.AsyncIterable[Any]:
|
|
1095
|
-
|
|
1096
|
-
_maybe_warn_about_exceptions("starmap.aio", return_exceptions, wrap_returned_exceptions)
|
|
1059
|
+
_maybe_warn_about_wrap_exceptions("starmap.aio", wrap_returned_exceptions)
|
|
1097
1060
|
async for output in _map_helper(
|
|
1098
1061
|
self,
|
|
1099
1062
|
sync_or_async_iter(input_iterator),
|
|
1100
1063
|
kwargs=kwargs,
|
|
1101
1064
|
order_outputs=order_outputs,
|
|
1102
1065
|
return_exceptions=return_exceptions,
|
|
1103
|
-
wrap_returned_exceptions=wrap_returned_exceptions,
|
|
1104
1066
|
):
|
|
1105
1067
|
yield output
|
|
1106
1068
|
|
|
@@ -1115,7 +1077,6 @@ async def _for_each_async(self, *input_iterators, kwargs={}, ignore_exceptions:
|
|
|
1115
1077
|
kwargs=kwargs,
|
|
1116
1078
|
order_outputs=False,
|
|
1117
1079
|
return_exceptions=ignore_exceptions,
|
|
1118
|
-
wrap_returned_exceptions=False,
|
|
1119
1080
|
):
|
|
1120
1081
|
pass
|
|
1121
1082
|
|
|
@@ -1127,7 +1088,7 @@ def _map_sync(
|
|
|
1127
1088
|
kwargs={}, # any extra keyword arguments for the function
|
|
1128
1089
|
order_outputs: bool = True, # return outputs in order
|
|
1129
1090
|
return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
|
|
1130
|
-
wrap_returned_exceptions: bool =
|
|
1091
|
+
wrap_returned_exceptions: Optional[bool] = None,
|
|
1131
1092
|
) -> AsyncOrSyncIterable:
|
|
1132
1093
|
"""Parallel map over a set of inputs.
|
|
1133
1094
|
|
|
@@ -1161,11 +1122,11 @@ def _map_sync(
|
|
|
1161
1122
|
|
|
1162
1123
|
@app.local_entrypoint()
|
|
1163
1124
|
def main():
|
|
1164
|
-
# [0, 1,
|
|
1125
|
+
# [0, 1, Exception('ohno')]
|
|
1165
1126
|
print(list(my_func.map(range(3), return_exceptions=True)))
|
|
1166
1127
|
```
|
|
1167
1128
|
"""
|
|
1168
|
-
|
|
1129
|
+
_maybe_warn_about_wrap_exceptions("map", wrap_returned_exceptions)
|
|
1169
1130
|
|
|
1170
1131
|
return AsyncOrSyncIterable(
|
|
1171
1132
|
_map_async(
|
|
@@ -1174,7 +1135,6 @@ def _map_sync(
|
|
|
1174
1135
|
kwargs=kwargs,
|
|
1175
1136
|
order_outputs=order_outputs,
|
|
1176
1137
|
return_exceptions=return_exceptions,
|
|
1177
|
-
wrap_returned_exceptions=wrap_returned_exceptions,
|
|
1178
1138
|
),
|
|
1179
1139
|
nested_async_message=(
|
|
1180
1140
|
"You can't iter(Function.map()) from an async function. Use async for ... in Function.map.aio() instead."
|
|
@@ -1304,7 +1264,7 @@ def _starmap_sync(
|
|
|
1304
1264
|
kwargs={},
|
|
1305
1265
|
order_outputs: bool = True,
|
|
1306
1266
|
return_exceptions: bool = False,
|
|
1307
|
-
wrap_returned_exceptions: bool =
|
|
1267
|
+
wrap_returned_exceptions: Optional[bool] = None,
|
|
1308
1268
|
) -> AsyncOrSyncIterable:
|
|
1309
1269
|
"""Like `map`, but spreads arguments over multiple function arguments.
|
|
1310
1270
|
|
|
@@ -1322,7 +1282,7 @@ def _starmap_sync(
|
|
|
1322
1282
|
assert list(my_func.starmap([(1, 2), (3, 4)])) == [3, 7]
|
|
1323
1283
|
```
|
|
1324
1284
|
"""
|
|
1325
|
-
|
|
1285
|
+
_maybe_warn_about_wrap_exceptions("starmap", wrap_returned_exceptions)
|
|
1326
1286
|
return AsyncOrSyncIterable(
|
|
1327
1287
|
_starmap_async(
|
|
1328
1288
|
self,
|
|
@@ -1330,7 +1290,6 @@ def _starmap_sync(
|
|
|
1330
1290
|
kwargs=kwargs,
|
|
1331
1291
|
order_outputs=order_outputs,
|
|
1332
1292
|
return_exceptions=return_exceptions,
|
|
1333
|
-
wrap_returned_exceptions=wrap_returned_exceptions,
|
|
1334
1293
|
),
|
|
1335
1294
|
nested_async_message=(
|
|
1336
1295
|
"You can't `iter(Function.starmap())` from an async function. "
|
|
@@ -140,7 +140,6 @@ def _map_invocation(
|
|
|
140
140
|
client: modal.client._Client,
|
|
141
141
|
order_outputs: bool,
|
|
142
142
|
return_exceptions: bool,
|
|
143
|
-
wrap_returned_exceptions: bool,
|
|
144
143
|
count_update_callback: typing.Optional[collections.abc.Callable[[int, int], None]],
|
|
145
144
|
function_call_invocation_type: int,
|
|
146
145
|
): ...
|
|
@@ -150,7 +149,6 @@ def _map_invocation_inputplane(
|
|
|
150
149
|
client: modal.client._Client,
|
|
151
150
|
order_outputs: bool,
|
|
152
151
|
return_exceptions: bool,
|
|
153
|
-
wrap_returned_exceptions: bool,
|
|
154
152
|
count_update_callback: typing.Optional[collections.abc.Callable[[int, int], None]],
|
|
155
153
|
) -> typing.AsyncGenerator[typing.Any, None]:
|
|
156
154
|
"""Input-plane implementation of a function map invocation.
|
|
@@ -167,7 +165,6 @@ def _map_helper(
|
|
|
167
165
|
kwargs={},
|
|
168
166
|
order_outputs: bool = True,
|
|
169
167
|
return_exceptions: bool = False,
|
|
170
|
-
wrap_returned_exceptions: bool = True,
|
|
171
168
|
) -> typing.AsyncGenerator[typing.Any, None]:
|
|
172
169
|
"""Core implementation that supports `_map_async()`, `_starmap_async()` and `_for_each_async()`.
|
|
173
170
|
|
|
@@ -182,18 +179,14 @@ def _map_helper(
|
|
|
182
179
|
"""
|
|
183
180
|
...
|
|
184
181
|
|
|
185
|
-
def
|
|
186
|
-
def _invoked_from_sync_wrapper() -> bool:
|
|
187
|
-
"""Check whether the calling function was called from a sync wrapper."""
|
|
188
|
-
...
|
|
189
|
-
|
|
182
|
+
def _maybe_warn_about_wrap_exceptions(func_name: str, wrap_returned_exceptions: typing.Optional[bool]): ...
|
|
190
183
|
def _map_async(
|
|
191
184
|
self: modal.functions.Function,
|
|
192
185
|
*input_iterators: typing.Union[typing.Iterable[typing.Any], typing.AsyncIterable[typing.Any]],
|
|
193
186
|
kwargs={},
|
|
194
187
|
order_outputs: bool = True,
|
|
195
188
|
return_exceptions: bool = False,
|
|
196
|
-
wrap_returned_exceptions: bool =
|
|
189
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
197
190
|
) -> typing.AsyncGenerator[typing.Any, None]: ...
|
|
198
191
|
def _starmap_async(
|
|
199
192
|
self,
|
|
@@ -204,7 +197,7 @@ def _starmap_async(
|
|
|
204
197
|
kwargs={},
|
|
205
198
|
order_outputs: bool = True,
|
|
206
199
|
return_exceptions: bool = False,
|
|
207
|
-
wrap_returned_exceptions: bool =
|
|
200
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
208
201
|
) -> typing.AsyncIterable[typing.Any]: ...
|
|
209
202
|
async def _for_each_async(self, *input_iterators, kwargs={}, ignore_exceptions: bool = False) -> None: ...
|
|
210
203
|
def _map_sync(
|
|
@@ -213,7 +206,7 @@ def _map_sync(
|
|
|
213
206
|
kwargs={},
|
|
214
207
|
order_outputs: bool = True,
|
|
215
208
|
return_exceptions: bool = False,
|
|
216
|
-
wrap_returned_exceptions: bool =
|
|
209
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
217
210
|
) -> modal._utils.async_utils.AsyncOrSyncIterable:
|
|
218
211
|
"""Parallel map over a set of inputs.
|
|
219
212
|
|
|
@@ -247,7 +240,7 @@ def _map_sync(
|
|
|
247
240
|
|
|
248
241
|
@app.local_entrypoint()
|
|
249
242
|
def main():
|
|
250
|
-
# [0, 1,
|
|
243
|
+
# [0, 1, Exception('ohno')]
|
|
251
244
|
print(list(my_func.map(range(3), return_exceptions=True)))
|
|
252
245
|
```
|
|
253
246
|
"""
|
|
@@ -323,7 +316,7 @@ def _starmap_sync(
|
|
|
323
316
|
kwargs={},
|
|
324
317
|
order_outputs: bool = True,
|
|
325
318
|
return_exceptions: bool = False,
|
|
326
|
-
wrap_returned_exceptions: bool =
|
|
319
|
+
wrap_returned_exceptions: typing.Optional[bool] = None,
|
|
327
320
|
) -> modal._utils.async_utils.AsyncOrSyncIterable:
|
|
328
321
|
"""Like `map`, but spreads arguments over multiple function arguments.
|
|
329
322
|
|