modal 1.0.4.dev12__py3-none-any.whl → 1.0.5.dev2__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.
modal/_functions.py CHANGED
@@ -15,7 +15,6 @@ import typing_extensions
15
15
  from google.protobuf.message import Message
16
16
  from grpclib import GRPCError, Status
17
17
  from synchronicity.combined_types import MethodWithAio
18
- from synchronicity.exceptions import UserCodeException
19
18
 
20
19
  from modal_proto import api_pb2
21
20
  from modal_proto.modal_api_grpc import ModalClientModal
@@ -63,8 +62,6 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
63
62
  from .config import config
64
63
  from .exception import (
65
64
  ExecutionError,
66
- FunctionTimeoutError,
67
- InternalFailure,
68
65
  InvalidError,
69
66
  NotFoundError,
70
67
  OutputExpiredError,
@@ -257,7 +254,7 @@ class _Invocation:
257
254
  request,
258
255
  )
259
256
 
260
- async def _get_single_output(self, expected_jwt: Optional[str] = None) -> Any:
257
+ async def _get_single_output(self, expected_jwt: Optional[str] = None) -> api_pb2.FunctionGetOutputsItem:
261
258
  # waits indefinitely for a single result for the function, and clear the outputs buffer after
262
259
  item: api_pb2.FunctionGetOutputsItem = (
263
260
  await self.pop_function_call_outputs(
@@ -266,7 +263,7 @@ class _Invocation:
266
263
  input_jwts=[expected_jwt] if expected_jwt else None,
267
264
  )
268
265
  ).outputs[0]
269
- return await _process_result(item.result, item.data_format, self.stub, self.client)
266
+ return item
270
267
 
271
268
  async def run_function(self) -> Any:
272
269
  # Use retry logic only if retry policy is specified and
@@ -278,23 +275,30 @@ class _Invocation:
278
275
  or ctx.function_call_invocation_type != api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
279
276
  or not ctx.sync_client_retries_enabled
280
277
  ):
281
- return await self._get_single_output()
278
+ item = await self._get_single_output()
279
+ return await _process_result(item.result, item.data_format, self.stub, self.client)
282
280
 
283
281
  # User errors including timeouts are managed by the user specified retry policy.
284
282
  user_retry_manager = RetryManager(ctx.retry_policy)
285
283
 
286
284
  while True:
287
- try:
288
- return await self._get_single_output(ctx.input_jwt)
289
- except (UserCodeException, FunctionTimeoutError) as exc:
285
+ item = await self._get_single_output(ctx.input_jwt)
286
+ if item.result.status in (
287
+ api_pb2.GenericResult.GENERIC_STATUS_SUCCESS,
288
+ api_pb2.GenericResult.GENERIC_STATUS_TERMINATED,
289
+ ):
290
+ # success or cancellations are "final" results
291
+ return await _process_result(item.result, item.data_format, self.stub, self.client)
292
+
293
+ if item.result.status != api_pb2.GenericResult.GENERIC_STATUS_INTERNAL_FAILURE:
294
+ # non-internal failures get a delay before retrying
290
295
  delay_ms = user_retry_manager.get_delay_ms()
291
296
  if delay_ms is None:
292
- raise exc
297
+ # no more retries, this should raise an error when the non-success status is converted
298
+ # to an exception:
299
+ return await _process_result(item.result, item.data_format, self.stub, self.client)
293
300
  await asyncio.sleep(delay_ms / 1000)
294
- except InternalFailure:
295
- # For system failures on the server, we retry immediately,
296
- # and the failure does not count towards the retry policy.
297
- pass
301
+
298
302
  await self._retry_input()
299
303
 
300
304
  async def poll_function(self, timeout: Optional[float] = None):
@@ -399,27 +403,27 @@ class _InputPlaneInvocation:
399
403
  attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
400
404
  )
401
405
 
402
- try:
403
- if await_response.HasField("output"):
404
- return await _process_result(
405
- await_response.output.result, await_response.output.data_format, self.stub, self.client
406
- )
407
- except InternalFailure as e:
408
- internal_failure_count += 1
409
- # Limit the number of times we retry
410
- if internal_failure_count >= MAX_INTERNAL_FAILURE_COUNT:
411
- raise e
412
- # For system failures on the server, we retry immediately,
413
- # and the failure does not count towards the retry policy.
414
- retry_request = api_pb2.AttemptRetryRequest(
415
- function_id=self.function_id,
416
- parent_input_id=current_input_id() or "",
417
- input=self.input_item,
418
- attempt_token=self.attempt_token,
406
+ if await_response.HasField("output"):
407
+ if await_response.output.result.status == api_pb2.GenericResult.GENERIC_STATUS_INTERNAL_FAILURE:
408
+ internal_failure_count += 1
409
+ # Limit the number of times we retry
410
+ if internal_failure_count < MAX_INTERNAL_FAILURE_COUNT:
411
+ # For system failures on the server, we retry immediately,
412
+ # and the failure does not count towards the retry policy.
413
+ retry_request = api_pb2.AttemptRetryRequest(
414
+ function_id=self.function_id,
415
+ parent_input_id=current_input_id() or "",
416
+ input=self.input_item,
417
+ attempt_token=self.attempt_token,
418
+ )
419
+ # TODO(ryan): Add exponential backoff?
420
+ retry_response = await retry_transient_errors(self.stub.AttemptRetry, retry_request)
421
+ self.attempt_token = retry_response.attempt_token
422
+ continue
423
+
424
+ return await _process_result(
425
+ await_response.output.result, await_response.output.data_format, self.stub, self.client
419
426
  )
420
- # TODO(ryan): Add exponential backoff?
421
- retry_response = await retry_transient_errors(self.stub.AttemptRetry, retry_request)
422
- self.attempt_token = retry_response.attempt_token
423
427
 
424
428
 
425
429
  # Wrapper type for api_pb2.FunctionStats
@@ -1438,7 +1442,11 @@ Use the `Function.get_web_url()` method instead.
1438
1442
 
1439
1443
  @live_method_gen
1440
1444
  async def _map(
1441
- self, input_queue: _SynchronizedQueue, order_outputs: bool, return_exceptions: bool
1445
+ self,
1446
+ input_queue: _SynchronizedQueue,
1447
+ order_outputs: bool,
1448
+ return_exceptions: bool,
1449
+ wrap_returned_exceptions: bool,
1442
1450
  ) -> AsyncGenerator[Any, None]:
1443
1451
  """mdmd:hidden
1444
1452
 
@@ -1466,6 +1474,7 @@ Use the `Function.get_web_url()` method instead.
1466
1474
  self.client,
1467
1475
  order_outputs,
1468
1476
  return_exceptions,
1477
+ wrap_returned_exceptions,
1469
1478
  count_update_callback,
1470
1479
  api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC,
1471
1480
  )
@@ -396,7 +396,7 @@ class _WarnIfGeneratorIsNotConsumed:
396
396
  return await self.gen.aclose()
397
397
 
398
398
 
399
- synchronize_api(_WarnIfGeneratorIsNotConsumed)
399
+ _BlockingWarnIfGeneratorIsNotConsumed = synchronize_api(_WarnIfGeneratorIsNotConsumed)
400
400
 
401
401
 
402
402
  class _WarnIfNonWrappedGeneratorIsNotConsumed(_WarnIfGeneratorIsNotConsumed):
@@ -10,7 +10,6 @@ from typing import Any, Callable, Literal, Optional
10
10
 
11
11
  from grpclib import GRPCError
12
12
  from grpclib.exceptions import StreamTerminatedError
13
- from synchronicity.exceptions import UserCodeException
14
13
 
15
14
  import modal_proto
16
15
  from modal_proto import api_pb2
@@ -497,8 +496,9 @@ async def _process_result(result: api_pb2.GenericResult, data_format: int, stub,
497
496
  append_modal_tb(exc, tb_dict, line_cache)
498
497
  except Exception:
499
498
  pass
500
- uc_exc = UserCodeException(exc_with_hints(exc))
501
- raise uc_exc
499
+
500
+ raise exc_with_hints(exc)
501
+
502
502
  raise RemoteError(result.exception)
503
503
 
504
504
  try:
modal/client.pyi CHANGED
@@ -31,7 +31,7 @@ class _Client:
31
31
  server_url: str,
32
32
  client_type: int,
33
33
  credentials: typing.Optional[tuple[str, str]],
34
- version: str = "1.0.4.dev12",
34
+ version: str = "1.0.5.dev2",
35
35
  ): ...
36
36
  def is_closed(self) -> bool: ...
37
37
  @property
@@ -94,7 +94,7 @@ class Client:
94
94
  server_url: str,
95
95
  client_type: int,
96
96
  credentials: typing.Optional[tuple[str, str]],
97
- version: str = "1.0.4.dev12",
97
+ version: str = "1.0.5.dev2",
98
98
  ): ...
99
99
  def is_closed(self) -> bool: ...
100
100
  @property
modal/exception.py CHANGED
@@ -2,6 +2,10 @@
2
2
  import random
3
3
  import signal
4
4
 
5
+ import synchronicity.exceptions
6
+
7
+ UserCodeException = synchronicity.exceptions.UserCodeException # Deprecated type used for return_exception wrapping
8
+
5
9
 
6
10
  class Error(Exception):
7
11
  """
modal/functions.pyi CHANGED
@@ -197,10 +197,20 @@ class Function(
197
197
 
198
198
  class ___map_spec(typing_extensions.Protocol[SUPERSELF]):
199
199
  def __call__(
200
- self, /, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
200
+ self,
201
+ /,
202
+ input_queue: modal.parallel_map.SynchronizedQueue,
203
+ order_outputs: bool,
204
+ return_exceptions: bool,
205
+ wrap_returned_exceptions: bool,
201
206
  ) -> typing.Generator[typing.Any, None, None]: ...
202
207
  def aio(
203
- self, /, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
208
+ self,
209
+ /,
210
+ input_queue: modal.parallel_map.SynchronizedQueue,
211
+ order_outputs: bool,
212
+ return_exceptions: bool,
213
+ wrap_returned_exceptions: bool,
204
214
  ) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
205
215
 
206
216
  _map: ___map_spec[typing_extensions.Self]
@@ -227,11 +237,11 @@ class Function(
227
237
 
228
238
  _call_generator: ___call_generator_spec[typing_extensions.Self]
229
239
 
230
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
240
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
231
241
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
232
242
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
233
243
 
234
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
244
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
235
245
 
236
246
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
237
247
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
@@ -246,12 +256,12 @@ class Function(
246
256
  self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
247
257
  ) -> modal._functions.OriginalReturnType: ...
248
258
 
249
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
259
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
250
260
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
251
261
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
252
262
 
253
263
  _experimental_spawn: ___experimental_spawn_spec[
254
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
264
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
255
265
  ]
256
266
 
257
267
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -260,11 +270,11 @@ class Function(
260
270
 
261
271
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
262
272
 
263
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
273
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
264
274
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
265
275
  async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
266
276
 
267
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
277
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
268
278
 
269
279
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
270
280
 
@@ -282,7 +292,13 @@ class Function(
282
292
 
283
293
  class __map_spec(typing_extensions.Protocol[SUPERSELF]):
284
294
  def __call__(
285
- self, /, *input_iterators, kwargs={}, order_outputs: bool = True, return_exceptions: bool = False
295
+ self,
296
+ /,
297
+ *input_iterators,
298
+ kwargs={},
299
+ order_outputs: bool = True,
300
+ return_exceptions: bool = False,
301
+ wrap_returned_exceptions: bool = True,
286
302
  ) -> modal._utils.async_utils.AsyncOrSyncIterable: ...
287
303
  def aio(
288
304
  self,
@@ -291,6 +307,7 @@ class Function(
291
307
  kwargs={},
292
308
  order_outputs: bool = True,
293
309
  return_exceptions: bool = False,
310
+ wrap_returned_exceptions: bool = True,
294
311
  ) -> typing.AsyncGenerator[typing.Any, None]: ...
295
312
 
296
313
  map: __map_spec[typing_extensions.Self]
@@ -304,6 +321,7 @@ class Function(
304
321
  kwargs={},
305
322
  order_outputs: bool = True,
306
323
  return_exceptions: bool = False,
324
+ wrap_returned_exceptions: bool = True,
307
325
  ) -> modal._utils.async_utils.AsyncOrSyncIterable: ...
308
326
  def aio(
309
327
  self,
@@ -315,6 +333,7 @@ class Function(
315
333
  kwargs={},
316
334
  order_outputs: bool = True,
317
335
  return_exceptions: bool = False,
336
+ wrap_returned_exceptions: bool = True,
318
337
  ) -> typing.AsyncIterable[typing.Any]: ...
319
338
 
320
339
  starmap: __starmap_spec[typing_extensions.Self]
modal/parallel_map.py CHANGED
@@ -9,6 +9,7 @@ from typing import Any, Callable, Optional
9
9
 
10
10
  from grpclib import Status
11
11
 
12
+ import modal.exception
12
13
  from modal._runtime.execution_context import current_input_id
13
14
  from modal._utils.async_utils import (
14
15
  AsyncOrSyncIterable,
@@ -89,6 +90,7 @@ async def _map_invocation(
89
90
  client: "modal.client._Client",
90
91
  order_outputs: bool,
91
92
  return_exceptions: bool,
93
+ wrap_returned_exceptions: bool,
92
94
  count_update_callback: Optional[Callable[[int, int], None]],
93
95
  function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType",
94
96
  ):
@@ -278,17 +280,19 @@ async def _map_invocation(
278
280
  )
279
281
  )
280
282
  map_done_task = asyncio.create_task(map_done_event.wait())
281
- done, pending = await asyncio.wait([get_response_task, map_done_task], return_when=FIRST_COMPLETED)
282
- if get_response_task in done:
283
- map_done_task.cancel()
284
- response = get_response_task.result()
285
- else:
286
- assert map_done_event.is_set()
287
- # map is done, cancel the pending call
283
+ try:
284
+ done, pending = await asyncio.wait([get_response_task, map_done_task], return_when=FIRST_COMPLETED)
285
+ if get_response_task in done:
286
+ map_done_task.cancel()
287
+ response = get_response_task.result()
288
+ else:
289
+ assert map_done_event.is_set()
290
+ # map is done - no more outputs, so return early
291
+ return
292
+ finally:
293
+ # clean up tasks, in case of cancellations etc.
288
294
  get_response_task.cancel()
289
- # not strictly necessary - don't leave dangling task
290
- await asyncio.gather(get_response_task, return_exceptions=True)
291
- return
295
+ map_done_task.cancel()
292
296
 
293
297
  last_entry_id = response.last_entry_id
294
298
  now_seconds = int(time.time())
@@ -339,7 +343,13 @@ async def _map_invocation(
339
343
  output = await _process_result(item.result, item.data_format, client.stub, client)
340
344
  except Exception as e:
341
345
  if return_exceptions:
342
- output = e
346
+ if wrap_returned_exceptions:
347
+ # Prior to client 1.0.4 there was a bug where return_exceptions would wrap
348
+ # any returned exceptions in a synchronicity.UserCodeException. This adds
349
+ # deprecated non-breaking compatibility bandaid for migrating away from that:
350
+ output = modal.exception.UserCodeException(e)
351
+ else:
352
+ output = e
343
353
  else:
344
354
  raise e
345
355
  return (item.idx, output)
@@ -411,6 +421,7 @@ async def _map_helper(
411
421
  kwargs={}, # any extra keyword arguments for the function
412
422
  order_outputs: bool = True, # return outputs in order
413
423
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
424
+ wrap_returned_exceptions: bool = True,
414
425
  ) -> typing.AsyncGenerator[Any, None]:
415
426
  """Core implementation that supports `_map_async()`, `_starmap_async()` and `_for_each_async()`.
416
427
 
@@ -441,7 +452,9 @@ async def _map_helper(
441
452
  # synchronicity-wrapped, since they accept executable code in the form of iterators that we don't want to run inside
442
453
  # the synchronicity thread. Instead, we delegate to `._map()` with a safer Queue as input.
443
454
  async with aclosing(
444
- async_merge(self._map.aio(raw_input_queue, order_outputs, return_exceptions), feed_queue())
455
+ async_merge(
456
+ self._map.aio(raw_input_queue, order_outputs, return_exceptions, wrap_returned_exceptions), feed_queue()
457
+ )
445
458
  ) as map_output_stream:
446
459
  async for output in map_output_stream:
447
460
  yield output
@@ -456,10 +469,16 @@ async def _map_async(
456
469
  kwargs={}, # any extra keyword arguments for the function
457
470
  order_outputs: bool = True, # return outputs in order
458
471
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
472
+ wrap_returned_exceptions: bool = True, # wrap returned exceptions in modal.exception.UserCodeException
459
473
  ) -> typing.AsyncGenerator[Any, None]:
460
474
  async_input_gen = async_zip(*[sync_or_async_iter(it) for it in input_iterators])
461
475
  async for output in _map_helper(
462
- self, async_input_gen, kwargs=kwargs, order_outputs=order_outputs, return_exceptions=return_exceptions
476
+ self,
477
+ async_input_gen,
478
+ kwargs=kwargs,
479
+ order_outputs=order_outputs,
480
+ return_exceptions=return_exceptions,
481
+ wrap_returned_exceptions=wrap_returned_exceptions,
463
482
  ):
464
483
  yield output
465
484
 
@@ -472,6 +491,7 @@ async def _starmap_async(
472
491
  kwargs={},
473
492
  order_outputs: bool = True,
474
493
  return_exceptions: bool = False,
494
+ wrap_returned_exceptions: bool = True,
475
495
  ) -> typing.AsyncIterable[Any]:
476
496
  async for output in _map_helper(
477
497
  self,
@@ -479,6 +499,7 @@ async def _starmap_async(
479
499
  kwargs=kwargs,
480
500
  order_outputs=order_outputs,
481
501
  return_exceptions=return_exceptions,
502
+ wrap_returned_exceptions=wrap_returned_exceptions,
482
503
  ):
483
504
  yield output
484
505
 
@@ -500,6 +521,7 @@ def _map_sync(
500
521
  kwargs={}, # any extra keyword arguments for the function
501
522
  order_outputs: bool = True, # return outputs in order
502
523
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
524
+ wrap_returned_exceptions: bool = True,
503
525
  ) -> AsyncOrSyncIterable:
504
526
  """Parallel map over a set of inputs.
505
527
 
@@ -540,7 +562,12 @@ def _map_sync(
540
562
 
541
563
  return AsyncOrSyncIterable(
542
564
  _map_async(
543
- self, *input_iterators, kwargs=kwargs, order_outputs=order_outputs, return_exceptions=return_exceptions
565
+ self,
566
+ *input_iterators,
567
+ kwargs=kwargs,
568
+ order_outputs=order_outputs,
569
+ return_exceptions=return_exceptions,
570
+ wrap_returned_exceptions=wrap_returned_exceptions,
544
571
  ),
545
572
  nested_async_message=(
546
573
  "You can't iter(Function.map()) from an async function. Use async for ... in Function.map.aio() instead."
@@ -620,6 +647,7 @@ def _starmap_sync(
620
647
  kwargs={},
621
648
  order_outputs: bool = True,
622
649
  return_exceptions: bool = False,
650
+ wrap_returned_exceptions: bool = True,
623
651
  ) -> AsyncOrSyncIterable:
624
652
  """Like `map`, but spreads arguments over multiple function arguments.
625
653
 
@@ -639,7 +667,12 @@ def _starmap_sync(
639
667
  """
640
668
  return AsyncOrSyncIterable(
641
669
  _starmap_async(
642
- self, input_iterator, kwargs=kwargs, order_outputs=order_outputs, return_exceptions=return_exceptions
670
+ self,
671
+ input_iterator,
672
+ kwargs=kwargs,
673
+ order_outputs=order_outputs,
674
+ return_exceptions=return_exceptions,
675
+ wrap_returned_exceptions=wrap_returned_exceptions,
643
676
  ),
644
677
  nested_async_message=(
645
678
  "You can't `iter(Function.starmap())` from an async function. "
@@ -716,6 +749,7 @@ class _MapItemContext:
716
749
  Return True if input state was changed to COMPLETE, otherwise False.
717
750
  """
718
751
  # If the item is already complete, this is a duplicate output and can be ignored.
752
+
719
753
  if self.state == _MapItemState.COMPLETE:
720
754
  logger.debug(
721
755
  f"Received output for input marked as complete. Must be duplicate, so ignoring. "
@@ -761,11 +795,12 @@ class _MapItemContext:
761
795
  delay_ms = 0
762
796
 
763
797
  # None means the maximum number of retries has been reached, so output the error
764
- if delay_ms is None:
798
+ if delay_ms is None or item.result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
765
799
  self.state = _MapItemState.COMPLETE
766
800
  return _OutputType.FAILED_COMPLETION
767
801
 
768
802
  self.state = _MapItemState.WAITING_TO_RETRY
803
+
769
804
  await retry_queue.put(now_seconds + (delay_ms / 1000), item.idx)
770
805
 
771
806
  return _OutputType.RETRYING
modal/parallel_map.pyi CHANGED
@@ -52,6 +52,7 @@ def _map_invocation(
52
52
  client: modal.client._Client,
53
53
  order_outputs: bool,
54
54
  return_exceptions: bool,
55
+ wrap_returned_exceptions: bool,
55
56
  count_update_callback: typing.Optional[collections.abc.Callable[[int, int], None]],
56
57
  function_call_invocation_type: int,
57
58
  ): ...
@@ -61,6 +62,7 @@ def _map_helper(
61
62
  kwargs={},
62
63
  order_outputs: bool = True,
63
64
  return_exceptions: bool = False,
65
+ wrap_returned_exceptions: bool = True,
64
66
  ) -> typing.AsyncGenerator[typing.Any, None]: ...
65
67
  def _map_async(
66
68
  self: modal.functions.Function,
@@ -68,6 +70,7 @@ def _map_async(
68
70
  kwargs={},
69
71
  order_outputs: bool = True,
70
72
  return_exceptions: bool = False,
73
+ wrap_returned_exceptions: bool = True,
71
74
  ) -> typing.AsyncGenerator[typing.Any, None]: ...
72
75
  def _starmap_async(
73
76
  self,
@@ -78,10 +81,16 @@ def _starmap_async(
78
81
  kwargs={},
79
82
  order_outputs: bool = True,
80
83
  return_exceptions: bool = False,
84
+ wrap_returned_exceptions: bool = True,
81
85
  ) -> typing.AsyncIterable[typing.Any]: ...
82
86
  async def _for_each_async(self, *input_iterators, kwargs={}, ignore_exceptions: bool = False) -> None: ...
83
87
  def _map_sync(
84
- self, *input_iterators, kwargs={}, order_outputs: bool = True, return_exceptions: bool = False
88
+ self,
89
+ *input_iterators,
90
+ kwargs={},
91
+ order_outputs: bool = True,
92
+ return_exceptions: bool = False,
93
+ wrap_returned_exceptions: bool = True,
85
94
  ) -> modal._utils.async_utils.AsyncOrSyncIterable: ...
86
95
  async def _spawn_map_async(self, *input_iterators, kwargs={}) -> None: ...
87
96
  def _spawn_map_sync(self, *input_iterators, kwargs={}) -> None: ...
@@ -93,6 +102,7 @@ def _starmap_sync(
93
102
  kwargs={},
94
103
  order_outputs: bool = True,
95
104
  return_exceptions: bool = False,
105
+ wrap_returned_exceptions: bool = True,
96
106
  ) -> modal._utils.async_utils.AsyncOrSyncIterable: ...
97
107
 
98
108
  class _MapItemState(enum.Enum):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.4.dev12
3
+ Version: 1.0.5.dev2
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ Requires-Dist: click~=8.1.0
22
22
  Requires-Dist: grpclib==0.4.7
23
23
  Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
24
24
  Requires-Dist: rich>=12.0.0
25
- Requires-Dist: synchronicity~=0.9.14
25
+ Requires-Dist: synchronicity~=0.9.15
26
26
  Requires-Dist: toml
27
27
  Requires-Dist: typer>=0.9
28
28
  Requires-Dist: types-certifi
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=sTJcc9EbDuCKSwg3tL6ZckFw9WWdlkXW8mId1IvJCNc,2846
3
3
  modal/_clustered_functions.py,sha256=kTf-9YBXY88NutC1akI-gCbvf01RhMPCw-zoOI_YIUE,2700
4
4
  modal/_clustered_functions.pyi,sha256=2aWxN2v5WUnj-R-sk6BzJ-3AvggkQGQjwhtvbDH3pds,777
5
5
  modal/_container_entrypoint.py,sha256=2Zx9O_EMJg0H77EdnC2vGKs6uFMWwbP1NLFf-qYmWmU,28962
6
- modal/_functions.py,sha256=4UDJaBh5S0A24V5-whnzQGamXxGbKZAk2HWc7jH3rAY,78955
6
+ modal/_functions.py,sha256=x_NEh_UnCJIL_62LAwTiJQxsPjLdot6GqQJY5S4Vg2g,79584
7
7
  modal/_ipython.py,sha256=TW1fkVOmZL3YYqdS2YlM1hqpf654Yf8ZyybHdBnlhSw,301
8
8
  modal/_location.py,sha256=joiX-0ZeutEUDTrrqLF1GHXCdVLF-rHzstocbMcd_-k,366
9
9
  modal/_object.py,sha256=KzzzZoM41UQUiY9TKOrft9BtZKgjWG_ukdlyLGjB4UY,10758
@@ -22,7 +22,7 @@ modal/app.py,sha256=NZ_rJ9TuMfiNiLg8-gOFgufD5flGtXWPHOZI0gdD3hE,46585
22
22
  modal/app.pyi,sha256=4-b_vbe3lNAqQPcMRpQCEDsE1zsVkQRJGUql9B7HvbM,22659
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=OwISJvkgMb-rHm9Gc4i-7YcDgGiZgwJ7F_PzwZH7a6Q,16847
25
- modal/client.pyi,sha256=KVuhPigNi5GmL70lURFg0-4rTmfp21DMJVai9gIsgCs,8459
25
+ modal/client.pyi,sha256=7PjUQqV3Ts7Lph5KW8C12qV85jLN4wNwSzn3EkuYWsE,8457
26
26
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
27
27
  modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
28
28
  modal/cls.py,sha256=JjxAeVudXKMp-co9YUnsGG6COJOv91K0ylf4f3mR67o,39792
@@ -34,12 +34,12 @@ modal/dict.py,sha256=w-Zuk3FXuwkyxKuF1ry86S8j2cvoC8-u4Ga0h-GfV1s,14324
34
34
  modal/dict.pyi,sha256=RBaQyOd1ABRNN7vIf5L_rv94y7Kq5Qn9IlKHSr4j8N0,8120
35
35
  modal/environments.py,sha256=gHFNLG78bqgizpQ4w_elz27QOqmcgAonFsmLs7NjUJ4,6804
36
36
  modal/environments.pyi,sha256=4HbI0kywveaUVI7HqDtZ4HphCTGXYi_wie2hz87up5A,3425
37
- modal/exception.py,sha256=2pgq-j8JP-tB3yU2VmYOzn9CsynU9_h8IU_MgqgKegM,5352
37
+ modal/exception.py,sha256=PynBCuyc1zJ4NBJlNSGkRpz_SdiLDW8aNUhrBkfnbd8,5503
38
38
  modal/file_io.py,sha256=SCBfLk5gRieqdTVlA_f-2YHHtRp7Iy_sA6iR1zPsO3c,21100
39
39
  modal/file_io.pyi,sha256=IPQsnr5nn5Ci4OdjmRPSI1qi3AYamxWMhpEFNYPKlHM,9436
40
40
  modal/file_pattern_matcher.py,sha256=wov-otB5M1oTdrYDtR2_VgacYin2srdtAP4McA1Cqzw,6516
41
41
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
42
- modal/functions.pyi,sha256=iqdp5ixtOOlm8bF-QYbD_G8VKqSRt_AVLT7AWjpn6pQ,16236
42
+ modal/functions.pyi,sha256=V0tBRU4CpwJpZCzRzktDXoeV60TV5neLWKBbWUl8-KM,16687
43
43
  modal/gpu.py,sha256=Kbhs_u49FaC2Zi0TjCdrpstpRtT5eZgecynmQi5IZVE,6752
44
44
  modal/image.py,sha256=yrI9DCw7GAck3d788GCHJom-_yU55zNu7reNapBhlgE,93284
45
45
  modal/image.pyi,sha256=2xjB6XOZDtm_chDdd90UoIj8pnDt5hCg6bOmu5fNaA4,25625
@@ -52,8 +52,8 @@ modal/network_file_system.pyi,sha256=58DiUqHGlARmI3cz-Yo7IFObKKFIiGh5UIU5JxGNFfc
52
52
  modal/object.py,sha256=bTeskuY8JFrESjU4_UL_nTwYlBQdOLmVaOX3X6EMxsg,164
53
53
  modal/object.pyi,sha256=UkR8NQ1jCIaw3hBUPxBRc6vvrOqtV37G_hsW2O5-4wE,5378
54
54
  modal/output.py,sha256=q4T9uHduunj4NwY-YSwkHGgjZlCXMuJbfQ5UFaAGRAc,1968
55
- modal/parallel_map.py,sha256=1SOOUzKGVItv9fP2WvYR5vl49Y5q2GdupsnJMLIUDUw,37434
56
- modal/parallel_map.pyi,sha256=mhYGQmufQEJbjNrX7vNhBS2gUdfBrpmuWNUHth_Dz6U,6140
55
+ modal/parallel_map.py,sha256=CnjC0-So6gGZtknuWZ_88MrprEbV2Xxb5TjVKlMmOes,38729
56
+ modal/parallel_map.pyi,sha256=ITRPGSaKsyxKd5IOyOZgUH6Ia47pqzDk0zF0hdxoc7k,6408
57
57
  modal/partial_function.py,sha256=SwuAAj2wj4SO6F6nkSnwNZrczEmm9w9YdlQTHh6hr04,1195
58
58
  modal/partial_function.pyi,sha256=NFWz1aCAs2B3-GnPf1cTatWRZOLnYpFKCnjP_X9iNRs,6411
59
59
  modal/proxy.py,sha256=XEjIHzZvbD3UW4YWyDzbDuNFq6hDUxyPPxupl2qwULY,1429
@@ -91,12 +91,12 @@ modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5
91
91
  modal/_runtime/user_code_imports.py,sha256=78wJyleqY2RVibqcpbDQyfWVBVT9BjyHPeoV9WdwV5Y,17720
92
92
  modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
93
93
  modal/_utils/app_utils.py,sha256=88BT4TPLWfYAQwKTHcyzNQRHg8n9B-QE2UyJs96iV-0,108
94
- modal/_utils/async_utils.py,sha256=wgOSjofVaQAyP8Oq_xTSET5mYP5Y5W_eaQCPmE63xRA,29321
94
+ modal/_utils/async_utils.py,sha256=MhSCsCL8GqIVFWoHubU_899IH-JBZAiiqadG9Wri2l4,29361
95
95
  modal/_utils/blob_utils.py,sha256=IexC2Jbtqp_Tkmy62ayfgzTYte0UPCNufB_v-DO21g8,18585
96
96
  modal/_utils/bytes_io_segment_payload.py,sha256=vaXPq8b52-x6G2hwE7SrjS58pg_aRm7gV3bn3yjmTzQ,4261
97
97
  modal/_utils/deprecation.py,sha256=EXP1beU4pmEqEzWMLw6E3kUfNfpmNA_VOp6i0EHi93g,4856
98
98
  modal/_utils/docker_utils.py,sha256=h1uETghR40mp_y3fSWuZAfbIASH1HMzuphJHghAL6DU,3722
99
- modal/_utils/function_utils.py,sha256=bhrjyOHPPXm6fAyJx3bzI1Yh56j6xh8eeMSFKdAWrHQ,26978
99
+ modal/_utils/function_utils.py,sha256=cc1QQ5s_Y-5mlLa2wzZ6NF6cH2k8asaDHerASUqI7FU,26878
100
100
  modal/_utils/git_utils.py,sha256=qtUU6JAttF55ZxYq51y55OR58B0tDPZsZWK5dJe6W5g,3182
101
101
  modal/_utils/grpc_testing.py,sha256=H1zHqthv19eGPJz2HKXDyWXWGSqO4BRsxah3L5Xaa8A,8619
102
102
  modal/_utils/grpc_utils.py,sha256=xSFosSJYQ4m6cH9WtChcSXqsnyk6DMeVvOHI4N3914g,10922
@@ -147,7 +147,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
147
147
  modal/requirements/PREVIEW.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqrs,312
148
148
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
149
149
  modal/requirements/base-images.json,sha256=f1bwyp2UkM844eoO9Qk30gQw_xrMqKpMSeJ6MErXnEk,995
150
- modal-1.0.4.dev12.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
150
+ modal-1.0.5.dev2.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
151
151
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
152
152
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
153
153
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -170,10 +170,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
170
170
  modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
171
171
  modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
172
172
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
173
- modal_version/__init__.py,sha256=EQ_zUTh_-WBVmHGSI7Yvy46jXn5PYMLFT67epWx6oLI,121
173
+ modal_version/__init__.py,sha256=NkZAo6TNWTd_faZuAJdv5q-Ut_nDvzdfGsX-g7jaNuE,120
174
174
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
175
- modal-1.0.4.dev12.dist-info/METADATA,sha256=mrPfy1u126T4G9QF_ieUE8KzjFPDiFZmJR1irfrXoJc,2455
176
- modal-1.0.4.dev12.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
177
- modal-1.0.4.dev12.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
- modal-1.0.4.dev12.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
- modal-1.0.4.dev12.dist-info/RECORD,,
175
+ modal-1.0.5.dev2.dist-info/METADATA,sha256=mr9ndcJWwMGP1WTeAPuk1l-7aCnvOcn-xsJvtB6VQXY,2454
176
+ modal-1.0.5.dev2.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
177
+ modal-1.0.5.dev2.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
+ modal-1.0.5.dev2.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
+ modal-1.0.5.dev2.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.0.4.dev12"
4
+ __version__ = "1.0.5.dev2"