modAL 1.2.2__tar.gz → 1.2.2.dev2__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.2 → modal-1.2.2.dev2}/PKG-INFO +2 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_clustered_functions.py +3 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_functions.py +97 -106
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_object.py +18 -47
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_output.py +4 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_resolver.py +35 -21
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/container_io_manager.py +22 -21
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/async_utils.py +3 -12
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/auth_token_manager.py +4 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/blob_utils.py +4 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/function_utils.py +2 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/grpc_utils.py +51 -80
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/task_command_router_client.py +4 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/app.py +4 -10
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/app.pyi +0 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/config.py +1 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/container.py +2 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/dict.py +2 -5
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/network_file_system.py +4 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/queues.py +4 -5
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/secret.py +2 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/client.py +115 -5
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/client.pyi +99 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cloud_bucket_mount.py +0 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cloud_bucket_mount.pyi +0 -4
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cls.py +33 -71
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cls.pyi +0 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/config.py +1 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/container_process.py +5 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/dict.py +17 -26
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/dict.pyi +0 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/environments.py +8 -16
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/environments.pyi +2 -6
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/__init__.py +3 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/flash.py +12 -7
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/file_io.py +27 -13
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/functions.pyi +10 -16
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/image.py +26 -26
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/image.pyi +9 -9
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/io_streams.py +44 -10
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/io_streams.pyi +91 -15
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/mount.py +26 -36
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/mount.pyi +7 -33
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/network_file_system.py +11 -19
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/network_file_system.pyi +2 -12
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/object.pyi +8 -35
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/parallel_map.py +31 -29
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/parallel_map.pyi +9 -3
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/proxy.py +6 -14
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/proxy.pyi +2 -10
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/queue.py +23 -31
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/queue.pyi +2 -12
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/runner.py +58 -62
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/runner.pyi +3 -4
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/sandbox.py +58 -44
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/sandbox.pyi +1 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/secret.py +44 -61
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/secret.pyi +4 -21
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/serving.py +11 -7
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/serving.pyi +8 -7
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/snapshot.py +8 -11
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/token_flow.py +1 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/volume.py +28 -44
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/volume.pyi +0 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/PKG-INFO +2 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/SOURCES.txt +0 -2
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api.proto +9 -23
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2.py +1015 -1031
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2.pyi +19 -90
- modal-1.2.2.dev2/modal_proto/modal_api_grpc.py +194 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_version/__init__.py +1 -1
- {modal-1.2.2 → modal-1.2.2.dev2}/pyproject.toml +1 -1
- modal-1.2.2/modal/_grpc_client.py +0 -171
- modal-1.2.2/modal/_load_context.py +0 -105
- modal-1.2.2/modal_proto/modal_api_grpc.py +0 -194
- {modal-1.2.2 → modal-1.2.2.dev2}/LICENSE +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/README.md +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/__main__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_billing.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_ipython.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_location.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_partial_function.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_pty.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_resources.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_serialization.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_traceback.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_tunnel.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_tunnel.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_type_manager.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/logger.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/_watcher.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/billing.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/README.md +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/builder/base-images.json +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/call_graph.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/_download.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/_traceback.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/app.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/cluster.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/entry_point.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/environment.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/import_refs.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/launch.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/profile.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/run.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/token.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/utils.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/cli/volume.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/container_process.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/exception.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/experimental/ipython.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/file_io.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/functions.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/gpu.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/object.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/output.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/partial_function.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/partial_function.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/py.typed +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/retries.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/running_app.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/schedule.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/scheduler_placement.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/snapshot.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/stream_type.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal/token_flow.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/__init__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/py.typed +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/modal_version/__main__.py +0 -0
- {modal-1.2.2 → modal-1.2.2.dev2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.2.2
|
|
3
|
+
Version: 1.2.2.dev2
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,7 +13,7 @@ Classifier: Topic :: System :: Distributed Computing
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Requires-Python:
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: aiohttp
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from modal._utils.async_utils import synchronize_api
|
|
8
|
+
from modal._utils.grpc_utils import retry_transient_errors
|
|
8
9
|
from modal.client import _Client
|
|
9
10
|
from modal.exception import InvalidError
|
|
10
11
|
from modal_proto import api_pb2
|
|
@@ -60,7 +61,8 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
60
61
|
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
|
61
62
|
|
|
62
63
|
if world_size > 1:
|
|
63
|
-
resp = await
|
|
64
|
+
resp: api_pb2.TaskClusterHelloResponse = await retry_transient_errors(
|
|
65
|
+
client.stub.TaskClusterHello,
|
|
64
66
|
api_pb2.TaskClusterHelloRequest(
|
|
65
67
|
task_id=task_id,
|
|
66
68
|
container_ip=container_ip,
|
|
@@ -19,8 +19,7 @@ from synchronicity.combined_types import MethodWithAio
|
|
|
19
19
|
from modal_proto import api_pb2
|
|
20
20
|
from modal_proto.modal_api_grpc import ModalClientModal
|
|
21
21
|
|
|
22
|
-
from .
|
|
23
|
-
from ._object import _Object, live_method, live_method_gen
|
|
22
|
+
from ._object import _get_environment_name, _Object, live_method, live_method_gen
|
|
24
23
|
from ._pty import get_pty_info
|
|
25
24
|
from ._resolver import Resolver
|
|
26
25
|
from ._resources import convert_fn_config_to_resources_config
|
|
@@ -54,7 +53,7 @@ from ._utils.function_utils import (
|
|
|
54
53
|
get_function_type,
|
|
55
54
|
is_async,
|
|
56
55
|
)
|
|
57
|
-
from ._utils.grpc_utils import
|
|
56
|
+
from ._utils.grpc_utils import RetryWarningMessage, retry_transient_errors
|
|
58
57
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes
|
|
59
58
|
from .call_graph import InputInfo, _reconstruct_call_graph
|
|
60
59
|
from .client import _Client
|
|
@@ -165,22 +164,21 @@ class _Invocation:
|
|
|
165
164
|
|
|
166
165
|
if from_spawn_map:
|
|
167
166
|
request.from_spawn_map = True
|
|
168
|
-
response = await
|
|
167
|
+
response = await retry_transient_errors(
|
|
168
|
+
client.stub.FunctionMap,
|
|
169
169
|
request,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
|
|
178
|
-
),
|
|
179
|
-
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
170
|
+
max_retries=None,
|
|
171
|
+
max_delay=30.0,
|
|
172
|
+
retry_warning_message=RetryWarningMessage(
|
|
173
|
+
message="Warning: `.spawn_map(...)` for function `{self._function_name}` is waiting to create"
|
|
174
|
+
"more function calls. This may be due to hitting rate limits or function backlog limits.",
|
|
175
|
+
warning_interval=10,
|
|
176
|
+
errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
|
|
180
177
|
),
|
|
178
|
+
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
181
179
|
)
|
|
182
180
|
else:
|
|
183
|
-
response = await client.stub.FunctionMap
|
|
181
|
+
response = await retry_transient_errors(client.stub.FunctionMap, request)
|
|
184
182
|
|
|
185
183
|
function_call_id = response.function_call_id
|
|
186
184
|
if response.pipelined_inputs:
|
|
@@ -200,7 +198,10 @@ class _Invocation:
|
|
|
200
198
|
request_put = api_pb2.FunctionPutInputsRequest(
|
|
201
199
|
function_id=function_id, inputs=[item], function_call_id=function_call_id
|
|
202
200
|
)
|
|
203
|
-
inputs_response: api_pb2.FunctionPutInputsResponse = await
|
|
201
|
+
inputs_response: api_pb2.FunctionPutInputsResponse = await retry_transient_errors(
|
|
202
|
+
client.stub.FunctionPutInputs,
|
|
203
|
+
request_put,
|
|
204
|
+
)
|
|
204
205
|
processed_inputs = inputs_response.inputs
|
|
205
206
|
if not processed_inputs:
|
|
206
207
|
raise Exception("Could not create function call - the input queue seems to be full")
|
|
@@ -242,9 +243,10 @@ class _Invocation:
|
|
|
242
243
|
start_idx=index,
|
|
243
244
|
end_idx=index,
|
|
244
245
|
)
|
|
245
|
-
response: api_pb2.FunctionGetOutputsResponse = await
|
|
246
|
+
response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
|
|
247
|
+
self.stub.FunctionGetOutputs,
|
|
246
248
|
request,
|
|
247
|
-
|
|
249
|
+
attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
248
250
|
)
|
|
249
251
|
|
|
250
252
|
if len(response.outputs) > 0:
|
|
@@ -264,7 +266,10 @@ class _Invocation:
|
|
|
264
266
|
|
|
265
267
|
item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
|
|
266
268
|
request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
|
|
267
|
-
await
|
|
269
|
+
await retry_transient_errors(
|
|
270
|
+
self.stub.FunctionRetryInputs,
|
|
271
|
+
request,
|
|
272
|
+
)
|
|
268
273
|
|
|
269
274
|
async def _get_single_output(self, expected_jwt: Optional[str] = None) -> api_pb2.FunctionGetOutputsItem:
|
|
270
275
|
# waits indefinitely for a single result for the function, and clear the outputs buffer after
|
|
@@ -368,8 +373,10 @@ class _Invocation:
|
|
|
368
373
|
start_idx=current_index,
|
|
369
374
|
end_idx=batch_end_index,
|
|
370
375
|
)
|
|
371
|
-
response: api_pb2.FunctionGetOutputsResponse = await
|
|
372
|
-
|
|
376
|
+
response: api_pb2.FunctionGetOutputsResponse = await retry_transient_errors(
|
|
377
|
+
self.stub.FunctionGetOutputs,
|
|
378
|
+
request,
|
|
379
|
+
attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
373
380
|
)
|
|
374
381
|
|
|
375
382
|
outputs = list(response.outputs)
|
|
@@ -441,7 +448,7 @@ class _InputPlaneInvocation:
|
|
|
441
448
|
)
|
|
442
449
|
|
|
443
450
|
metadata = await client.get_input_plane_metadata(input_plane_region)
|
|
444
|
-
response = await stub.AttemptStart
|
|
451
|
+
response = await retry_transient_errors(stub.AttemptStart, request, metadata=metadata)
|
|
445
452
|
attempt_token = response.attempt_token
|
|
446
453
|
|
|
447
454
|
return _InputPlaneInvocation(
|
|
@@ -461,9 +468,10 @@ class _InputPlaneInvocation:
|
|
|
461
468
|
requested_at=time.time(),
|
|
462
469
|
)
|
|
463
470
|
metadata = await self.client.get_input_plane_metadata(self.input_plane_region)
|
|
464
|
-
await_response: api_pb2.AttemptAwaitResponse = await
|
|
471
|
+
await_response: api_pb2.AttemptAwaitResponse = await retry_transient_errors(
|
|
472
|
+
self.stub.AttemptAwait,
|
|
465
473
|
await_request,
|
|
466
|
-
|
|
474
|
+
attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
467
475
|
metadata=metadata,
|
|
468
476
|
)
|
|
469
477
|
|
|
@@ -503,7 +511,11 @@ class _InputPlaneInvocation:
|
|
|
503
511
|
input=self.input_item,
|
|
504
512
|
attempt_token=self.attempt_token,
|
|
505
513
|
)
|
|
506
|
-
retry_response = await
|
|
514
|
+
retry_response = await retry_transient_errors(
|
|
515
|
+
self.stub.AttemptRetry,
|
|
516
|
+
retry_request,
|
|
517
|
+
metadata=metadata,
|
|
518
|
+
)
|
|
507
519
|
return retry_response.attempt_token
|
|
508
520
|
|
|
509
521
|
async def run_generator(self):
|
|
@@ -657,7 +669,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
657
669
|
@staticmethod
|
|
658
670
|
def from_local(
|
|
659
671
|
info: FunctionInfo,
|
|
660
|
-
app
|
|
672
|
+
app,
|
|
661
673
|
image: _Image,
|
|
662
674
|
env: Optional[dict[str, Optional[str]]] = None,
|
|
663
675
|
secrets: Optional[Collection[_Secret]] = None,
|
|
@@ -883,12 +895,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
883
895
|
is_web_endpoint, is_generator, restrict_output
|
|
884
896
|
)
|
|
885
897
|
|
|
886
|
-
async def _preload(
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
assert
|
|
898
|
+
async def _preload(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
899
|
+
assert resolver.client and resolver.client.stub
|
|
900
|
+
|
|
901
|
+
assert resolver.app_id
|
|
890
902
|
req = api_pb2.FunctionPrecreateRequest(
|
|
891
|
-
app_id=
|
|
903
|
+
app_id=resolver.app_id,
|
|
892
904
|
function_name=info.function_name,
|
|
893
905
|
function_type=function_type,
|
|
894
906
|
existing_function_id=existing_object_id or "",
|
|
@@ -904,12 +916,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
904
916
|
elif webhook_config:
|
|
905
917
|
req.webhook_config.CopyFrom(webhook_config)
|
|
906
918
|
|
|
907
|
-
response = await
|
|
908
|
-
self._hydrate(response.function_id,
|
|
919
|
+
response = await retry_transient_errors(resolver.client.stub.FunctionPrecreate, req)
|
|
920
|
+
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
|
909
921
|
|
|
910
|
-
async def _load(
|
|
911
|
-
|
|
912
|
-
):
|
|
922
|
+
async def _load(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
923
|
+
assert resolver.client and resolver.client.stub
|
|
913
924
|
with FunctionCreationStatus(resolver, tag) as function_creation_status:
|
|
914
925
|
timeout_secs = timeout
|
|
915
926
|
|
|
@@ -1105,16 +1116,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1105
1116
|
),
|
|
1106
1117
|
)
|
|
1107
1118
|
|
|
1108
|
-
assert
|
|
1119
|
+
assert resolver.app_id
|
|
1109
1120
|
assert (function_definition is None) != (function_data is None) # xor
|
|
1110
1121
|
request = api_pb2.FunctionCreateRequest(
|
|
1111
|
-
app_id=
|
|
1122
|
+
app_id=resolver.app_id,
|
|
1112
1123
|
function=function_definition,
|
|
1113
1124
|
function_data=function_data,
|
|
1114
1125
|
existing_function_id=existing_object_id or "",
|
|
1115
1126
|
)
|
|
1116
1127
|
try:
|
|
1117
|
-
response: api_pb2.FunctionCreateResponse = await
|
|
1128
|
+
response: api_pb2.FunctionCreateResponse = await retry_transient_errors(
|
|
1129
|
+
resolver.client.stub.FunctionCreate, request
|
|
1130
|
+
)
|
|
1118
1131
|
except GRPCError as exc:
|
|
1119
1132
|
if exc.status == Status.INVALID_ARGUMENT:
|
|
1120
1133
|
raise InvalidError(exc.message)
|
|
@@ -1129,14 +1142,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1129
1142
|
serve_mounts = {m for m in all_mounts if m.is_local()}
|
|
1130
1143
|
serve_mounts |= image._serve_mounts
|
|
1131
1144
|
obj._serve_mounts = frozenset(serve_mounts)
|
|
1132
|
-
self._hydrate(response.function_id,
|
|
1145
|
+
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
|
1133
1146
|
|
|
1134
1147
|
rep = f"Function({tag})"
|
|
1135
|
-
|
|
1136
|
-
# the only way to infer a LoadContext for an `@app.function`, and the App doesn't
|
|
1137
|
-
# get its client until *after* the Function is created.
|
|
1138
|
-
load_context = app._root_load_context if app else LoadContext.empty()
|
|
1139
|
-
obj = _Function._from_loader(_load, rep, preload=_preload, deps=_deps, load_context_overrides=load_context)
|
|
1148
|
+
obj = _Function._from_loader(_load, rep, preload=_preload, deps=_deps)
|
|
1140
1149
|
|
|
1141
1150
|
obj._raw_f = info.raw_f
|
|
1142
1151
|
obj._info = info
|
|
@@ -1178,12 +1187,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1178
1187
|
|
|
1179
1188
|
parent = self
|
|
1180
1189
|
|
|
1181
|
-
async def _load(
|
|
1182
|
-
param_bound_func: _Function,
|
|
1183
|
-
resolver: Resolver,
|
|
1184
|
-
load_context: LoadContext,
|
|
1185
|
-
existing_object_id: Optional[str],
|
|
1186
|
-
):
|
|
1190
|
+
async def _load(param_bound_func: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
1187
1191
|
if not parent.is_hydrated:
|
|
1188
1192
|
# While the base Object.hydrate() method appears to be idempotent, it's not always safe
|
|
1189
1193
|
await parent.hydrate()
|
|
@@ -1216,6 +1220,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1216
1220
|
param_bound_func._hydrate_from_other(parent)
|
|
1217
1221
|
return
|
|
1218
1222
|
|
|
1223
|
+
environment_name = _get_environment_name(None, resolver)
|
|
1219
1224
|
assert parent is not None and parent.is_hydrated
|
|
1220
1225
|
|
|
1221
1226
|
if options:
|
|
@@ -1255,11 +1260,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1255
1260
|
function_id=parent.object_id,
|
|
1256
1261
|
serialized_params=serialized_params,
|
|
1257
1262
|
function_options=options_pb,
|
|
1258
|
-
environment_name=
|
|
1263
|
+
environment_name=environment_name
|
|
1259
1264
|
or "", # TODO: investigate shouldn't environment name always be specified here?
|
|
1260
1265
|
)
|
|
1261
1266
|
|
|
1262
|
-
response = await parent._client.stub.FunctionBindParams
|
|
1267
|
+
response = await retry_transient_errors(parent._client.stub.FunctionBindParams, req)
|
|
1263
1268
|
param_bound_func._hydrate(response.bound_function_id, parent._client, response.handle_metadata)
|
|
1264
1269
|
|
|
1265
1270
|
def _deps():
|
|
@@ -1272,13 +1277,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1272
1277
|
return [dep for dep in all_deps if not dep.is_hydrated]
|
|
1273
1278
|
return []
|
|
1274
1279
|
|
|
1275
|
-
fun: _Function = _Function._from_loader(
|
|
1276
|
-
_load,
|
|
1277
|
-
"Function(parametrized)",
|
|
1278
|
-
hydrate_lazily=True,
|
|
1279
|
-
deps=_deps,
|
|
1280
|
-
load_context_overrides=self._load_context_overrides,
|
|
1281
|
-
)
|
|
1280
|
+
fun: _Function = _Function._from_loader(_load, "Function(parametrized)", hydrate_lazily=True, deps=_deps)
|
|
1282
1281
|
|
|
1283
1282
|
fun._info = self._info
|
|
1284
1283
|
fun._obj = obj
|
|
@@ -1329,7 +1328,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1329
1328
|
scaledown_window=scaledown_window,
|
|
1330
1329
|
)
|
|
1331
1330
|
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1332
|
-
await self.client.stub.FunctionUpdateSchedulingParams
|
|
1331
|
+
await retry_transient_errors(self.client.stub.FunctionUpdateSchedulingParams, request)
|
|
1333
1332
|
|
|
1334
1333
|
# One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
|
|
1335
1334
|
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
@@ -1376,43 +1375,34 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1376
1375
|
cls,
|
|
1377
1376
|
app_name: str,
|
|
1378
1377
|
name: str,
|
|
1379
|
-
|
|
1380
|
-
|
|
1378
|
+
namespace=None, # mdmd:line-hidden
|
|
1379
|
+
environment_name: Optional[str] = None,
|
|
1381
1380
|
):
|
|
1382
1381
|
# internal function lookup implementation that allows lookup of class "service functions"
|
|
1383
1382
|
# in addition to non-class functions
|
|
1384
|
-
async def _load_remote(
|
|
1385
|
-
|
|
1386
|
-
):
|
|
1383
|
+
async def _load_remote(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
1384
|
+
assert resolver.client and resolver.client.stub
|
|
1387
1385
|
request = api_pb2.FunctionGetRequest(
|
|
1388
1386
|
app_name=app_name,
|
|
1389
1387
|
object_tag=name,
|
|
1390
|
-
environment_name=
|
|
1388
|
+
environment_name=_get_environment_name(environment_name, resolver) or "",
|
|
1391
1389
|
)
|
|
1392
1390
|
try:
|
|
1393
|
-
response = await
|
|
1391
|
+
response = await retry_transient_errors(resolver.client.stub.FunctionGet, request)
|
|
1394
1392
|
except NotFoundError as exc:
|
|
1395
1393
|
# refine the error message
|
|
1396
|
-
env_context = (
|
|
1397
|
-
f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
|
|
1398
|
-
)
|
|
1394
|
+
env_context = f" (in the '{environment_name}' environment)" if environment_name else ""
|
|
1399
1395
|
raise NotFoundError(
|
|
1400
1396
|
f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
|
|
1401
1397
|
) from None
|
|
1402
1398
|
|
|
1403
1399
|
print_server_warnings(response.server_warnings)
|
|
1404
1400
|
|
|
1405
|
-
self._hydrate(response.function_id,
|
|
1401
|
+
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
|
1406
1402
|
|
|
1407
|
-
environment_rep =
|
|
1408
|
-
f", environment_name={load_context_overrides.environment_name!r}"
|
|
1409
|
-
if load_context_overrides._environment_name # slightly ugly - checking if _environment_name is overridden
|
|
1410
|
-
else ""
|
|
1411
|
-
)
|
|
1403
|
+
environment_rep = f", environment_name={environment_name!r}" if environment_name else ""
|
|
1412
1404
|
rep = f"modal.Function.from_name('{app_name}', '{name}'{environment_rep})"
|
|
1413
|
-
return cls._from_loader(
|
|
1414
|
-
_load_remote, rep, is_another_app=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
|
|
1415
|
-
)
|
|
1405
|
+
return cls._from_loader(_load_remote, rep, is_another_app=True, hydrate_lazily=True)
|
|
1416
1406
|
|
|
1417
1407
|
@classmethod
|
|
1418
1408
|
def from_name(
|
|
@@ -1422,7 +1412,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1422
1412
|
*,
|
|
1423
1413
|
namespace=None, # mdmd:line-hidden
|
|
1424
1414
|
environment_name: Optional[str] = None,
|
|
1425
|
-
client: Optional[_Client] = None,
|
|
1426
1415
|
) -> "_Function":
|
|
1427
1416
|
"""Reference a Function from a deployed App by its name.
|
|
1428
1417
|
|
|
@@ -1446,9 +1435,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1446
1435
|
)
|
|
1447
1436
|
|
|
1448
1437
|
warn_if_passing_namespace(namespace, "modal.Function.from_name")
|
|
1449
|
-
return cls._from_name(
|
|
1450
|
-
app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
|
|
1451
|
-
)
|
|
1438
|
+
return cls._from_name(app_name, name, environment_name=environment_name)
|
|
1452
1439
|
|
|
1453
1440
|
@property
|
|
1454
1441
|
def tag(self) -> str:
|
|
@@ -1671,8 +1658,8 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1671
1658
|
input_queue,
|
|
1672
1659
|
self.client,
|
|
1673
1660
|
)
|
|
1674
|
-
|
|
1675
|
-
fc
|
|
1661
|
+
metadata = api_pb2.FunctionCallFromIdResponse(function_call_id=function_call_id, num_inputs=num_inputs)
|
|
1662
|
+
fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, metadata)
|
|
1676
1663
|
return fc
|
|
1677
1664
|
|
|
1678
1665
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
|
@@ -1901,9 +1888,10 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1901
1888
|
@live_method
|
|
1902
1889
|
async def get_current_stats(self) -> FunctionStats:
|
|
1903
1890
|
"""Return a `FunctionStats` object describing the current function's queue and runner counts."""
|
|
1904
|
-
resp = await
|
|
1891
|
+
resp = await retry_transient_errors(
|
|
1892
|
+
self.client.stub.FunctionGetCurrentStats,
|
|
1905
1893
|
api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
|
|
1906
|
-
|
|
1894
|
+
total_timeout=10.0,
|
|
1907
1895
|
)
|
|
1908
1896
|
return FunctionStats(backlog=resp.backlog, num_total_runners=resp.num_total_tasks)
|
|
1909
1897
|
|
|
@@ -1941,16 +1929,19 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1941
1929
|
def _invocation(self):
|
|
1942
1930
|
return _Invocation(self.client.stub, self.object_id, self.client)
|
|
1943
1931
|
|
|
1932
|
+
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
1933
|
+
if not metadata:
|
|
1934
|
+
return
|
|
1935
|
+
assert isinstance(metadata, api_pb2.FunctionCallFromIdResponse)
|
|
1936
|
+
self._num_inputs = metadata.num_inputs
|
|
1937
|
+
|
|
1944
1938
|
@live_method
|
|
1945
1939
|
async def num_inputs(self) -> int:
|
|
1946
1940
|
"""Get the number of inputs in the function call."""
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
resp = await self.client.stub.FunctionCallFromId(request)
|
|
1950
|
-
self._num_inputs = resp.num_inputs # cached
|
|
1941
|
+
# Should have been hydrated.
|
|
1942
|
+
assert self._num_inputs is not None
|
|
1951
1943
|
return self._num_inputs
|
|
1952
1944
|
|
|
1953
|
-
@live_method
|
|
1954
1945
|
async def get(self, timeout: Optional[float] = None, *, index: int = 0) -> ReturnType:
|
|
1955
1946
|
"""Get the result of the index-th input of the function call.
|
|
1956
1947
|
`.spawn()` calls have a single output, so only specifying `index=0` is valid.
|
|
@@ -1994,7 +1985,6 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1994
1985
|
async for _, item in self._invocation().enumerate(start_index=start, end_index=end):
|
|
1995
1986
|
yield item
|
|
1996
1987
|
|
|
1997
|
-
@live_method
|
|
1998
1988
|
async def get_call_graph(self) -> list[InputInfo]:
|
|
1999
1989
|
"""Returns a structure representing the call graph from a given root
|
|
2000
1990
|
call ID, along with the status of execution for each node.
|
|
@@ -2004,10 +1994,9 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2004
1994
|
"""
|
|
2005
1995
|
assert self._client and self._client.stub
|
|
2006
1996
|
request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
|
|
2007
|
-
response = await self._client.stub.FunctionGetCallGraph
|
|
1997
|
+
response = await retry_transient_errors(self._client.stub.FunctionGetCallGraph, request)
|
|
2008
1998
|
return _reconstruct_call_graph(response)
|
|
2009
1999
|
|
|
2010
|
-
@live_method
|
|
2011
2000
|
async def cancel(
|
|
2012
2001
|
self,
|
|
2013
2002
|
# if true, containers running the inputs are forcibly terminated
|
|
@@ -2023,7 +2012,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2023
2012
|
function_call_id=self.object_id, terminate_containers=terminate_containers
|
|
2024
2013
|
)
|
|
2025
2014
|
assert self._client and self._client.stub
|
|
2026
|
-
await self._client.stub.FunctionCallCancel
|
|
2015
|
+
await retry_transient_errors(self._client.stub.FunctionCallCancel, request)
|
|
2027
2016
|
|
|
2028
2017
|
@staticmethod
|
|
2029
2018
|
async def from_id(function_call_id: str, client: Optional[_Client] = None) -> "_FunctionCall[Any]":
|
|
@@ -2045,18 +2034,20 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2045
2034
|
if you no longer have access to the original object returned from `Function.spawn`.
|
|
2046
2035
|
|
|
2047
2036
|
"""
|
|
2037
|
+
if client is None:
|
|
2038
|
+
client = await _Client.from_env()
|
|
2048
2039
|
|
|
2049
|
-
async def _load(
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
self._hydrate(function_call_id, load_context.client, None)
|
|
2040
|
+
async def _load(self: _FunctionCall, resolver: Resolver, existing_object_id: Optional[str]):
|
|
2041
|
+
request = api_pb2.FunctionCallFromIdRequest(function_call_id=function_call_id)
|
|
2042
|
+
resp = await retry_transient_errors(resolver.client.stub.FunctionCallFromId, request)
|
|
2043
|
+
self._hydrate(function_call_id, resolver.client, resp)
|
|
2054
2044
|
|
|
2055
2045
|
rep = f"FunctionCall.from_id({function_call_id!r})"
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2046
|
+
fc: _FunctionCall[Any] = _FunctionCall._from_loader(_load, rep, hydrate_lazily=True)
|
|
2047
|
+
# We already know the object ID, so we can set it directly
|
|
2048
|
+
fc._object_id = function_call_id
|
|
2049
|
+
fc._client = client
|
|
2050
|
+
return fc
|
|
2060
2051
|
|
|
2061
2052
|
@staticmethod
|
|
2062
2053
|
async def gather(*function_calls: "_FunctionCall[T]") -> typing.Sequence[T]:
|
|
@@ -10,7 +10,6 @@ from typing_extensions import Self
|
|
|
10
10
|
|
|
11
11
|
from modal._traceback import suppress_tb_frames
|
|
12
12
|
|
|
13
|
-
from ._load_context import LoadContext
|
|
14
13
|
from ._resolver import Resolver
|
|
15
14
|
from ._utils.async_utils import aclosing
|
|
16
15
|
from ._utils.deprecation import deprecation_warning
|
|
@@ -21,19 +20,11 @@ from .exception import ExecutionError, InvalidError
|
|
|
21
20
|
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP: int = 300
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def _get_environment_name(
|
|
25
|
-
environment_name: Optional[str] = None,
|
|
26
|
-
) -> Optional[str]:
|
|
27
|
-
"""Get environment name from various sources.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
environment_name: Explicitly provided environment name (highest priority)
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Environment name from first available source, or config default
|
|
34
|
-
"""
|
|
23
|
+
def _get_environment_name(environment_name: Optional[str] = None, resolver: Optional[Resolver] = None) -> Optional[str]:
|
|
35
24
|
if environment_name:
|
|
36
25
|
return environment_name
|
|
26
|
+
elif resolver and resolver.environment_name:
|
|
27
|
+
return resolver.environment_name
|
|
37
28
|
else:
|
|
38
29
|
return config.get("environment")
|
|
39
30
|
|
|
@@ -43,14 +34,13 @@ class _Object:
|
|
|
43
34
|
_prefix_to_type: ClassVar[dict[str, type]] = {}
|
|
44
35
|
|
|
45
36
|
# For constructors
|
|
46
|
-
_load: Optional[Callable[[Self, Resolver,
|
|
47
|
-
_preload: Optional[Callable[[Self, Resolver,
|
|
37
|
+
_load: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]]
|
|
38
|
+
_preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]]
|
|
48
39
|
_rep: str
|
|
49
40
|
_is_another_app: bool
|
|
50
41
|
_hydrate_lazily: bool
|
|
51
42
|
_deps: Optional[Callable[..., Sequence["_Object"]]]
|
|
52
43
|
_deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None
|
|
53
|
-
_load_context_overrides: LoadContext
|
|
54
44
|
|
|
55
45
|
# For hydrated objects
|
|
56
46
|
_object_id: Optional[str]
|
|
@@ -76,15 +66,13 @@ class _Object:
|
|
|
76
66
|
def _init(
|
|
77
67
|
self,
|
|
78
68
|
rep: str,
|
|
79
|
-
load: Optional[Callable[[Self, Resolver,
|
|
69
|
+
load: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
|
|
80
70
|
is_another_app: bool = False,
|
|
81
|
-
preload: Optional[Callable[[Self, Resolver,
|
|
71
|
+
preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
|
|
82
72
|
hydrate_lazily: bool = False,
|
|
83
73
|
deps: Optional[Callable[..., Sequence["_Object"]]] = None,
|
|
84
74
|
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
|
85
75
|
name: Optional[str] = None,
|
|
86
|
-
*,
|
|
87
|
-
load_context_overrides: Optional[LoadContext] = None,
|
|
88
76
|
):
|
|
89
77
|
self._local_uuid = str(uuid.uuid4())
|
|
90
78
|
self._load = load
|
|
@@ -94,9 +82,6 @@ class _Object:
|
|
|
94
82
|
self._hydrate_lazily = hydrate_lazily
|
|
95
83
|
self._deps = deps
|
|
96
84
|
self._deduplication_key = deduplication_key
|
|
97
|
-
self._load_context_overrides = (
|
|
98
|
-
load_context_overrides if load_context_overrides is not None else LoadContext.empty()
|
|
99
|
-
)
|
|
100
85
|
|
|
101
86
|
self._object_id = None
|
|
102
87
|
self._client = None
|
|
@@ -178,30 +163,18 @@ class _Object:
|
|
|
178
163
|
@classmethod
|
|
179
164
|
def _from_loader(
|
|
180
165
|
cls,
|
|
181
|
-
load: Callable[[Self, Resolver,
|
|
166
|
+
load: Callable[[Self, Resolver, Optional[str]], Awaitable[None]],
|
|
182
167
|
rep: str,
|
|
183
168
|
is_another_app: bool = False,
|
|
184
|
-
preload: Optional[Callable[[Self, Resolver,
|
|
169
|
+
preload: Optional[Callable[[Self, Resolver, Optional[str]], Awaitable[None]]] = None,
|
|
185
170
|
hydrate_lazily: bool = False,
|
|
186
171
|
deps: Optional[Callable[..., Sequence["_Object"]]] = None,
|
|
187
172
|
deduplication_key: Optional[Callable[[], Awaitable[Hashable]]] = None,
|
|
188
173
|
name: Optional[str] = None,
|
|
189
|
-
*,
|
|
190
|
-
load_context_overrides: LoadContext,
|
|
191
174
|
):
|
|
192
175
|
# TODO(erikbern): flip the order of the two first arguments
|
|
193
176
|
obj = _Object.__new__(cls)
|
|
194
|
-
obj._init(
|
|
195
|
-
rep,
|
|
196
|
-
load,
|
|
197
|
-
is_another_app,
|
|
198
|
-
preload,
|
|
199
|
-
hydrate_lazily,
|
|
200
|
-
deps,
|
|
201
|
-
deduplication_key,
|
|
202
|
-
name,
|
|
203
|
-
load_context_overrides=load_context_overrides,
|
|
204
|
-
)
|
|
177
|
+
obj._init(rep, load, is_another_app, preload, hydrate_lazily, deps, deduplication_key, name)
|
|
205
178
|
return obj
|
|
206
179
|
|
|
207
180
|
@staticmethod
|
|
@@ -302,27 +275,25 @@ class _Object:
|
|
|
302
275
|
|
|
303
276
|
*Added in v0.72.39*: This method replaces the deprecated `.resolve()` method.
|
|
304
277
|
"""
|
|
305
|
-
# TODO: add deprecation for the client argument here - should be added in constructors instead
|
|
306
278
|
if self._is_hydrated:
|
|
307
279
|
if self.client._snapshotted and not self._is_rehydrated:
|
|
308
280
|
# memory snapshots capture references which must be rehydrated
|
|
309
281
|
# on restore to handle staleness.
|
|
310
282
|
logger.debug(f"rehydrating {self} after snapshot")
|
|
311
283
|
self._is_hydrated = False # un-hydrate and re-resolve
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
resolver
|
|
315
|
-
await resolver.load(typing.cast(_Object, self), root_load_context)
|
|
284
|
+
c = client if client is not None else await _Client.from_env()
|
|
285
|
+
resolver = Resolver(c)
|
|
286
|
+
await resolver.load(typing.cast(_Object, self))
|
|
316
287
|
self._is_rehydrated = True
|
|
317
|
-
logger.debug(f"rehydrated {self} with client {id(
|
|
288
|
+
logger.debug(f"rehydrated {self} with client {id(c)}")
|
|
318
289
|
elif not self._hydrate_lazily:
|
|
290
|
+
# TODO(michael) can remove _hydrate lazily? I think all objects support it now?
|
|
319
291
|
self._validate_is_hydrated()
|
|
320
292
|
else:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
resolver = Resolver()
|
|
293
|
+
c = client if client is not None else await _Client.from_env()
|
|
294
|
+
resolver = Resolver(c)
|
|
324
295
|
with suppress_tb_frames(1): # skip this frame by default
|
|
325
|
-
await resolver.load(self
|
|
296
|
+
await resolver.load(self)
|
|
326
297
|
return self
|
|
327
298
|
|
|
328
299
|
|
|
@@ -34,7 +34,7 @@ from rich.text import Text
|
|
|
34
34
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
35
35
|
from modal_proto import api_pb2
|
|
36
36
|
|
|
37
|
-
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES,
|
|
37
|
+
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, retry_transient_errors
|
|
38
38
|
from ._utils.shell_utils import stream_from_stdin, write_to_fd
|
|
39
39
|
from .client import _Client
|
|
40
40
|
from .config import logger
|
|
@@ -489,11 +489,12 @@ async def stream_pty_shell_input(client: _Client, exec_id: str, finish_event: as
|
|
|
489
489
|
"""
|
|
490
490
|
|
|
491
491
|
async def _handle_input(data: bytes, message_index: int):
|
|
492
|
-
await
|
|
492
|
+
await retry_transient_errors(
|
|
493
|
+
client.stub.ContainerExecPutInput,
|
|
493
494
|
api_pb2.ContainerExecPutInputRequest(
|
|
494
495
|
exec_id=exec_id, input=api_pb2.RuntimeInputMessage(message=data, message_index=message_index)
|
|
495
496
|
),
|
|
496
|
-
|
|
497
|
+
total_timeout=10,
|
|
497
498
|
)
|
|
498
499
|
|
|
499
500
|
async with stream_from_stdin(_handle_input, use_raw_terminal=True):
|