modal 1.1.5.dev66__py3-none-any.whl → 1.3.1.dev8__py3-none-any.whl
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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/__init__.py +4 -4
- modal/__main__.py +4 -29
- modal/_billing.py +84 -0
- modal/_clustered_functions.py +1 -3
- modal/_container_entrypoint.py +33 -208
- modal/_functions.py +171 -138
- modal/_grpc_client.py +191 -0
- modal/_ipython.py +16 -6
- modal/_load_context.py +106 -0
- modal/_object.py +72 -21
- modal/_output.py +12 -14
- modal/_partial_function.py +31 -4
- modal/_resolver.py +44 -57
- modal/_runtime/container_io_manager.py +30 -28
- modal/_runtime/container_io_manager.pyi +42 -44
- modal/_runtime/gpu_memory_snapshot.py +9 -7
- modal/_runtime/user_code_event_loop.py +80 -0
- modal/_runtime/user_code_imports.py +236 -10
- modal/_serialization.py +2 -1
- modal/_traceback.py +4 -13
- modal/_tunnel.py +16 -11
- modal/_tunnel.pyi +25 -3
- modal/_utils/async_utils.py +337 -10
- modal/_utils/auth_token_manager.py +1 -4
- modal/_utils/blob_utils.py +29 -22
- modal/_utils/function_utils.py +20 -21
- modal/_utils/grpc_testing.py +6 -3
- modal/_utils/grpc_utils.py +223 -64
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +2 -3
- modal/_utils/package_utils.py +0 -1
- modal/_utils/rand_pb_testing.py +8 -1
- modal/_utils/task_command_router_client.py +524 -0
- modal/_vendor/cloudpickle.py +144 -48
- modal/app.py +285 -105
- modal/app.pyi +216 -53
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +6 -3
- modal/builder/PREVIEW.txt +2 -1
- modal/builder/base-images.json +4 -2
- modal/cli/_download.py +19 -3
- modal/cli/cluster.py +4 -2
- modal/cli/config.py +3 -1
- modal/cli/container.py +5 -4
- modal/cli/dict.py +5 -2
- modal/cli/entry_point.py +26 -2
- modal/cli/environment.py +2 -16
- modal/cli/launch.py +1 -76
- modal/cli/network_file_system.py +5 -20
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +5 -4
- modal/cli/run.py +24 -204
- modal/cli/secret.py +1 -2
- modal/cli/shell.py +375 -0
- modal/cli/utils.py +1 -13
- modal/cli/volume.py +11 -17
- modal/client.py +16 -125
- modal/client.pyi +94 -144
- modal/cloud_bucket_mount.py +3 -1
- modal/cloud_bucket_mount.pyi +4 -0
- modal/cls.py +101 -64
- modal/cls.pyi +9 -8
- modal/config.py +21 -1
- modal/container_process.py +288 -12
- modal/container_process.pyi +99 -38
- modal/dict.py +72 -33
- modal/dict.pyi +88 -57
- modal/environments.py +16 -8
- modal/environments.pyi +6 -2
- modal/exception.py +154 -16
- modal/experimental/__init__.py +24 -53
- modal/experimental/flash.py +161 -74
- modal/experimental/flash.pyi +97 -49
- modal/file_io.py +50 -92
- modal/file_io.pyi +117 -89
- modal/functions.pyi +70 -87
- modal/image.py +82 -47
- modal/image.pyi +51 -30
- modal/io_streams.py +500 -149
- modal/io_streams.pyi +279 -189
- modal/mount.py +60 -46
- modal/mount.pyi +41 -17
- modal/network_file_system.py +19 -11
- modal/network_file_system.pyi +72 -39
- modal/object.pyi +114 -22
- modal/parallel_map.py +42 -44
- modal/parallel_map.pyi +9 -17
- modal/partial_function.pyi +4 -2
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +45 -38
- modal/queue.pyi +88 -52
- modal/runner.py +96 -96
- modal/runner.pyi +44 -27
- modal/sandbox.py +225 -107
- modal/sandbox.pyi +226 -60
- modal/secret.py +58 -56
- modal/secret.pyi +28 -13
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +29 -15
- modal/snapshot.pyi +18 -10
- modal/token_flow.py +1 -1
- modal/token_flow.pyi +4 -6
- modal/volume.py +102 -55
- modal/volume.pyi +125 -66
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
- modal-1.3.1.dev8.dist-info/RECORD +189 -0
- modal_proto/api.proto +141 -70
- modal_proto/api_grpc.py +42 -26
- modal_proto/api_pb2.py +1123 -1103
- modal_proto/api_pb2.pyi +331 -83
- modal_proto/api_pb2_grpc.py +80 -48
- modal_proto/api_pb2_grpc.pyi +26 -18
- modal_proto/modal_api_grpc.py +175 -174
- modal_proto/task_command_router.proto +164 -0
- modal_proto/task_command_router_grpc.py +138 -0
- modal_proto/task_command_router_pb2.py +180 -0
- modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +148 -57
- modal_proto/task_command_router_pb2_grpc.py +272 -0
- modal_proto/task_command_router_pb2_grpc.pyi +100 -0
- modal_version/__init__.py +1 -1
- modal_version/__main__.py +1 -1
- modal/cli/programs/launch_instance_ssh.py +0 -94
- modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.5.dev66.dist-info/RECORD +0 -191
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- modal_proto/sandbox_router.proto +0 -125
- modal_proto/sandbox_router_grpc.py +0 -89
- modal_proto/sandbox_router_pb2.py +0 -128
- modal_proto/sandbox_router_pb2_grpc.py +0 -169
- modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
modal/_functions.py
CHANGED
|
@@ -13,13 +13,14 @@ from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Optional, Union
|
|
|
13
13
|
|
|
14
14
|
import typing_extensions
|
|
15
15
|
from google.protobuf.message import Message
|
|
16
|
-
from grpclib import
|
|
16
|
+
from grpclib import Status
|
|
17
17
|
from synchronicity.combined_types import MethodWithAio
|
|
18
18
|
|
|
19
19
|
from modal_proto import api_pb2
|
|
20
20
|
from modal_proto.modal_api_grpc import ModalClientModal
|
|
21
21
|
|
|
22
|
-
from .
|
|
22
|
+
from ._load_context import LoadContext
|
|
23
|
+
from ._object import _Object, live_method, live_method_gen
|
|
23
24
|
from ._pty import get_pty_info
|
|
24
25
|
from ._resolver import Resolver
|
|
25
26
|
from ._resources import convert_fn_config_to_resources_config
|
|
@@ -37,6 +38,7 @@ from ._utils.async_utils import (
|
|
|
37
38
|
aclosing,
|
|
38
39
|
async_merge,
|
|
39
40
|
callable_to_agen,
|
|
41
|
+
deprecate_aio_usage,
|
|
40
42
|
synchronizer,
|
|
41
43
|
warn_if_generator_is_not_consumed,
|
|
42
44
|
)
|
|
@@ -53,7 +55,7 @@ from ._utils.function_utils import (
|
|
|
53
55
|
get_function_type,
|
|
54
56
|
is_async,
|
|
55
57
|
)
|
|
56
|
-
from ._utils.grpc_utils import
|
|
58
|
+
from ._utils.grpc_utils import Retry, RetryWarningMessage
|
|
57
59
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes
|
|
58
60
|
from .call_graph import InputInfo, _reconstruct_call_graph
|
|
59
61
|
from .client import _Client
|
|
@@ -89,13 +91,13 @@ from .parallel_map import (
|
|
|
89
91
|
from .proxy import _Proxy
|
|
90
92
|
from .retries import Retries, RetryManager
|
|
91
93
|
from .schedule import Schedule
|
|
92
|
-
from .scheduler_placement import SchedulerPlacement
|
|
93
94
|
from .secret import _Secret
|
|
94
95
|
from .volume import _Volume
|
|
95
96
|
|
|
96
97
|
if TYPE_CHECKING:
|
|
97
98
|
import modal.app
|
|
98
99
|
import modal.cls
|
|
100
|
+
import modal.functions
|
|
99
101
|
|
|
100
102
|
MAX_INTERNAL_FAILURE_COUNT = 8
|
|
101
103
|
TERMINAL_STATUSES = (
|
|
@@ -164,21 +166,22 @@ class _Invocation:
|
|
|
164
166
|
|
|
165
167
|
if from_spawn_map:
|
|
166
168
|
request.from_spawn_map = True
|
|
167
|
-
response = await
|
|
168
|
-
client.stub.FunctionMap,
|
|
169
|
+
response = await client.stub.FunctionMap(
|
|
169
170
|
request,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
retry=Retry(
|
|
172
|
+
max_retries=None,
|
|
173
|
+
max_delay=30.0,
|
|
174
|
+
warning_message=RetryWarningMessage(
|
|
175
|
+
message="Warning: `.spawn_map(...)` for function `{self._function_name}` is waiting to create"
|
|
176
|
+
"more function calls. This may be due to hitting rate limits or function backlog limits.",
|
|
177
|
+
warning_interval=10,
|
|
178
|
+
errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
|
|
179
|
+
),
|
|
180
|
+
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
177
181
|
),
|
|
178
|
-
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
179
182
|
)
|
|
180
183
|
else:
|
|
181
|
-
response = await
|
|
184
|
+
response = await client.stub.FunctionMap(request)
|
|
182
185
|
|
|
183
186
|
function_call_id = response.function_call_id
|
|
184
187
|
if response.pipelined_inputs:
|
|
@@ -198,10 +201,7 @@ class _Invocation:
|
|
|
198
201
|
request_put = api_pb2.FunctionPutInputsRequest(
|
|
199
202
|
function_id=function_id, inputs=[item], function_call_id=function_call_id
|
|
200
203
|
)
|
|
201
|
-
inputs_response: api_pb2.FunctionPutInputsResponse = await
|
|
202
|
-
client.stub.FunctionPutInputs,
|
|
203
|
-
request_put,
|
|
204
|
-
)
|
|
204
|
+
inputs_response: api_pb2.FunctionPutInputsResponse = await client.stub.FunctionPutInputs(request_put)
|
|
205
205
|
processed_inputs = inputs_response.inputs
|
|
206
206
|
if not processed_inputs:
|
|
207
207
|
raise Exception("Could not create function call - the input queue seems to be full")
|
|
@@ -243,10 +243,9 @@ class _Invocation:
|
|
|
243
243
|
start_idx=index,
|
|
244
244
|
end_idx=index,
|
|
245
245
|
)
|
|
246
|
-
response: api_pb2.FunctionGetOutputsResponse = await
|
|
247
|
-
self.stub.FunctionGetOutputs,
|
|
246
|
+
response: api_pb2.FunctionGetOutputsResponse = await self.stub.FunctionGetOutputs(
|
|
248
247
|
request,
|
|
249
|
-
attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
248
|
+
retry=Retry(attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD),
|
|
250
249
|
)
|
|
251
250
|
|
|
252
251
|
if len(response.outputs) > 0:
|
|
@@ -266,10 +265,7 @@ class _Invocation:
|
|
|
266
265
|
|
|
267
266
|
item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
|
|
268
267
|
request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
|
|
269
|
-
await
|
|
270
|
-
self.stub.FunctionRetryInputs,
|
|
271
|
-
request,
|
|
272
|
-
)
|
|
268
|
+
await self.stub.FunctionRetryInputs(request)
|
|
273
269
|
|
|
274
270
|
async def _get_single_output(self, expected_jwt: Optional[str] = None) -> api_pb2.FunctionGetOutputsItem:
|
|
275
271
|
# waits indefinitely for a single result for the function, and clear the outputs buffer after
|
|
@@ -373,10 +369,8 @@ class _Invocation:
|
|
|
373
369
|
start_idx=current_index,
|
|
374
370
|
end_idx=batch_end_index,
|
|
375
371
|
)
|
|
376
|
-
response: api_pb2.FunctionGetOutputsResponse = await
|
|
377
|
-
|
|
378
|
-
request,
|
|
379
|
-
attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
372
|
+
response: api_pb2.FunctionGetOutputsResponse = await self.stub.FunctionGetOutputs(
|
|
373
|
+
request, retry=Retry(attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD)
|
|
380
374
|
)
|
|
381
375
|
|
|
382
376
|
outputs = list(response.outputs)
|
|
@@ -448,7 +442,7 @@ class _InputPlaneInvocation:
|
|
|
448
442
|
)
|
|
449
443
|
|
|
450
444
|
metadata = await client.get_input_plane_metadata(input_plane_region)
|
|
451
|
-
response = await
|
|
445
|
+
response = await stub.AttemptStart(request, metadata=metadata)
|
|
452
446
|
attempt_token = response.attempt_token
|
|
453
447
|
|
|
454
448
|
return _InputPlaneInvocation(
|
|
@@ -468,10 +462,9 @@ class _InputPlaneInvocation:
|
|
|
468
462
|
requested_at=time.time(),
|
|
469
463
|
)
|
|
470
464
|
metadata = await self.client.get_input_plane_metadata(self.input_plane_region)
|
|
471
|
-
await_response: api_pb2.AttemptAwaitResponse = await
|
|
472
|
-
self.stub.AttemptAwait,
|
|
465
|
+
await_response: api_pb2.AttemptAwaitResponse = await self.stub.AttemptAwait(
|
|
473
466
|
await_request,
|
|
474
|
-
attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
467
|
+
retry=Retry(attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD),
|
|
475
468
|
metadata=metadata,
|
|
476
469
|
)
|
|
477
470
|
|
|
@@ -511,11 +504,7 @@ class _InputPlaneInvocation:
|
|
|
511
504
|
input=self.input_item,
|
|
512
505
|
attempt_token=self.attempt_token,
|
|
513
506
|
)
|
|
514
|
-
retry_response = await
|
|
515
|
-
self.stub.AttemptRetry,
|
|
516
|
-
retry_request,
|
|
517
|
-
metadata=metadata,
|
|
518
|
-
)
|
|
507
|
+
retry_response = await self.stub.AttemptRetry(retry_request, metadata=metadata)
|
|
519
508
|
return retry_response.attempt_token
|
|
520
509
|
|
|
521
510
|
async def run_generator(self):
|
|
@@ -549,6 +538,7 @@ class _InputPlaneInvocation:
|
|
|
549
538
|
async def _get_metadata(input_plane_region: str, client: _Client) -> list[tuple[str, str]]:
|
|
550
539
|
if not input_plane_region:
|
|
551
540
|
return []
|
|
541
|
+
assert client._auth_token_manager, "Client is not open"
|
|
552
542
|
token = await client._auth_token_manager.get_token()
|
|
553
543
|
return [("x-modal-input-plane-region", input_plane_region), ("x-modal-auth-token", token)]
|
|
554
544
|
|
|
@@ -601,10 +591,25 @@ class _FunctionSpec:
|
|
|
601
591
|
cpu: Optional[Union[float, tuple[float, float]]]
|
|
602
592
|
memory: Optional[Union[int, tuple[int, int]]]
|
|
603
593
|
ephemeral_disk: Optional[int]
|
|
604
|
-
scheduler_placement: Optional[SchedulerPlacement]
|
|
594
|
+
scheduler_placement: Optional[api_pb2.SchedulerPlacement]
|
|
605
595
|
proxy: Optional[_Proxy]
|
|
606
596
|
|
|
607
597
|
|
|
598
|
+
def _get_supported_input_output_formats(is_web_endpoint: bool, is_generator: bool, restrict_output: bool):
|
|
599
|
+
if is_web_endpoint:
|
|
600
|
+
supported_input_formats = [api_pb2.DATA_FORMAT_ASGI]
|
|
601
|
+
supported_output_formats = [api_pb2.DATA_FORMAT_ASGI, api_pb2.DATA_FORMAT_GENERATOR_DONE]
|
|
602
|
+
else:
|
|
603
|
+
supported_input_formats = [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]
|
|
604
|
+
if restrict_output:
|
|
605
|
+
supported_output_formats = [api_pb2.DATA_FORMAT_CBOR]
|
|
606
|
+
else:
|
|
607
|
+
supported_output_formats = [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]
|
|
608
|
+
if is_generator:
|
|
609
|
+
supported_output_formats.append(api_pb2.DATA_FORMAT_GENERATOR_DONE)
|
|
610
|
+
return supported_input_formats, supported_output_formats
|
|
611
|
+
|
|
612
|
+
|
|
608
613
|
P = typing_extensions.ParamSpec("P")
|
|
609
614
|
ReturnType = typing.TypeVar("ReturnType", covariant=True)
|
|
610
615
|
OriginalReturnType = typing.TypeVar(
|
|
@@ -654,7 +659,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
654
659
|
@staticmethod
|
|
655
660
|
def from_local(
|
|
656
661
|
info: FunctionInfo,
|
|
657
|
-
app,
|
|
662
|
+
app: Optional["modal.app._App"], # App here should only be None in case of Image.run_function
|
|
658
663
|
image: _Image,
|
|
659
664
|
env: Optional[dict[str, Optional[str]]] = None,
|
|
660
665
|
secrets: Optional[Collection[_Secret]] = None,
|
|
@@ -680,7 +685,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
680
685
|
batch_max_size: Optional[int] = None,
|
|
681
686
|
batch_wait_ms: Optional[int] = None,
|
|
682
687
|
cloud: Optional[str] = None,
|
|
683
|
-
|
|
688
|
+
region: Optional[Union[str, Sequence[str]]] = None,
|
|
689
|
+
nonpreemptible: bool = False,
|
|
684
690
|
is_builder_function: bool = False,
|
|
685
691
|
is_auto_snapshot: bool = False,
|
|
686
692
|
enable_memory_snapshot: bool = False,
|
|
@@ -690,13 +696,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
690
696
|
# Experimental: Clustered functions
|
|
691
697
|
cluster_size: Optional[int] = None,
|
|
692
698
|
rdma: Optional[bool] = None,
|
|
693
|
-
|
|
699
|
+
single_use_containers: bool = False,
|
|
694
700
|
ephemeral_disk: Optional[int] = None,
|
|
695
701
|
include_source: bool = True,
|
|
696
702
|
experimental_options: Optional[dict[str, str]] = None,
|
|
697
703
|
_experimental_proxy_ip: Optional[str] = None,
|
|
698
704
|
_experimental_custom_scaling_factor: Optional[float] = None,
|
|
699
705
|
restrict_output: bool = False,
|
|
706
|
+
http_config: Optional[api_pb2.HTTPConfig] = None,
|
|
700
707
|
) -> "_Function":
|
|
701
708
|
"""mdmd:hidden
|
|
702
709
|
|
|
@@ -736,10 +743,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
736
743
|
if is_generator:
|
|
737
744
|
raise InvalidError("Generator functions do not support retries.")
|
|
738
745
|
|
|
746
|
+
if timeout is None: # type: ignore[unreachable] # Help users who aren't using type checkers
|
|
747
|
+
raise InvalidError("The `timeout` parameter cannot be set to None: https://modal.com/docs/guide/timeouts")
|
|
748
|
+
|
|
739
749
|
secrets = secrets or []
|
|
740
750
|
if env:
|
|
741
751
|
secrets = [*secrets, _Secret.from_dict(env)]
|
|
742
752
|
|
|
753
|
+
scheduler_placement: Optional[api_pb2.SchedulerPlacement] = None
|
|
754
|
+
if region or nonpreemptible:
|
|
755
|
+
regions = [region] if isinstance(region, str) else (list(region) if region else None)
|
|
756
|
+
scheduler_placement = api_pb2.SchedulerPlacement(regions=regions, nonpreemptible=nonpreemptible)
|
|
757
|
+
|
|
743
758
|
function_spec = _FunctionSpec(
|
|
744
759
|
mounts=all_mounts,
|
|
745
760
|
secrets=secrets,
|
|
@@ -772,6 +787,16 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
772
787
|
scaledown_window=scaledown_window,
|
|
773
788
|
)
|
|
774
789
|
|
|
790
|
+
# For clustered functions, container settings must be multiples of cluster_size
|
|
791
|
+
if cluster_size is not None and cluster_size > 1:
|
|
792
|
+
for field in ["min_containers", "max_containers", "buffer_containers"]:
|
|
793
|
+
value = getattr(autoscaler_settings, field)
|
|
794
|
+
if value and value % cluster_size != 0:
|
|
795
|
+
raise InvalidError(
|
|
796
|
+
f"`{field}` ({value}) must be a multiple of `cluster_size` ({cluster_size}) "
|
|
797
|
+
f"for clustered Functions"
|
|
798
|
+
)
|
|
799
|
+
|
|
775
800
|
if _experimental_custom_scaling_factor is not None and (
|
|
776
801
|
_experimental_custom_scaling_factor < 0 or _experimental_custom_scaling_factor > 1
|
|
777
802
|
):
|
|
@@ -797,14 +822,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
797
822
|
if arg.default is not inspect.Parameter.empty:
|
|
798
823
|
raise InvalidError(f"Modal batched function {func_name} does not accept default arguments.")
|
|
799
824
|
|
|
800
|
-
if max_inputs is not None:
|
|
801
|
-
if not isinstance(max_inputs, int):
|
|
802
|
-
raise InvalidError(f"`max_inputs` must be an int, not {type(max_inputs).__name__}")
|
|
803
|
-
if max_inputs <= 0:
|
|
804
|
-
raise InvalidError("`max_inputs` must be positive")
|
|
805
|
-
if max_inputs > 1:
|
|
806
|
-
raise InvalidError("Only `max_inputs=1` is currently supported")
|
|
807
|
-
|
|
808
825
|
# Validate volumes
|
|
809
826
|
validated_volumes = validate_volumes(volumes)
|
|
810
827
|
cloud_bucket_mounts = [(k, v) for k, v in validated_volumes if isinstance(v, _CloudBucketMount)]
|
|
@@ -833,15 +850,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
833
850
|
is_web_endpoint=is_web_endpoint,
|
|
834
851
|
ignore_first_argument=True,
|
|
835
852
|
)
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
else:
|
|
840
|
-
method_input_formats = [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]
|
|
841
|
-
if restrict_output:
|
|
842
|
-
method_output_formats = [api_pb2.DATA_FORMAT_CBOR]
|
|
843
|
-
else:
|
|
844
|
-
method_output_formats = [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]
|
|
853
|
+
method_input_formats, method_output_formats = _get_supported_input_output_formats(
|
|
854
|
+
is_web_endpoint, partial_function.params.is_generator or False, restrict_output
|
|
855
|
+
)
|
|
845
856
|
|
|
846
857
|
method_definition = api_pb2.MethodDefinition(
|
|
847
858
|
webhook_config=partial_function.params.webhook_config,
|
|
@@ -877,22 +888,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
877
888
|
# classes don't have data formats themselves - input/output formats are set per method above
|
|
878
889
|
supported_input_formats = []
|
|
879
890
|
supported_output_formats = []
|
|
880
|
-
elif webhook_config is not None:
|
|
881
|
-
supported_input_formats = [api_pb2.DATA_FORMAT_ASGI]
|
|
882
|
-
supported_output_formats = [api_pb2.DATA_FORMAT_ASGI]
|
|
883
891
|
else:
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
supported_output_formats = [api_pb2.DATA_FORMAT_PICKLE, api_pb2.DATA_FORMAT_CBOR]
|
|
889
|
-
|
|
890
|
-
async def _preload(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
891
|
-
assert resolver.client and resolver.client.stub
|
|
892
|
+
is_web_endpoint = webhook_config is not None and webhook_config.type != api_pb2.WEBHOOK_TYPE_UNSPECIFIED
|
|
893
|
+
supported_input_formats, supported_output_formats = _get_supported_input_output_formats(
|
|
894
|
+
is_web_endpoint, is_generator, restrict_output
|
|
895
|
+
)
|
|
892
896
|
|
|
893
|
-
|
|
897
|
+
async def _preload(
|
|
898
|
+
self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
899
|
+
):
|
|
900
|
+
assert load_context.app_id
|
|
894
901
|
req = api_pb2.FunctionPrecreateRequest(
|
|
895
|
-
app_id=
|
|
902
|
+
app_id=load_context.app_id,
|
|
896
903
|
function_name=info.function_name,
|
|
897
904
|
function_type=function_type,
|
|
898
905
|
existing_function_id=existing_object_id or "",
|
|
@@ -908,11 +915,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
908
915
|
elif webhook_config:
|
|
909
916
|
req.webhook_config.CopyFrom(webhook_config)
|
|
910
917
|
|
|
911
|
-
response = await
|
|
912
|
-
self._hydrate(response.function_id,
|
|
918
|
+
response = await load_context.client.stub.FunctionPrecreate(req)
|
|
919
|
+
self._hydrate(response.function_id, load_context.client, response.handle_metadata)
|
|
913
920
|
|
|
914
|
-
async def _load(
|
|
915
|
-
|
|
921
|
+
async def _load(
|
|
922
|
+
self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
923
|
+
):
|
|
916
924
|
with FunctionCreationStatus(resolver, tag) as function_creation_status:
|
|
917
925
|
timeout_secs = timeout
|
|
918
926
|
|
|
@@ -984,6 +992,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
984
992
|
function_definition = api_pb2.Function(
|
|
985
993
|
module_name=info.module_name or "",
|
|
986
994
|
function_name=info.function_name,
|
|
995
|
+
implementation_name=info.implementation_name,
|
|
987
996
|
mount_ids=loaded_mount_ids,
|
|
988
997
|
secret_ids=[secret.object_id for secret in secrets],
|
|
989
998
|
image_id=(image.object_id if image else ""),
|
|
@@ -1019,9 +1028,10 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1019
1028
|
object_dependencies=object_dependencies,
|
|
1020
1029
|
block_network=block_network,
|
|
1021
1030
|
untrusted=restrict_modal_access,
|
|
1022
|
-
|
|
1031
|
+
single_use_containers=single_use_containers,
|
|
1032
|
+
max_inputs=int(single_use_containers), # TODO(michael) remove after worker rollover
|
|
1023
1033
|
cloud_bucket_mounts=cloud_bucket_mounts_to_proto(cloud_bucket_mounts),
|
|
1024
|
-
scheduler_placement=scheduler_placement
|
|
1034
|
+
scheduler_placement=scheduler_placement,
|
|
1025
1035
|
is_class=info.is_service_class(),
|
|
1026
1036
|
class_parameter_info=info.class_parameter_info(),
|
|
1027
1037
|
i6pn_enabled=i6pn_enabled,
|
|
@@ -1043,12 +1053,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1043
1053
|
function_schema=function_schema,
|
|
1044
1054
|
supported_input_formats=supported_input_formats,
|
|
1045
1055
|
supported_output_formats=supported_output_formats,
|
|
1056
|
+
http_config=http_config,
|
|
1046
1057
|
)
|
|
1047
1058
|
|
|
1048
1059
|
if isinstance(gpu, list):
|
|
1049
1060
|
function_data = api_pb2.FunctionData(
|
|
1050
1061
|
module_name=function_definition.module_name,
|
|
1051
1062
|
function_name=function_definition.function_name,
|
|
1063
|
+
implementation_name=function_definition.implementation_name,
|
|
1052
1064
|
function_type=function_definition.function_type,
|
|
1053
1065
|
warm_pool_size=function_definition.warm_pool_size,
|
|
1054
1066
|
concurrency_limit=function_definition.concurrency_limit,
|
|
@@ -1080,6 +1092,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1080
1092
|
untrusted=function_definition.untrusted,
|
|
1081
1093
|
supported_input_formats=supported_input_formats,
|
|
1082
1094
|
supported_output_formats=supported_output_formats,
|
|
1095
|
+
http_config=http_config,
|
|
1083
1096
|
)
|
|
1084
1097
|
|
|
1085
1098
|
ranked_functions = []
|
|
@@ -1108,24 +1121,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1108
1121
|
),
|
|
1109
1122
|
)
|
|
1110
1123
|
|
|
1111
|
-
assert
|
|
1124
|
+
assert load_context.app_id
|
|
1112
1125
|
assert (function_definition is None) != (function_data is None) # xor
|
|
1113
1126
|
request = api_pb2.FunctionCreateRequest(
|
|
1114
|
-
app_id=
|
|
1127
|
+
app_id=load_context.app_id,
|
|
1115
1128
|
function=function_definition,
|
|
1116
1129
|
function_data=function_data,
|
|
1117
1130
|
existing_function_id=existing_object_id or "",
|
|
1118
1131
|
)
|
|
1119
1132
|
try:
|
|
1120
|
-
response: api_pb2.FunctionCreateResponse = await
|
|
1121
|
-
|
|
1122
|
-
)
|
|
1123
|
-
except GRPCError as exc:
|
|
1124
|
-
if exc.status == Status.INVALID_ARGUMENT:
|
|
1125
|
-
raise InvalidError(exc.message)
|
|
1126
|
-
if exc.status == Status.FAILED_PRECONDITION:
|
|
1127
|
-
raise InvalidError(exc.message)
|
|
1128
|
-
if exc.message and "Received :status = '413'" in exc.message:
|
|
1133
|
+
response: api_pb2.FunctionCreateResponse = await load_context.client.stub.FunctionCreate(request)
|
|
1134
|
+
except Exception as exc:
|
|
1135
|
+
if "Received :status = '413'" in str(exc):
|
|
1129
1136
|
raise InvalidError(f"Function {info.function_name} is too large to deploy.")
|
|
1130
1137
|
raise
|
|
1131
1138
|
function_creation_status.set_response(response)
|
|
@@ -1134,10 +1141,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1134
1141
|
serve_mounts = {m for m in all_mounts if m.is_local()}
|
|
1135
1142
|
serve_mounts |= image._serve_mounts
|
|
1136
1143
|
obj._serve_mounts = frozenset(serve_mounts)
|
|
1137
|
-
self._hydrate(response.function_id,
|
|
1144
|
+
self._hydrate(response.function_id, load_context.client, response.handle_metadata)
|
|
1138
1145
|
|
|
1139
1146
|
rep = f"Function({tag})"
|
|
1140
|
-
|
|
1147
|
+
# Pass a *reference* to the App's LoadContext - this is important since the App is
|
|
1148
|
+
# the only way to infer a LoadContext for an `@app.function`, and the App doesn't
|
|
1149
|
+
# get its client until *after* the Function is created.
|
|
1150
|
+
load_context = app._root_load_context if app else LoadContext.empty()
|
|
1151
|
+
obj = _Function._from_loader(_load, rep, preload=_preload, deps=_deps, load_context_overrides=load_context)
|
|
1141
1152
|
|
|
1142
1153
|
obj._raw_f = info.raw_f
|
|
1143
1154
|
obj._info = info
|
|
@@ -1179,7 +1190,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1179
1190
|
|
|
1180
1191
|
parent = self
|
|
1181
1192
|
|
|
1182
|
-
async def _load(
|
|
1193
|
+
async def _load(
|
|
1194
|
+
param_bound_func: _Function,
|
|
1195
|
+
resolver: Resolver,
|
|
1196
|
+
load_context: LoadContext,
|
|
1197
|
+
existing_object_id: Optional[str],
|
|
1198
|
+
):
|
|
1183
1199
|
if not parent.is_hydrated:
|
|
1184
1200
|
# While the base Object.hydrate() method appears to be idempotent, it's not always safe
|
|
1185
1201
|
await parent.hydrate()
|
|
@@ -1212,7 +1228,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1212
1228
|
param_bound_func._hydrate_from_other(parent)
|
|
1213
1229
|
return
|
|
1214
1230
|
|
|
1215
|
-
environment_name = _get_environment_name(None, resolver)
|
|
1216
1231
|
assert parent is not None and parent.is_hydrated
|
|
1217
1232
|
|
|
1218
1233
|
if options:
|
|
@@ -1252,11 +1267,11 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1252
1267
|
function_id=parent.object_id,
|
|
1253
1268
|
serialized_params=serialized_params,
|
|
1254
1269
|
function_options=options_pb,
|
|
1255
|
-
environment_name=environment_name
|
|
1270
|
+
environment_name=load_context.environment_name
|
|
1256
1271
|
or "", # TODO: investigate shouldn't environment name always be specified here?
|
|
1257
1272
|
)
|
|
1258
1273
|
|
|
1259
|
-
response = await
|
|
1274
|
+
response = await parent._client.stub.FunctionBindParams(req)
|
|
1260
1275
|
param_bound_func._hydrate(response.bound_function_id, parent._client, response.handle_metadata)
|
|
1261
1276
|
|
|
1262
1277
|
def _deps():
|
|
@@ -1269,7 +1284,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1269
1284
|
return [dep for dep in all_deps if not dep.is_hydrated]
|
|
1270
1285
|
return []
|
|
1271
1286
|
|
|
1272
|
-
fun: _Function = _Function._from_loader(
|
|
1287
|
+
fun: _Function = _Function._from_loader(
|
|
1288
|
+
_load,
|
|
1289
|
+
"Function(parametrized)",
|
|
1290
|
+
hydrate_lazily=True,
|
|
1291
|
+
deps=_deps,
|
|
1292
|
+
load_context_overrides=self._load_context_overrides,
|
|
1293
|
+
)
|
|
1273
1294
|
|
|
1274
1295
|
fun._info = self._info
|
|
1275
1296
|
fun._obj = obj
|
|
@@ -1320,7 +1341,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1320
1341
|
scaledown_window=scaledown_window,
|
|
1321
1342
|
)
|
|
1322
1343
|
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1323
|
-
await
|
|
1344
|
+
await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1324
1345
|
|
|
1325
1346
|
# One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
|
|
1326
1347
|
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
@@ -1367,34 +1388,43 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1367
1388
|
cls,
|
|
1368
1389
|
app_name: str,
|
|
1369
1390
|
name: str,
|
|
1370
|
-
|
|
1371
|
-
|
|
1391
|
+
*,
|
|
1392
|
+
load_context_overrides: LoadContext,
|
|
1372
1393
|
):
|
|
1373
1394
|
# internal function lookup implementation that allows lookup of class "service functions"
|
|
1374
1395
|
# in addition to non-class functions
|
|
1375
|
-
async def _load_remote(
|
|
1376
|
-
|
|
1396
|
+
async def _load_remote(
|
|
1397
|
+
self: _Function, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
1398
|
+
):
|
|
1377
1399
|
request = api_pb2.FunctionGetRequest(
|
|
1378
1400
|
app_name=app_name,
|
|
1379
1401
|
object_tag=name,
|
|
1380
|
-
environment_name=
|
|
1402
|
+
environment_name=load_context.environment_name,
|
|
1381
1403
|
)
|
|
1382
1404
|
try:
|
|
1383
|
-
response = await
|
|
1405
|
+
response = await load_context.client.stub.FunctionGet(request)
|
|
1384
1406
|
except NotFoundError as exc:
|
|
1385
1407
|
# refine the error message
|
|
1386
|
-
env_context =
|
|
1408
|
+
env_context = (
|
|
1409
|
+
f" (in the '{load_context.environment_name}' environment)" if load_context.environment_name else ""
|
|
1410
|
+
)
|
|
1387
1411
|
raise NotFoundError(
|
|
1388
1412
|
f"Lookup failed for Function '{name}' from the '{app_name}' app{env_context}: {exc}."
|
|
1389
1413
|
) from None
|
|
1390
1414
|
|
|
1391
1415
|
print_server_warnings(response.server_warnings)
|
|
1392
1416
|
|
|
1393
|
-
self._hydrate(response.function_id,
|
|
1417
|
+
self._hydrate(response.function_id, load_context.client, response.handle_metadata)
|
|
1394
1418
|
|
|
1395
|
-
environment_rep =
|
|
1419
|
+
environment_rep = (
|
|
1420
|
+
f", environment_name={load_context_overrides.environment_name!r}"
|
|
1421
|
+
if load_context_overrides._environment_name # slightly ugly - checking if _environment_name is overridden
|
|
1422
|
+
else ""
|
|
1423
|
+
)
|
|
1396
1424
|
rep = f"modal.Function.from_name('{app_name}', '{name}'{environment_rep})"
|
|
1397
|
-
return cls._from_loader(
|
|
1425
|
+
return cls._from_loader(
|
|
1426
|
+
_load_remote, rep, is_another_app=True, hydrate_lazily=True, load_context_overrides=load_context_overrides
|
|
1427
|
+
)
|
|
1398
1428
|
|
|
1399
1429
|
@classmethod
|
|
1400
1430
|
def from_name(
|
|
@@ -1404,6 +1434,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1404
1434
|
*,
|
|
1405
1435
|
namespace=None, # mdmd:line-hidden
|
|
1406
1436
|
environment_name: Optional[str] = None,
|
|
1437
|
+
client: Optional[_Client] = None,
|
|
1407
1438
|
) -> "_Function":
|
|
1408
1439
|
"""Reference a Function from a deployed App by its name.
|
|
1409
1440
|
|
|
@@ -1427,7 +1458,9 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1427
1458
|
)
|
|
1428
1459
|
|
|
1429
1460
|
warn_if_passing_namespace(namespace, "modal.Function.from_name")
|
|
1430
|
-
return cls._from_name(
|
|
1461
|
+
return cls._from_name(
|
|
1462
|
+
app_name, name, load_context_overrides=LoadContext(environment_name=environment_name, client=client)
|
|
1463
|
+
)
|
|
1431
1464
|
|
|
1432
1465
|
@property
|
|
1433
1466
|
def tag(self) -> str:
|
|
@@ -1650,8 +1683,8 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1650
1683
|
input_queue,
|
|
1651
1684
|
self.client,
|
|
1652
1685
|
)
|
|
1653
|
-
|
|
1654
|
-
fc
|
|
1686
|
+
fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, None)
|
|
1687
|
+
fc._num_inputs = num_inputs # set the cached value of num_inputs
|
|
1655
1688
|
return fc
|
|
1656
1689
|
|
|
1657
1690
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
|
@@ -1880,10 +1913,9 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1880
1913
|
@live_method
|
|
1881
1914
|
async def get_current_stats(self) -> FunctionStats:
|
|
1882
1915
|
"""Return a `FunctionStats` object describing the current function's queue and runner counts."""
|
|
1883
|
-
resp = await
|
|
1884
|
-
self.client.stub.FunctionGetCurrentStats,
|
|
1916
|
+
resp = await self.client.stub.FunctionGetCurrentStats(
|
|
1885
1917
|
api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
|
|
1886
|
-
total_timeout=10.0,
|
|
1918
|
+
retry=Retry(total_timeout=10.0),
|
|
1887
1919
|
)
|
|
1888
1920
|
return FunctionStats(backlog=resp.backlog, num_total_runners=resp.num_total_tasks)
|
|
1889
1921
|
|
|
@@ -1921,19 +1953,16 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1921
1953
|
def _invocation(self):
|
|
1922
1954
|
return _Invocation(self.client.stub, self.object_id, self.client)
|
|
1923
1955
|
|
|
1924
|
-
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
1925
|
-
if not metadata:
|
|
1926
|
-
return
|
|
1927
|
-
assert isinstance(metadata, api_pb2.FunctionCallFromIdResponse)
|
|
1928
|
-
self._num_inputs = metadata.num_inputs
|
|
1929
|
-
|
|
1930
1956
|
@live_method
|
|
1931
1957
|
async def num_inputs(self) -> int:
|
|
1932
1958
|
"""Get the number of inputs in the function call."""
|
|
1933
|
-
|
|
1934
|
-
|
|
1959
|
+
if self._num_inputs is None:
|
|
1960
|
+
request = api_pb2.FunctionCallFromIdRequest(function_call_id=self.object_id)
|
|
1961
|
+
resp = await self.client.stub.FunctionCallFromId(request)
|
|
1962
|
+
self._num_inputs = resp.num_inputs # cached
|
|
1935
1963
|
return self._num_inputs
|
|
1936
1964
|
|
|
1965
|
+
@live_method
|
|
1937
1966
|
async def get(self, timeout: Optional[float] = None, *, index: int = 0) -> ReturnType:
|
|
1938
1967
|
"""Get the result of the index-th input of the function call.
|
|
1939
1968
|
`.spawn()` calls have a single output, so only specifying `index=0` is valid.
|
|
@@ -1977,6 +2006,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1977
2006
|
async for _, item in self._invocation().enumerate(start_index=start, end_index=end):
|
|
1978
2007
|
yield item
|
|
1979
2008
|
|
|
2009
|
+
@live_method
|
|
1980
2010
|
async def get_call_graph(self) -> list[InputInfo]:
|
|
1981
2011
|
"""Returns a structure representing the call graph from a given root
|
|
1982
2012
|
call ID, along with the status of execution for each node.
|
|
@@ -1986,9 +2016,10 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1986
2016
|
"""
|
|
1987
2017
|
assert self._client and self._client.stub
|
|
1988
2018
|
request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
|
|
1989
|
-
response = await
|
|
2019
|
+
response = await self._client.stub.FunctionGetCallGraph(request)
|
|
1990
2020
|
return _reconstruct_call_graph(response)
|
|
1991
2021
|
|
|
2022
|
+
@live_method
|
|
1992
2023
|
async def cancel(
|
|
1993
2024
|
self,
|
|
1994
2025
|
# if true, containers running the inputs are forcibly terminated
|
|
@@ -2004,10 +2035,13 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2004
2035
|
function_call_id=self.object_id, terminate_containers=terminate_containers
|
|
2005
2036
|
)
|
|
2006
2037
|
assert self._client and self._client.stub
|
|
2007
|
-
await
|
|
2038
|
+
await self._client.stub.FunctionCallCancel(request)
|
|
2008
2039
|
|
|
2009
|
-
@
|
|
2010
|
-
|
|
2040
|
+
@deprecate_aio_usage((2025, 11, 14), "FunctionCall.from_id")
|
|
2041
|
+
@classmethod
|
|
2042
|
+
def from_id(
|
|
2043
|
+
cls, function_call_id: str, client: Optional["modal.client.Client"] = None
|
|
2044
|
+
) -> "modal.functions.FunctionCall[Any]":
|
|
2011
2045
|
"""Instantiate a FunctionCall object from an existing ID.
|
|
2012
2046
|
|
|
2013
2047
|
Examples:
|
|
@@ -2018,7 +2052,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2018
2052
|
fc_id = fc.object_id
|
|
2019
2053
|
|
|
2020
2054
|
# Later, use the ID to re-instantiate the FunctionCall object
|
|
2021
|
-
fc =
|
|
2055
|
+
fc = FunctionCall.from_id(fc_id)
|
|
2022
2056
|
result = fc.get()
|
|
2023
2057
|
```
|
|
2024
2058
|
|
|
@@ -2026,20 +2060,19 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2026
2060
|
if you no longer have access to the original object returned from `Function.spawn`.
|
|
2027
2061
|
|
|
2028
2062
|
"""
|
|
2029
|
-
|
|
2030
|
-
client = await _Client.from_env()
|
|
2063
|
+
_client = typing.cast(_Client, synchronizer._translate_in(client))
|
|
2031
2064
|
|
|
2032
|
-
async def _load(
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2065
|
+
async def _load(
|
|
2066
|
+
self: _FunctionCall, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
2067
|
+
):
|
|
2068
|
+
# this loader doesn't do anything in practice, but it will get the client from the load_context
|
|
2069
|
+
self._hydrate(function_call_id, load_context.client, None)
|
|
2036
2070
|
|
|
2037
2071
|
rep = f"FunctionCall.from_id({function_call_id!r})"
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
return fc
|
|
2072
|
+
impl_instance = _FunctionCall._from_loader(
|
|
2073
|
+
_load, rep, hydrate_lazily=True, load_context_overrides=LoadContext(client=_client)
|
|
2074
|
+
)
|
|
2075
|
+
return typing.cast("modal.functions.FunctionCall[Any]", synchronizer._translate_out(impl_instance))
|
|
2043
2076
|
|
|
2044
2077
|
@staticmethod
|
|
2045
2078
|
async def gather(*function_calls: "_FunctionCall[T]") -> typing.Sequence[T]:
|