modal 0.67.9__py3-none-any.whl → 0.67.11__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.
@@ -193,7 +193,7 @@ def get_user_class_instance(cls: typing.Union[type, modal.cls.Cls], args: tuple,
193
193
  if isinstance(cls, modal.cls.Cls):
194
194
  # globally @app.cls-decorated class
195
195
  modal_obj: modal.cls.Obj = cls(*args, **kwargs)
196
- modal_obj.entered = True # ugly but prevents .local() from triggering additional enter-logic
196
+ modal_obj._entered = True # ugly but prevents .local() from triggering additional enter-logic
197
197
  # TODO: unify lifecycle logic between .local() and container_entrypoint
198
198
  user_cls_instance = modal_obj._cached_user_cls_instance()
199
199
  else:
modal/client.pyi CHANGED
@@ -26,7 +26,7 @@ class _Client:
26
26
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
27
27
 
28
28
  def __init__(
29
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.9"
29
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.11"
30
30
  ): ...
31
31
  def is_closed(self) -> bool: ...
32
32
  @property
@@ -81,7 +81,7 @@ class Client:
81
81
  _stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
82
82
 
83
83
  def __init__(
84
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.9"
84
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.11"
85
85
  ): ...
86
86
  def is_closed(self) -> bool: ...
87
87
  @property
modal/cls.py CHANGED
@@ -78,7 +78,7 @@ class _Obj:
78
78
  All this class does is to return `Function` objects."""
79
79
 
80
80
  _functions: dict[str, _Function]
81
- _entered: bool
81
+ _has_entered: bool
82
82
  _user_cls_instance: Optional[Any] = None
83
83
  _construction_args: tuple[tuple, dict[str, Any]]
84
84
 
@@ -121,8 +121,7 @@ class _Obj:
121
121
  self._method_functions[method_name] = method
122
122
 
123
123
  # Used for construction local object lazily
124
- self._entered = False
125
- self._local_user_cls_instance = None
124
+ self._has_entered = False
126
125
  self._user_cls = user_cls
127
126
  self._construction_args = (args, kwargs) # used for lazy construction in case of explicit constructors
128
127
 
@@ -176,8 +175,8 @@ class _Obj:
176
175
 
177
176
  return self._user_cls_instance
178
177
 
179
- def enter(self):
180
- if not self._entered:
178
+ def _enter(self):
179
+ if not self._has_entered:
181
180
  if hasattr(self._user_cls_instance, "__enter__"):
182
181
  self._user_cls_instance.__enter__()
183
182
 
@@ -188,26 +187,26 @@ class _Obj:
188
187
  for enter_method in _find_callables_for_obj(self._user_cls_instance, method_flag).values():
189
188
  enter_method()
190
189
 
191
- self._entered = True
190
+ self._has_entered = True
192
191
 
193
192
  @property
194
- def entered(self):
195
- # needed because aenter is nowrap
196
- return self._entered
193
+ def _entered(self) -> bool:
194
+ # needed because _aenter is nowrap
195
+ return self._has_entered
197
196
 
198
- @entered.setter
199
- def entered(self, val):
200
- self._entered = val
197
+ @_entered.setter
198
+ def _entered(self, val: bool):
199
+ self._has_entered = val
201
200
 
202
201
  @synchronizer.nowrap
203
- async def aenter(self):
204
- if not self.entered:
202
+ async def _aenter(self):
203
+ if not self._entered: # use the property to get at the impl class
205
204
  user_cls_instance = self._cached_user_cls_instance()
206
205
  if hasattr(user_cls_instance, "__aenter__"):
207
206
  await user_cls_instance.__aenter__()
208
207
  elif hasattr(user_cls_instance, "__enter__"):
209
208
  user_cls_instance.__enter__()
210
- self.entered = True
209
+ self._has_entered = True
211
210
 
212
211
  def __getattr__(self, k):
213
212
  if k in self._method_functions:
modal/cls.pyi CHANGED
@@ -22,7 +22,7 @@ def _get_class_constructor_signature(user_cls: type) -> inspect.Signature: ...
22
22
 
23
23
  class _Obj:
24
24
  _functions: dict[str, modal.functions._Function]
25
- _entered: bool
25
+ _has_entered: bool
26
26
  _user_cls_instance: typing.Optional[typing.Any]
27
27
  _construction_args: tuple[tuple, dict[str, typing.Any]]
28
28
  _instance_service_function: typing.Optional[modal.functions._Function]
@@ -41,17 +41,17 @@ class _Obj:
41
41
  def _new_user_cls_instance(self): ...
42
42
  async def keep_warm(self, warm_pool_size: int) -> None: ...
43
43
  def _cached_user_cls_instance(self): ...
44
- def enter(self): ...
44
+ def _enter(self): ...
45
45
  @property
46
- def entered(self): ...
47
- @entered.setter
48
- def entered(self, val): ...
49
- async def aenter(self): ...
46
+ def _entered(self) -> bool: ...
47
+ @_entered.setter
48
+ def _entered(self, val: bool): ...
49
+ async def _aenter(self): ...
50
50
  def __getattr__(self, k): ...
51
51
 
52
52
  class Obj:
53
53
  _functions: dict[str, modal.functions.Function]
54
- _entered: bool
54
+ _has_entered: bool
55
55
  _user_cls_instance: typing.Optional[typing.Any]
56
56
  _construction_args: tuple[tuple, dict[str, typing.Any]]
57
57
  _instance_service_function: typing.Optional[modal.functions.Function]
@@ -76,12 +76,12 @@ class Obj:
76
76
  keep_warm: __keep_warm_spec
77
77
 
78
78
  def _cached_user_cls_instance(self): ...
79
- def enter(self): ...
79
+ def _enter(self): ...
80
80
  @property
81
- def entered(self): ...
82
- @entered.setter
83
- def entered(self, val): ...
84
- async def aenter(self): ...
81
+ def _entered(self) -> bool: ...
82
+ @_entered.setter
83
+ def _entered(self, val: bool): ...
84
+ async def _aenter(self): ...
85
85
  def __getattr__(self, k): ...
86
86
 
87
87
  class _Cls(modal.object._Object):
modal/config.py CHANGED
@@ -221,6 +221,7 @@ _SETTINGS = {
221
221
  "image_builder_version": _Setting(),
222
222
  "strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
223
223
  "snapshot_debug": _Setting(False, transform=_to_boolean),
224
+ "client_retries": _Setting(False, transform=_to_boolean), # For internal testing.
224
225
  }
225
226
 
226
227
 
modal/functions.py CHANGED
@@ -1,4 +1,5 @@
1
1
  # Copyright Modal Labs 2023
2
+ import dataclasses
2
3
  import inspect
3
4
  import textwrap
4
5
  import time
@@ -19,6 +20,7 @@ import typing_extensions
19
20
  from google.protobuf.message import Message
20
21
  from grpclib import GRPCError, Status
21
22
  from synchronicity.combined_types import MethodWithAio
23
+ from synchronicity.exceptions import UserCodeException
22
24
 
23
25
  from modal._utils.async_utils import aclosing
24
26
  from modal_proto import api_pb2
@@ -57,6 +59,7 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
57
59
  from .config import config
58
60
  from .exception import (
59
61
  ExecutionError,
62
+ FunctionTimeoutError,
60
63
  InvalidError,
61
64
  NotFoundError,
62
65
  OutputExpiredError,
@@ -79,7 +82,7 @@ from .parallel_map import (
79
82
  _SynchronizedQueue,
80
83
  )
81
84
  from .proxy import _Proxy
82
- from .retries import Retries
85
+ from .retries import Retries, RetryManager
83
86
  from .schedule import Schedule
84
87
  from .scheduler_placement import SchedulerPlacement
85
88
  from .secret import _Secret
@@ -91,15 +94,32 @@ if TYPE_CHECKING:
91
94
  import modal.partial_function
92
95
 
93
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
+
94
107
  class _Invocation:
95
108
  """Internal client representation of a single-input call to a Modal Function or Generator"""
96
109
 
97
110
  stub: ModalClientModal
98
111
 
99
- 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
+ ):
100
119
  self.stub = stub
101
120
  self.client = client # Used by the deserializer.
102
121
  self.function_call_id = function_call_id # TODO: remove and use only input_id
122
+ self._retry_context = retry_context
103
123
 
104
124
  @staticmethod
105
125
  async def create(
@@ -125,7 +145,17 @@ class _Invocation:
125
145
  function_call_id = response.function_call_id
126
146
 
127
147
  if response.pipelined_inputs:
128
- 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)
129
159
 
130
160
  request_put = api_pb2.FunctionPutInputsRequest(
131
161
  function_id=function_id, inputs=[item], function_call_id=function_call_id
@@ -137,7 +167,16 @@ class _Invocation:
137
167
  processed_inputs = inputs_response.inputs
138
168
  if not processed_inputs:
139
169
  raise Exception("Could not create function call - the input queue seems to be full")
140
- 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)
141
180
 
142
181
  async def pop_function_call_outputs(
143
182
  self, timeout: Optional[float], clear_on_success: bool
@@ -173,13 +212,46 @@ class _Invocation:
173
212
  # return the last response to check for state of num_unfinished_inputs
174
213
  return response
175
214
 
176
- 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:
177
228
  # waits indefinitely for a single result for the function, and clear the outputs buffer after
178
229
  item: api_pb2.FunctionGetOutputsItem = (
179
230
  await self.pop_function_call_outputs(timeout=None, clear_on_success=True)
180
231
  ).outputs[0]
181
232
  return await _process_result(item.result, item.data_format, self.stub, self.client)
182
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
+
183
255
  async def poll_function(self, timeout: Optional[float] = None):
184
256
  """Waits up to timeout for a result from a function.
185
257
 
@@ -1225,13 +1297,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1225
1297
  yield item
1226
1298
 
1227
1299
  async def _call_function(self, args, kwargs) -> ReturnType:
1300
+ if config.get("client_retries"):
1301
+ function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
1302
+ else:
1303
+ function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY
1228
1304
  invocation = await _Invocation.create(
1229
1305
  self,
1230
1306
  args,
1231
1307
  kwargs,
1232
1308
  client=self._client,
1233
- function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY,
1309
+ function_call_invocation_type=function_call_invocation_type,
1234
1310
  )
1311
+
1235
1312
  return await invocation.run_function()
1236
1313
 
1237
1314
  async def _call_function_nowait(
@@ -1348,12 +1425,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
1348
1425
  if is_async(info.raw_f):
1349
1426
  # We want to run __aenter__ and fun in the same coroutine
1350
1427
  async def coro():
1351
- await obj.aenter()
1428
+ await obj._aenter()
1352
1429
  return await fun(*args, **kwargs)
1353
1430
 
1354
1431
  return coro() # type: ignore
1355
1432
  else:
1356
- obj.enter()
1433
+ obj._enter()
1357
1434
  return fun(*args, **kwargs)
1358
1435
 
1359
1436
  @synchronizer.no_input_translation
modal/functions.pyi CHANGED
@@ -26,11 +26,35 @@ import pathlib
26
26
  import typing
27
27
  import typing_extensions
28
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
+
29
49
  class _Invocation:
30
50
  stub: modal_proto.modal_api_grpc.ModalClientModal
31
51
 
32
52
  def __init__(
33
- 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,
34
58
  ): ...
35
59
  @staticmethod
36
60
  async def create(
@@ -39,6 +63,8 @@ class _Invocation:
39
63
  async def pop_function_call_outputs(
40
64
  self, timeout: typing.Optional[float], clear_on_success: bool
41
65
  ) -> modal_proto.api_pb2.FunctionGetOutputsResponse: ...
66
+ async def _retry_input(self) -> None: ...
67
+ async def _get_single_output(self) -> typing.Any: ...
42
68
  async def run_function(self) -> typing.Any: ...
43
69
  async def poll_function(self, timeout: typing.Optional[float] = None): ...
44
70
  def run_generator(self): ...
@@ -433,11 +459,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
433
459
 
434
460
  _call_generator_nowait: ___call_generator_nowait_spec
435
461
 
436
- class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
462
+ class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
437
463
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
438
464
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
439
465
 
440
- remote: __remote_spec[ReturnType, P]
466
+ remote: __remote_spec[P, ReturnType]
441
467
 
442
468
  class __remote_gen_spec(typing_extensions.Protocol):
443
469
  def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -449,17 +475,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
449
475
  def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
450
476
  def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
451
477
 
452
- class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
478
+ class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
453
479
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
454
480
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
455
481
 
456
- _experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
482
+ _experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
457
483
 
458
- class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
484
+ class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
459
485
  def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
460
486
  async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
461
487
 
462
- spawn: __spawn_spec[ReturnType, P]
488
+ spawn: __spawn_spec[P, ReturnType]
463
489
 
464
490
  def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
465
491
 
modal/retries.py CHANGED
@@ -1,10 +1,14 @@
1
1
  # Copyright Modal Labs 2022
2
+ import asyncio
2
3
  from datetime import timedelta
3
4
 
4
5
  from modal_proto import api_pb2
5
6
 
6
7
  from .exception import InvalidError
7
8
 
9
+ MIN_INPUT_RETRY_DELAY_MS = 1000
10
+ MAX_INPUT_RETRY_DELAY_MS = 24 * 60 * 60 * 1000
11
+
8
12
 
9
13
  class Retries:
10
14
  """Adds a retry policy to a Modal function.
@@ -103,3 +107,37 @@ class Retries:
103
107
  initial_delay_ms=self.initial_delay // timedelta(milliseconds=1),
104
108
  max_delay_ms=self.max_delay // timedelta(milliseconds=1),
105
109
  )
110
+
111
+
112
+ class RetryManager:
113
+ """
114
+ Helper class to apply the specified retry policy.
115
+ """
116
+
117
+ def __init__(self, retry_policy: api_pb2.FunctionRetryPolicy):
118
+ self.retry_policy = retry_policy
119
+ self.attempt_count = 0
120
+
121
+ async def raise_or_sleep(self, exc: Exception):
122
+ """
123
+ Raises an exception if the maximum retry count has been reached, otherwise sleeps for calculated delay.
124
+ """
125
+ self.attempt_count += 1
126
+ if self.attempt_count > self.retry_policy.retries:
127
+ raise exc
128
+ delay_ms = self._retry_delay_ms(self.attempt_count, self.retry_policy)
129
+ await asyncio.sleep(delay_ms / 1000)
130
+
131
+ @staticmethod
132
+ def _retry_delay_ms(attempt_count: int, retry_policy: api_pb2.FunctionRetryPolicy) -> float:
133
+ """
134
+ Computes the amount of time to sleep before retrying based on the backend_coefficient and initial_delay_ms args.
135
+ """
136
+ if attempt_count < 1:
137
+ raise ValueError(f"Cannot compute retry delay. attempt_count must be at least 1, but was {attempt_count}")
138
+ delay_ms = retry_policy.initial_delay_ms * (retry_policy.backoff_coefficient ** (attempt_count - 1))
139
+ if delay_ms < MIN_INPUT_RETRY_DELAY_MS:
140
+ return MIN_INPUT_RETRY_DELAY_MS
141
+ if delay_ms > MAX_INPUT_RETRY_DELAY_MS:
142
+ return MAX_INPUT_RETRY_DELAY_MS
143
+ return delay_ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.67.9
3
+ Version: 0.67.11
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -19,12 +19,12 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
19
19
  modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
20
20
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
21
21
  modal/client.py,sha256=VMg_aIuo_LOEe2ttxBHEND3PLhTp5lo-onH4wELhIyY,16375
22
- modal/client.pyi,sha256=r--ghy9vX3h0MDTB7LKkpOu-FpjBuKUm1bPcMUJc-OU,7352
22
+ modal/client.pyi,sha256=B7hnEwCh6sm4OeZ4kEeSBWemVRup_MP8GsTAuAXGZ5g,7354
23
23
  modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
24
24
  modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
25
- modal/cls.py,sha256=fCtqbwu5j7LOnuPYKWPFzL7DTvls3CBvLjV0PCPpIfo,24708
26
- modal/cls.pyi,sha256=2NzoOL9cdxTMAy0Tay8KqFaCoOa8COxAcNB79mBOp20,8101
27
- modal/config.py,sha256=86vrmCPiH7PhrbC7-EDuU9yuUnqTpDGy1OEvGTdt7Fs,10923
25
+ modal/cls.py,sha256=F2jk5zFCAA8h-GfM0dbdBG3Mu5wiG9k9Z9JLYRYuT2Q,24758
26
+ modal/cls.pyi,sha256=2_nbvSlkh2d0tfibTIxsThPiL0Xcrcosc5f_ET-i0sk,8147
27
+ modal/config.py,sha256=1KhNJkjYsJkX1V8RPPdRYPlM2HE-ZZs0JVSxbiXjmrw,11010
28
28
  modal/container_process.py,sha256=c_jBPtyPeSxbIcbLfs_FzTrt-1eErtRSnsfxkDozFoY,5589
29
29
  modal/container_process.pyi,sha256=k2kClwaSzz11eci1pzFZgCm-ptXapHAyHTOENorlazA,2594
30
30
  modal/dict.py,sha256=RmJlEwFJOdSfAYcVa50hbbFccV8e7BvC5tc5g1HXF-c,12622
@@ -33,8 +33,8 @@ modal/environments.py,sha256=5cgA-zbm6ngKLsRA19zSOgtgo9-BarJK3FJK0BiF2Lo,6505
33
33
  modal/environments.pyi,sha256=XalNpiPkAtHWAvOU2Cotq0ozmtl-Jv0FDsR8h9mr27Q,3521
34
34
  modal/exception.py,sha256=EBkdWVved2XEPsXaoPRu56xfxFFHL9iuqvUsdj42WDA,6392
35
35
  modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
36
- modal/functions.py,sha256=KOhlt716_HnBZw9hwtIzC5i1mBfjeuw9zy0zcWJtw-w,66913
37
- modal/functions.pyi,sha256=yAb8-EE-Y7bbZXC0P5lEvCvmNVgxZCYlZxdagGdC3_M,24325
36
+ modal/functions.py,sha256=Pwebl3aeEkNrniXCzuDdjfxgExykTWQo7o0VjFD4To8,69853
37
+ modal/functions.pyi,sha256=qVcpwr9I48PGGDCshtkYxsesQrMdRgmleRvIXUtyAVk,25107
38
38
  modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
39
39
  modal/image.py,sha256=ZIC8tgjJnqWamN4sZ0Gch3x2VmcM671MWfRLR5SMmoc,79423
40
40
  modal/image.pyi,sha256=JjicLNuaBsfuPZ_xo_eN0zKZkDrEm2alYg-szENhJjM,24591
@@ -56,7 +56,7 @@ modal/proxy.pyi,sha256=UvygdOYneLTuoDY6hVaMNCyZ947Tmx93IdLjErUqkvM,368
56
56
  modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  modal/queue.py,sha256=q0SDkrN2e2CNxsHjhs6xMwf03LZdvuUFYL7SZPZpKps,18390
58
58
  modal/queue.pyi,sha256=di3ownBw4jc6d4X7ygXtbpjlUMOK69qyaD3lVsJbpoM,9900
59
- modal/retries.py,sha256=z4dYXdksUcjkefM3vGLkhCQ_m_TUPLJgC4uSYDzWSOU,3750
59
+ modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
60
60
  modal/runner.py,sha256=7obU-Gq1ocpBGCuR6pvn1T-D6ggg1T48qFo2TNUGWkU,24089
61
61
  modal/runner.pyi,sha256=RAtCvx_lXWjyFjIaZ3t9-X1c7rqpgAQlhl4Hww53OY8,5038
62
62
  modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
@@ -78,7 +78,7 @@ modal/_runtime/asgi.py,sha256=GvuxZqWnIHMIR-Bx5f7toCQlkERaJO8CHjTPNM9IFIw,21537
78
78
  modal/_runtime/container_io_manager.py,sha256=yVKSBBybfciDaady7NxOPVU5cm9qL690OgdZ4dZN1bs,44134
79
79
  modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
80
80
  modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
81
- modal/_runtime/user_code_imports.py,sha256=_sTWN4w1WjrO9b9oZIb5QNY_giTx3G5L_ly2UdZcy28,14520
81
+ modal/_runtime/user_code_imports.py,sha256=q_3JOYqCPDcVFZWCHEjyEqj8yzdFsQ49HzeqYmFDLbk,14521
82
82
  modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
83
83
  modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
84
84
  modal/_utils/async_utils.py,sha256=CYXogDVqqUtSe-DVP2A3F-6KztjPZaW6ez2lrYBCW_Y,24917
@@ -159,10 +159,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
159
159
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
160
  modal_version/__init__.py,sha256=3IY-AWLH55r35_mQXIaut0jrJvoPuf1NZJBQQfSbPuo,470
161
161
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
162
- modal_version/_version_generated.py,sha256=p9KVwv9r1G7UnZfBzzZEBOK-_rV1N6IZLGBkzFzsXeA,148
163
- modal-0.67.9.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
164
- modal-0.67.9.dist-info/METADATA,sha256=9qjMkC-PPnHwVJEs-r6_GlW4f4-4XcRdq36REUp6ln8,2328
165
- modal-0.67.9.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
166
- modal-0.67.9.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
167
- modal-0.67.9.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
168
- modal-0.67.9.dist-info/RECORD,,
162
+ modal_version/_version_generated.py,sha256=YsypBM6HmXq8DPKueY9FgGnZ2xdO5KF3g6dZfzBASeg,149
163
+ modal-0.67.11.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
164
+ modal-0.67.11.dist-info/METADATA,sha256=StjByc8ZnmcoGsEIpjloa7D5qiraqKpSoc5zvrmvT4o,2329
165
+ modal-0.67.11.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
166
+ modal-0.67.11.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
167
+ modal-0.67.11.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
168
+ modal-0.67.11.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2024
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 9 # git: 89698e5
4
+ build_number = 11 # git: d6fd2ae