modal 0.67.1__py3-none-any.whl → 0.67.22__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.
Files changed (113) hide show
  1. modal/_clustered_functions.py +2 -2
  2. modal/_clustered_functions.pyi +2 -2
  3. modal/_container_entrypoint.py +5 -4
  4. modal/_output.py +29 -28
  5. modal/_pty.py +2 -2
  6. modal/_resolver.py +6 -5
  7. modal/_resources.py +3 -3
  8. modal/_runtime/asgi.py +46 -6
  9. modal/_runtime/container_io_manager.py +22 -26
  10. modal/_runtime/execution_context.py +2 -2
  11. modal/_runtime/telemetry.py +1 -2
  12. modal/_runtime/user_code_imports.py +12 -14
  13. modal/_serialization.py +3 -7
  14. modal/_traceback.py +5 -5
  15. modal/_tunnel.py +5 -4
  16. modal/_tunnel.pyi +2 -2
  17. modal/_utils/async_utils.py +53 -17
  18. modal/_utils/blob_utils.py +22 -7
  19. modal/_utils/function_utils.py +14 -10
  20. modal/_utils/grpc_testing.py +7 -6
  21. modal/_utils/grpc_utils.py +2 -3
  22. modal/_utils/hash_utils.py +2 -2
  23. modal/_utils/mount_utils.py +5 -4
  24. modal/_utils/package_utils.py +2 -3
  25. modal/_utils/pattern_matcher.py +6 -6
  26. modal/_utils/rand_pb_testing.py +3 -3
  27. modal/_utils/shell_utils.py +2 -1
  28. modal/_vendor/a2wsgi_wsgi.py +62 -72
  29. modal/_vendor/cloudpickle.py +1 -1
  30. modal/_watcher.py +8 -7
  31. modal/app.py +68 -62
  32. modal/app.pyi +104 -99
  33. modal/call_graph.py +6 -6
  34. modal/cli/_download.py +3 -2
  35. modal/cli/_traceback.py +4 -4
  36. modal/cli/app.py +4 -4
  37. modal/cli/container.py +4 -4
  38. modal/cli/dict.py +1 -1
  39. modal/cli/environment.py +2 -3
  40. modal/cli/import_refs.py +1 -1
  41. modal/cli/launch.py +2 -2
  42. modal/cli/network_file_system.py +1 -1
  43. modal/cli/profile.py +1 -1
  44. modal/cli/programs/run_jupyter.py +2 -2
  45. modal/cli/programs/vscode.py +3 -3
  46. modal/cli/queues.py +1 -1
  47. modal/cli/run.py +6 -6
  48. modal/cli/secret.py +3 -3
  49. modal/cli/utils.py +2 -1
  50. modal/cli/volume.py +3 -3
  51. modal/client.py +6 -11
  52. modal/client.pyi +18 -27
  53. modal/cloud_bucket_mount.py +3 -3
  54. modal/cloud_bucket_mount.pyi +2 -2
  55. modal/cls.py +32 -32
  56. modal/cls.pyi +35 -34
  57. modal/config.py +3 -2
  58. modal/container_process.py +6 -2
  59. modal/dict.py +6 -3
  60. modal/dict.pyi +10 -9
  61. modal/environments.py +3 -3
  62. modal/environments.pyi +3 -3
  63. modal/exception.py +2 -3
  64. modal/functions.py +111 -40
  65. modal/functions.pyi +71 -48
  66. modal/image.py +46 -49
  67. modal/image.pyi +102 -101
  68. modal/io_streams.py +20 -12
  69. modal/io_streams.pyi +24 -14
  70. modal/mount.py +24 -24
  71. modal/mount.pyi +28 -29
  72. modal/network_file_system.py +14 -11
  73. modal/network_file_system.pyi +12 -11
  74. modal/object.py +9 -8
  75. modal/object.pyi +47 -34
  76. modal/output.py +2 -1
  77. modal/parallel_map.py +4 -4
  78. modal/partial_function.py +10 -14
  79. modal/partial_function.pyi +17 -18
  80. modal/queue.py +11 -8
  81. modal/queue.pyi +23 -22
  82. modal/retries.py +38 -0
  83. modal/runner.py +8 -7
  84. modal/runner.pyi +8 -14
  85. modal/running_app.py +3 -3
  86. modal/sandbox.py +20 -13
  87. modal/sandbox.pyi +73 -72
  88. modal/scheduler_placement.py +2 -1
  89. modal/secret.py +7 -7
  90. modal/secret.pyi +12 -12
  91. modal/serving.py +4 -3
  92. modal/serving.pyi +5 -4
  93. modal/token_flow.py +3 -2
  94. modal/token_flow.pyi +3 -3
  95. modal/volume.py +16 -23
  96. modal/volume.pyi +17 -16
  97. {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/METADATA +2 -2
  98. modal-0.67.22.dist-info/RECORD +168 -0
  99. modal_docs/mdmd/signatures.py +1 -2
  100. modal_global_objects/mounts/python_standalone.py +1 -1
  101. modal_proto/api.proto +13 -0
  102. modal_proto/api_grpc.py +16 -0
  103. modal_proto/api_pb2.py +241 -221
  104. modal_proto/api_pb2.pyi +41 -0
  105. modal_proto/api_pb2_grpc.py +33 -0
  106. modal_proto/api_pb2_grpc.pyi +10 -0
  107. modal_proto/modal_api_grpc.py +1 -0
  108. modal_version/_version_generated.py +1 -1
  109. modal-0.67.1.dist-info/RECORD +0 -168
  110. {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/LICENSE +0 -0
  111. {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/WHEEL +0 -0
  112. {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/entry_points.txt +0 -0
  113. {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/top_level.txt +0 -0
modal/functions.py CHANGED
@@ -1,24 +1,18 @@
1
1
  # Copyright Modal Labs 2023
2
+ import dataclasses
2
3
  import inspect
3
4
  import textwrap
4
5
  import time
5
6
  import typing
6
7
  import warnings
8
+ from collections.abc import AsyncGenerator, Collection, Sequence, Sized
7
9
  from dataclasses import dataclass
8
10
  from pathlib import PurePosixPath
9
11
  from typing import (
10
12
  TYPE_CHECKING,
11
13
  Any,
12
- AsyncGenerator,
13
14
  Callable,
14
- Collection,
15
- Dict,
16
- List,
17
15
  Optional,
18
- Sequence,
19
- Sized,
20
- Tuple,
21
- Type,
22
16
  Union,
23
17
  )
24
18
 
@@ -26,6 +20,7 @@ import typing_extensions
26
20
  from google.protobuf.message import Message
27
21
  from grpclib import GRPCError, Status
28
22
  from synchronicity.combined_types import MethodWithAio
23
+ from synchronicity.exceptions import UserCodeException
29
24
 
30
25
  from modal._utils.async_utils import aclosing
31
26
  from modal_proto import api_pb2
@@ -64,6 +59,7 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
64
59
  from .config import config
65
60
  from .exception import (
66
61
  ExecutionError,
62
+ FunctionTimeoutError,
67
63
  InvalidError,
68
64
  NotFoundError,
69
65
  OutputExpiredError,
@@ -86,7 +82,7 @@ from .parallel_map import (
86
82
  _SynchronizedQueue,
87
83
  )
88
84
  from .proxy import _Proxy
89
- from .retries import Retries
85
+ from .retries import Retries, RetryManager
90
86
  from .schedule import Schedule
91
87
  from .scheduler_placement import SchedulerPlacement
92
88
  from .secret import _Secret
@@ -98,15 +94,32 @@ if TYPE_CHECKING:
98
94
  import modal.partial_function
99
95
 
100
96
 
97
+ @dataclasses.dataclass
98
+ class _RetryContext:
99
+ function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType"
100
+ retry_policy: api_pb2.FunctionRetryPolicy
101
+ function_call_jwt: str
102
+ input_jwt: str
103
+ input_id: str
104
+ item: api_pb2.FunctionPutInputsItem
105
+
106
+
101
107
  class _Invocation:
102
108
  """Internal client representation of a single-input call to a Modal Function or Generator"""
103
109
 
104
110
  stub: ModalClientModal
105
111
 
106
- def __init__(self, stub: ModalClientModal, function_call_id: str, client: _Client):
112
+ def __init__(
113
+ self,
114
+ stub: ModalClientModal,
115
+ function_call_id: str,
116
+ client: _Client,
117
+ retry_context: Optional[_RetryContext] = None,
118
+ ):
107
119
  self.stub = stub
108
120
  self.client = client # Used by the deserializer.
109
121
  self.function_call_id = function_call_id # TODO: remove and use only input_id
122
+ self._retry_context = retry_context
110
123
 
111
124
  @staticmethod
112
125
  async def create(
@@ -132,7 +145,17 @@ class _Invocation:
132
145
  function_call_id = response.function_call_id
133
146
 
134
147
  if response.pipelined_inputs:
135
- return _Invocation(client.stub, function_call_id, client)
148
+ assert len(response.pipelined_inputs) == 1
149
+ input = response.pipelined_inputs[0]
150
+ retry_context = _RetryContext(
151
+ function_call_invocation_type=function_call_invocation_type,
152
+ retry_policy=response.retry_policy,
153
+ function_call_jwt=response.function_call_jwt,
154
+ input_jwt=input.input_jwt,
155
+ input_id=input.input_id,
156
+ item=item,
157
+ )
158
+ return _Invocation(client.stub, function_call_id, client, retry_context)
136
159
 
137
160
  request_put = api_pb2.FunctionPutInputsRequest(
138
161
  function_id=function_id, inputs=[item], function_call_id=function_call_id
@@ -144,7 +167,16 @@ class _Invocation:
144
167
  processed_inputs = inputs_response.inputs
145
168
  if not processed_inputs:
146
169
  raise Exception("Could not create function call - the input queue seems to be full")
147
- return _Invocation(client.stub, function_call_id, client)
170
+ input = inputs_response.inputs[0]
171
+ retry_context = _RetryContext(
172
+ function_call_invocation_type=function_call_invocation_type,
173
+ retry_policy=response.retry_policy,
174
+ function_call_jwt=response.function_call_jwt,
175
+ input_jwt=input.input_jwt,
176
+ input_id=input.input_id,
177
+ item=item,
178
+ )
179
+ return _Invocation(client.stub, function_call_id, client, retry_context)
148
180
 
149
181
  async def pop_function_call_outputs(
150
182
  self, timeout: Optional[float], clear_on_success: bool
@@ -180,13 +212,46 @@ class _Invocation:
180
212
  # return the last response to check for state of num_unfinished_inputs
181
213
  return response
182
214
 
183
- async def run_function(self) -> Any:
215
+ async def _retry_input(self) -> None:
216
+ ctx = self._retry_context
217
+ if not ctx:
218
+ raise ValueError("Cannot retry input when _retry_context is empty.")
219
+
220
+ item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
221
+ request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
222
+ await retry_transient_errors(
223
+ self.client.stub.FunctionRetryInputs,
224
+ request,
225
+ )
226
+
227
+ async def _get_single_output(self) -> Any:
184
228
  # waits indefinitely for a single result for the function, and clear the outputs buffer after
185
229
  item: api_pb2.FunctionGetOutputsItem = (
186
230
  await self.pop_function_call_outputs(timeout=None, clear_on_success=True)
187
231
  ).outputs[0]
188
232
  return await _process_result(item.result, item.data_format, self.stub, self.client)
189
233
 
234
+ async def run_function(self) -> Any:
235
+ # Use retry logic only if retry policy is specified and
236
+ ctx = self._retry_context
237
+ if (
238
+ not ctx
239
+ or not ctx.retry_policy
240
+ or ctx.retry_policy.retries == 0
241
+ or ctx.function_call_invocation_type != api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
242
+ ):
243
+ return await self._get_single_output()
244
+
245
+ # User errors including timeouts are managed by the user specified retry policy.
246
+ user_retry_manager = RetryManager(ctx.retry_policy)
247
+
248
+ while True:
249
+ try:
250
+ return await self._get_single_output()
251
+ except (UserCodeException, FunctionTimeoutError) as exc:
252
+ await user_retry_manager.raise_or_sleep(exc)
253
+ await self._retry_input()
254
+
190
255
  async def poll_function(self, timeout: Optional[float] = None):
191
256
  """Waits up to timeout for a result from a function.
192
257
 
@@ -278,12 +343,12 @@ class _FunctionSpec:
278
343
  image: Optional[_Image]
279
344
  mounts: Sequence[_Mount]
280
345
  secrets: Sequence[_Secret]
281
- network_file_systems: Dict[Union[str, PurePosixPath], _NetworkFileSystem]
282
- volumes: Dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
283
- gpus: Union[GPU_T, List[GPU_T]] # TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
346
+ network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem]
347
+ volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
348
+ gpus: Union[GPU_T, list[GPU_T]] # TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
284
349
  cloud: Optional[str]
285
350
  cpu: Optional[float]
286
- memory: Optional[Union[int, Tuple[int, int]]]
351
+ memory: Optional[Union[int, tuple[int, int]]]
287
352
  ephemeral_disk: Optional[int]
288
353
  scheduler_placement: Optional[SchedulerPlacement]
289
354
 
@@ -304,7 +369,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
304
369
 
305
370
  # TODO: more type annotations
306
371
  _info: Optional[FunctionInfo]
307
- _serve_mounts: typing.FrozenSet[_Mount] # set at load time, only by loader
372
+ _serve_mounts: frozenset[_Mount] # set at load time, only by loader
308
373
  _app: Optional["modal.app._App"] = None
309
374
  _obj: Optional["modal.cls._Obj"] = None # only set for InstanceServiceFunctions and bound instance methods
310
375
  _web_url: Optional[str]
@@ -323,7 +388,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
323
388
  _use_method_name: str = ""
324
389
 
325
390
  _class_parameter_info: Optional["api_pb2.ClassParameterInfo"] = None
326
- _method_handle_metadata: Optional[Dict[str, "api_pb2.FunctionHandleMetadata"]] = None
391
+ _method_handle_metadata: Optional[dict[str, "api_pb2.FunctionHandleMetadata"]] = None
327
392
 
328
393
  def _bind_method(
329
394
  self,
@@ -429,14 +494,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
429
494
  secrets: Sequence[_Secret] = (),
430
495
  schedule: Optional[Schedule] = None,
431
496
  is_generator=False,
432
- gpu: Union[GPU_T, List[GPU_T]] = None,
497
+ gpu: Union[GPU_T, list[GPU_T]] = None,
433
498
  # TODO: maybe break this out into a separate decorator for notebooks.
434
499
  mounts: Collection[_Mount] = (),
435
- network_file_systems: Dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
500
+ network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
436
501
  allow_cross_region_volumes: bool = False,
437
- volumes: Dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
502
+ volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
438
503
  webhook_config: Optional[api_pb2.WebhookConfig] = None,
439
- memory: Optional[Union[int, Tuple[int, int]]] = None,
504
+ memory: Optional[Union[int, tuple[int, int]]] = None,
440
505
  proxy: Optional[_Proxy] = None,
441
506
  retries: Optional[Union[int, Retries]] = None,
442
507
  timeout: Optional[int] = None,
@@ -623,8 +688,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
623
688
  if image is not None and not isinstance(image, _Image):
624
689
  raise InvalidError(f"Expected modal.Image object. Got {type(image)}.")
625
690
 
626
- method_definitions: Optional[Dict[str, api_pb2.MethodDefinition]] = None
627
- partial_functions: Dict[str, "modal.partial_function._PartialFunction"] = {}
691
+ method_definitions: Optional[dict[str, api_pb2.MethodDefinition]] = None
692
+ partial_functions: dict[str, "modal.partial_function._PartialFunction"] = {}
628
693
  if info.user_cls:
629
694
  method_definitions = {}
630
695
  partial_functions = _find_partial_methods_for_user_cls(info.user_cls, _PartialFunctionFlags.FUNCTION)
@@ -640,8 +705,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
640
705
 
641
706
  function_type = get_function_type(is_generator)
642
707
 
643
- def _deps(only_explicit_mounts=False) -> List[_Object]:
644
- deps: List[_Object] = list(secrets)
708
+ def _deps(only_explicit_mounts=False) -> list[_Object]:
709
+ deps: list[_Object] = list(secrets)
645
710
  if only_explicit_mounts:
646
711
  # TODO: this is a bit hacky, but all_mounts may differ in the container vs locally
647
712
  # We don't want the function dependencies to change, so we have this way to force it to
@@ -821,6 +886,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
821
886
  is_method=function_definition.is_method,
822
887
  use_function_id=function_definition.use_function_id,
823
888
  use_method_name=function_definition.use_method_name,
889
+ method_definitions=function_definition.method_definitions,
890
+ method_definitions_set=function_definition.method_definitions_set,
824
891
  _experimental_group_size=function_definition._experimental_group_size,
825
892
  _experimental_buffer_containers=function_definition._experimental_buffer_containers,
826
893
  _experimental_custom_scaling=function_definition._experimental_custom_scaling,
@@ -847,9 +914,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
847
914
  function_data.ranked_functions.extend(ranked_functions)
848
915
  function_definition = None # function_definition is not used in this case
849
916
  else:
850
- # TODO(irfansharif): Assert on this specific type once
851
- # we get rid of python 3.8.
852
- # assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
917
+ # TODO(irfansharif): Assert on this specific type once we get rid of python 3.9.
918
+ # assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
853
919
  function_definition.resources.CopyFrom(
854
920
  convert_fn_config_to_resources_config(
855
921
  cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk
@@ -878,7 +944,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
878
944
  raise InvalidError(f"Function {info.function_name} is too large to deploy.")
879
945
  raise
880
946
  function_creation_status.set_response(response)
881
- serve_mounts = set(m for m in all_mounts if m.is_local()) # needed for modal.serve file watching
947
+ serve_mounts = {m for m in all_mounts if m.is_local()} # needed for modal.serve file watching
882
948
  serve_mounts |= image._serve_mounts
883
949
  obj._serve_mounts = frozenset(serve_mounts)
884
950
  self._hydrate(response.function_id, resolver.client, response.handle_metadata)
@@ -897,7 +963,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
897
963
  obj._spec = function_spec # needed for modal shell
898
964
 
899
965
  # Used to check whether we should rebuild a modal.Image which uses `run_function`.
900
- gpus: List[GPU_T] = gpu if isinstance(gpu, list) else [gpu]
966
+ gpus: list[GPU_T] = gpu if isinstance(gpu, list) else [gpu]
901
967
  obj._build_args = dict( # See get_build_def
902
968
  secrets=repr(secrets),
903
969
  gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
@@ -919,7 +985,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
919
985
  from_other_workspace: bool,
920
986
  options: Optional[api_pb2.FunctionOptions],
921
987
  args: Sized,
922
- kwargs: Dict[str, Any],
988
+ kwargs: dict[str, Any],
923
989
  ) -> "_Function":
924
990
  """mdmd:hidden
925
991
 
@@ -997,7 +1063,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
997
1063
  Please exercise care when using this advanced feature!
998
1064
  Setting and forgetting a warm pool on functions can lead to increased costs.
999
1065
 
1000
- ```python
1066
+ ```python notest
1001
1067
  # Usage on a regular function.
1002
1068
  f = modal.Function.lookup("my-app", "function")
1003
1069
  f.keep_warm(2)
@@ -1025,7 +1091,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1025
1091
 
1026
1092
  @classmethod
1027
1093
  def from_name(
1028
- cls: Type["_Function"],
1094
+ cls: type["_Function"],
1029
1095
  app_name: str,
1030
1096
  tag: str,
1031
1097
  namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
@@ -1076,7 +1142,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1076
1142
  In contrast to `modal.Function.from_name`, this is an eager method
1077
1143
  that will hydrate the local object with metadata from Modal servers.
1078
1144
 
1079
- ```python
1145
+ ```python notest
1080
1146
  f = modal.Function.lookup("other-app", "function")
1081
1147
  ```
1082
1148
  """
@@ -1232,13 +1298,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1232
1298
  yield item
1233
1299
 
1234
1300
  async def _call_function(self, args, kwargs) -> ReturnType:
1301
+ if config.get("client_retries"):
1302
+ function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
1303
+ else:
1304
+ function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY
1235
1305
  invocation = await _Invocation.create(
1236
1306
  self,
1237
1307
  args,
1238
1308
  kwargs,
1239
1309
  client=self._client,
1240
- function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY,
1310
+ function_call_invocation_type=function_call_invocation_type,
1241
1311
  )
1312
+
1242
1313
  return await invocation.run_function()
1243
1314
 
1244
1315
  async def _call_function_nowait(
@@ -1355,12 +1426,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1355
1426
  if is_async(info.raw_f):
1356
1427
  # We want to run __aenter__ and fun in the same coroutine
1357
1428
  async def coro():
1358
- await obj.aenter()
1429
+ await obj._aenter()
1359
1430
  return await fun(*args, **kwargs)
1360
1431
 
1361
1432
  return coro() # type: ignore
1362
1433
  else:
1363
- obj.enter()
1434
+ obj._enter()
1364
1435
  return fun(*args, **kwargs)
1365
1436
 
1366
1437
  @synchronizer.no_input_translation
@@ -1476,7 +1547,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
1476
1547
  async for res in self._invocation().run_generator():
1477
1548
  yield res
1478
1549
 
1479
- async def get_call_graph(self) -> List[InputInfo]:
1550
+ async def get_call_graph(self) -> list[InputInfo]:
1480
1551
  """Returns a structure representing the call graph from a given root
1481
1552
  call ID, along with the status of execution for each node.
1482
1553
 
modal/functions.pyi CHANGED
@@ -1,3 +1,4 @@
1
+ import collections.abc
1
2
  import google.protobuf.message
2
3
  import modal._utils.async_utils
3
4
  import modal._utils.function_utils
@@ -25,11 +26,35 @@ import pathlib
25
26
  import typing
26
27
  import typing_extensions
27
28
 
29
+ class _RetryContext:
30
+ function_call_invocation_type: int
31
+ retry_policy: modal_proto.api_pb2.FunctionRetryPolicy
32
+ function_call_jwt: str
33
+ input_jwt: str
34
+ input_id: str
35
+ item: modal_proto.api_pb2.FunctionPutInputsItem
36
+
37
+ def __init__(
38
+ self,
39
+ function_call_invocation_type: int,
40
+ retry_policy: modal_proto.api_pb2.FunctionRetryPolicy,
41
+ function_call_jwt: str,
42
+ input_jwt: str,
43
+ input_id: str,
44
+ item: modal_proto.api_pb2.FunctionPutInputsItem,
45
+ ) -> None: ...
46
+ def __repr__(self): ...
47
+ def __eq__(self, other): ...
48
+
28
49
  class _Invocation:
29
50
  stub: modal_proto.modal_api_grpc.ModalClientModal
30
51
 
31
52
  def __init__(
32
- self, stub: modal_proto.modal_api_grpc.ModalClientModal, function_call_id: str, client: modal.client._Client
53
+ self,
54
+ stub: modal_proto.modal_api_grpc.ModalClientModal,
55
+ function_call_id: str,
56
+ client: modal.client._Client,
57
+ retry_context: typing.Optional[_RetryContext] = None,
33
58
  ): ...
34
59
  @staticmethod
35
60
  async def create(
@@ -38,6 +63,8 @@ class _Invocation:
38
63
  async def pop_function_call_outputs(
39
64
  self, timeout: typing.Optional[float], clear_on_success: bool
40
65
  ) -> modal_proto.api_pb2.FunctionGetOutputsResponse: ...
66
+ async def _retry_input(self) -> None: ...
67
+ async def _get_single_output(self) -> typing.Any: ...
41
68
  async def run_function(self) -> typing.Any: ...
42
69
  async def poll_function(self, timeout: typing.Optional[float] = None): ...
43
70
  def run_generator(self): ...
@@ -60,42 +87,38 @@ def _parse_retries(
60
87
 
61
88
  class _FunctionSpec:
62
89
  image: typing.Optional[modal.image._Image]
63
- mounts: typing.Sequence[modal.mount._Mount]
64
- secrets: typing.Sequence[modal.secret._Secret]
65
- network_file_systems: typing.Dict[
66
- typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
67
- ]
68
- volumes: typing.Dict[
90
+ mounts: collections.abc.Sequence[modal.mount._Mount]
91
+ secrets: collections.abc.Sequence[modal.secret._Secret]
92
+ network_file_systems: dict[typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem]
93
+ volumes: dict[
69
94
  typing.Union[str, pathlib.PurePosixPath],
70
95
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
71
96
  ]
72
- gpus: typing.Union[
73
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
74
- ]
97
+ gpus: typing.Union[None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]]
75
98
  cloud: typing.Optional[str]
76
99
  cpu: typing.Optional[float]
77
- memory: typing.Union[int, typing.Tuple[int, int], None]
100
+ memory: typing.Union[int, tuple[int, int], None]
78
101
  ephemeral_disk: typing.Optional[int]
79
102
  scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement]
80
103
 
81
104
  def __init__(
82
105
  self,
83
106
  image: typing.Optional[modal.image._Image],
84
- mounts: typing.Sequence[modal.mount._Mount],
85
- secrets: typing.Sequence[modal.secret._Secret],
86
- network_file_systems: typing.Dict[
107
+ mounts: collections.abc.Sequence[modal.mount._Mount],
108
+ secrets: collections.abc.Sequence[modal.secret._Secret],
109
+ network_file_systems: dict[
87
110
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
88
111
  ],
89
- volumes: typing.Dict[
112
+ volumes: dict[
90
113
  typing.Union[str, pathlib.PurePosixPath],
91
114
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
92
115
  ],
93
116
  gpus: typing.Union[
94
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
117
+ None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
95
118
  ],
96
119
  cloud: typing.Optional[str],
97
120
  cpu: typing.Optional[float],
98
- memory: typing.Union[int, typing.Tuple[int, int], None],
121
+ memory: typing.Union[int, tuple[int, int], None],
99
122
  ephemeral_disk: typing.Optional[int],
100
123
  scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement],
101
124
  ) -> None: ...
@@ -110,7 +133,7 @@ OriginalReturnType = typing.TypeVar("OriginalReturnType", covariant=True)
110
133
 
111
134
  class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object._Object):
112
135
  _info: typing.Optional[modal._utils.function_utils.FunctionInfo]
113
- _serve_mounts: typing.FrozenSet[modal.mount._Mount]
136
+ _serve_mounts: frozenset[modal.mount._Mount]
114
137
  _app: typing.Optional[modal.app._App]
115
138
  _obj: typing.Optional[modal.cls._Obj]
116
139
  _web_url: typing.Optional[str]
@@ -124,7 +147,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
124
147
  _cluster_size: typing.Optional[int]
125
148
  _use_method_name: str
126
149
  _class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
127
- _method_handle_metadata: typing.Optional[typing.Dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
150
+ _method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
128
151
 
129
152
  def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function._PartialFunction): ...
130
153
  def _bind_instance_method(self, class_bound_method: _Function): ...
@@ -133,23 +156,23 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
133
156
  info: modal._utils.function_utils.FunctionInfo,
134
157
  app,
135
158
  image: modal.image._Image,
136
- secrets: typing.Sequence[modal.secret._Secret] = (),
159
+ secrets: collections.abc.Sequence[modal.secret._Secret] = (),
137
160
  schedule: typing.Optional[modal.schedule.Schedule] = None,
138
161
  is_generator=False,
139
162
  gpu: typing.Union[
140
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
163
+ None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
141
164
  ] = None,
142
- mounts: typing.Collection[modal.mount._Mount] = (),
143
- network_file_systems: typing.Dict[
165
+ mounts: collections.abc.Collection[modal.mount._Mount] = (),
166
+ network_file_systems: dict[
144
167
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
145
168
  ] = {},
146
169
  allow_cross_region_volumes: bool = False,
147
- volumes: typing.Dict[
170
+ volumes: dict[
148
171
  typing.Union[str, pathlib.PurePosixPath],
149
172
  typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
150
173
  ] = {},
151
174
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
152
- memory: typing.Union[int, typing.Tuple[int, int], None] = None,
175
+ memory: typing.Union[int, tuple[int, int], None] = None,
153
176
  proxy: typing.Optional[modal.proxy._Proxy] = None,
154
177
  retries: typing.Union[int, modal.retries.Retries, None] = None,
155
178
  timeout: typing.Optional[int] = None,
@@ -179,13 +202,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
179
202
  obj: modal.cls._Obj,
180
203
  from_other_workspace: bool,
181
204
  options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
182
- args: typing.Sized,
183
- kwargs: typing.Dict[str, typing.Any],
205
+ args: collections.abc.Sized,
206
+ kwargs: dict[str, typing.Any],
184
207
  ) -> _Function: ...
185
208
  async def keep_warm(self, warm_pool_size: int) -> None: ...
186
209
  @classmethod
187
210
  def from_name(
188
- cls: typing.Type[_Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
211
+ cls: type[_Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
189
212
  ) -> _Function: ...
190
213
  @staticmethod
191
214
  async def lookup(
@@ -218,13 +241,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
218
241
  def cluster_size(self) -> int: ...
219
242
  def _map(
220
243
  self, input_queue: modal.parallel_map._SynchronizedQueue, order_outputs: bool, return_exceptions: bool
221
- ) -> typing.AsyncGenerator[typing.Any, None]: ...
244
+ ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
222
245
  async def _call_function(self, args, kwargs) -> ReturnType: ...
223
246
  async def _call_function_nowait(self, args, kwargs, function_call_invocation_type: int) -> _Invocation: ...
224
247
  def _call_generator(self, args, kwargs): ...
225
248
  async def _call_generator_nowait(self, args, kwargs): ...
226
249
  async def remote(self, *args: P.args, **kwargs: P.kwargs) -> ReturnType: ...
227
- def remote_gen(self, *args, **kwargs) -> typing.AsyncGenerator[typing.Any, None]: ...
250
+ def remote_gen(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
228
251
  def _get_info(self) -> modal._utils.function_utils.FunctionInfo: ...
229
252
  def _get_obj(self) -> typing.Optional[modal.cls._Obj]: ...
230
253
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
@@ -279,7 +302,7 @@ P_INNER = typing_extensions.ParamSpec("P_INNER")
279
302
 
280
303
  class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.Object):
281
304
  _info: typing.Optional[modal._utils.function_utils.FunctionInfo]
282
- _serve_mounts: typing.FrozenSet[modal.mount.Mount]
305
+ _serve_mounts: frozenset[modal.mount.Mount]
283
306
  _app: typing.Optional[modal.app.App]
284
307
  _obj: typing.Optional[modal.cls.Obj]
285
308
  _web_url: typing.Optional[str]
@@ -293,7 +316,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
293
316
  _cluster_size: typing.Optional[int]
294
317
  _use_method_name: str
295
318
  _class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
296
- _method_handle_metadata: typing.Optional[typing.Dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
319
+ _method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
297
320
 
298
321
  def __init__(self, *args, **kwargs): ...
299
322
  def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction): ...
@@ -303,23 +326,23 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
303
326
  info: modal._utils.function_utils.FunctionInfo,
304
327
  app,
305
328
  image: modal.image.Image,
306
- secrets: typing.Sequence[modal.secret.Secret] = (),
329
+ secrets: collections.abc.Sequence[modal.secret.Secret] = (),
307
330
  schedule: typing.Optional[modal.schedule.Schedule] = None,
308
331
  is_generator=False,
309
332
  gpu: typing.Union[
310
- None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
333
+ None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
311
334
  ] = None,
312
- mounts: typing.Collection[modal.mount.Mount] = (),
313
- network_file_systems: typing.Dict[
335
+ mounts: collections.abc.Collection[modal.mount.Mount] = (),
336
+ network_file_systems: dict[
314
337
  typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
315
338
  ] = {},
316
339
  allow_cross_region_volumes: bool = False,
317
- volumes: typing.Dict[
340
+ volumes: dict[
318
341
  typing.Union[str, pathlib.PurePosixPath],
319
342
  typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
320
343
  ] = {},
321
344
  webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
322
- memory: typing.Union[int, typing.Tuple[int, int], None] = None,
345
+ memory: typing.Union[int, tuple[int, int], None] = None,
323
346
  proxy: typing.Optional[modal.proxy.Proxy] = None,
324
347
  retries: typing.Union[int, modal.retries.Retries, None] = None,
325
348
  timeout: typing.Optional[int] = None,
@@ -349,8 +372,8 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
349
372
  obj: modal.cls.Obj,
350
373
  from_other_workspace: bool,
351
374
  options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
352
- args: typing.Sized,
353
- kwargs: typing.Dict[str, typing.Any],
375
+ args: collections.abc.Sized,
376
+ kwargs: dict[str, typing.Any],
354
377
  ) -> Function: ...
355
378
 
356
379
  class __keep_warm_spec(typing_extensions.Protocol):
@@ -361,7 +384,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
361
384
 
362
385
  @classmethod
363
386
  def from_name(
364
- cls: typing.Type[Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
387
+ cls: type[Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
365
388
  ) -> Function: ...
366
389
 
367
390
  class __lookup_spec(typing_extensions.Protocol):
@@ -412,7 +435,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
412
435
  ) -> typing.Generator[typing.Any, None, None]: ...
413
436
  def aio(
414
437
  self, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
415
- ) -> typing.AsyncGenerator[typing.Any, None]: ...
438
+ ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
416
439
 
417
440
  _map: ___map_spec
418
441
 
@@ -444,7 +467,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
444
467
 
445
468
  class __remote_gen_spec(typing_extensions.Protocol):
446
469
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
447
- def aio(self, *args, **kwargs) -> typing.AsyncGenerator[typing.Any, None]: ...
470
+ def aio(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
448
471
 
449
472
  remote_gen: __remote_gen_spec
450
473
 
@@ -517,8 +540,8 @@ class _FunctionCall(typing.Generic[ReturnType], modal.object._Object):
517
540
 
518
541
  def _invocation(self): ...
519
542
  async def get(self, timeout: typing.Optional[float] = None) -> ReturnType: ...
520
- def get_gen(self) -> typing.AsyncGenerator[typing.Any, None]: ...
521
- async def get_call_graph(self) -> typing.List[modal.call_graph.InputInfo]: ...
543
+ def get_gen(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
544
+ async def get_call_graph(self) -> list[modal.call_graph.InputInfo]: ...
522
545
  async def cancel(self, terminate_containers: bool = False): ...
523
546
  @staticmethod
524
547
  async def from_id(
@@ -539,13 +562,13 @@ class FunctionCall(typing.Generic[ReturnType], modal.object.Object):
539
562
 
540
563
  class __get_gen_spec(typing_extensions.Protocol):
541
564
  def __call__(self) -> typing.Generator[typing.Any, None, None]: ...
542
- def aio(self) -> typing.AsyncGenerator[typing.Any, None]: ...
565
+ def aio(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
543
566
 
544
567
  get_gen: __get_gen_spec
545
568
 
546
569
  class __get_call_graph_spec(typing_extensions.Protocol):
547
- def __call__(self) -> typing.List[modal.call_graph.InputInfo]: ...
548
- async def aio(self) -> typing.List[modal.call_graph.InputInfo]: ...
570
+ def __call__(self) -> list[modal.call_graph.InputInfo]: ...
571
+ async def aio(self) -> list[modal.call_graph.InputInfo]: ...
549
572
 
550
573
  get_call_graph: __get_call_graph_spec
551
574