modal 1.0.5.dev1__py3-none-any.whl → 1.0.5.dev3__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.
@@ -14,17 +14,84 @@ def network_file_system_mount_protos(
14
14
  ) -> list[modal_proto.api_pb2.SharedVolumeMount]: ...
15
15
 
16
16
  class _NetworkFileSystem(modal._object._Object):
17
+ """A shared, writable file system accessible by one or more Modal functions.
18
+
19
+ By attaching this file system as a mount to one or more functions, they can
20
+ share and persist data with each other.
21
+
22
+ **Usage**
23
+
24
+ ```python
25
+ import modal
26
+
27
+ nfs = modal.NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
28
+ app = modal.App()
29
+
30
+ @app.function(network_file_systems={"/root/foo": nfs})
31
+ def f():
32
+ pass
33
+
34
+ @app.function(network_file_systems={"/root/goo": nfs})
35
+ def g():
36
+ pass
37
+ ```
38
+
39
+ Also see the CLI methods for accessing network file systems:
40
+
41
+ ```
42
+ modal nfs --help
43
+ ```
44
+
45
+ A `NetworkFileSystem` can also be useful for some local scripting scenarios, e.g.:
46
+
47
+ ```python notest
48
+ nfs = modal.NetworkFileSystem.from_name("my-network-file-system")
49
+ for chunk in nfs.read_file("my_db_dump.csv"):
50
+ ...
51
+ ```
52
+ """
17
53
  @staticmethod
18
54
  def from_name(
19
55
  name: str, *, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
20
- ) -> _NetworkFileSystem: ...
56
+ ) -> _NetworkFileSystem:
57
+ """Reference a NetworkFileSystem by its name, creating if necessary.
58
+
59
+ In contrast to `modal.NetworkFileSystem.lookup`, this is a lazy method
60
+ that defers hydrating the local object with metadata from Modal servers
61
+ until the first time it is actually used.
62
+
63
+ ```python notest
64
+ nfs = NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
65
+
66
+ @app.function(network_file_systems={"/data": nfs})
67
+ def f():
68
+ pass
69
+ ```
70
+ """
71
+ ...
72
+
21
73
  @classmethod
22
74
  def ephemeral(
23
75
  cls: type[_NetworkFileSystem],
24
76
  client: typing.Optional[modal.client._Client] = None,
25
77
  environment_name: typing.Optional[str] = None,
26
78
  _heartbeat_sleep: float = 300,
27
- ) -> typing.AsyncContextManager[_NetworkFileSystem]: ...
79
+ ) -> typing.AsyncContextManager[_NetworkFileSystem]:
80
+ """Creates a new ephemeral network filesystem within a context manager:
81
+
82
+ Usage:
83
+ ```python
84
+ with modal.NetworkFileSystem.ephemeral() as nfs:
85
+ assert nfs.listdir("/") == []
86
+ ```
87
+
88
+ ```python notest
89
+ async with modal.NetworkFileSystem.ephemeral() as nfs:
90
+ assert await nfs.listdir("/") == []
91
+ ```
92
+ """
93
+ ...
94
+
28
95
  @staticmethod
29
96
  async def lookup(
30
97
  name: str,
@@ -32,14 +99,32 @@ class _NetworkFileSystem(modal._object._Object):
32
99
  client: typing.Optional[modal.client._Client] = None,
33
100
  environment_name: typing.Optional[str] = None,
34
101
  create_if_missing: bool = False,
35
- ) -> _NetworkFileSystem: ...
102
+ ) -> _NetworkFileSystem:
103
+ """mdmd:hidden
104
+ Lookup a named NetworkFileSystem.
105
+
106
+ DEPRECATED: This method is deprecated in favor of `modal.NetworkFileSystem.from_name`.
107
+
108
+ In contrast to `modal.NetworkFileSystem.from_name`, this is an eager method
109
+ that will hydrate the local object with metadata from Modal servers.
110
+
111
+ ```python notest
112
+ nfs = modal.NetworkFileSystem.lookup("my-nfs")
113
+ print(nfs.listdir("/"))
114
+ ```
115
+ """
116
+ ...
117
+
36
118
  @staticmethod
37
119
  async def create_deployed(
38
120
  deployment_name: str,
39
121
  namespace=1,
40
122
  client: typing.Optional[modal.client._Client] = None,
41
123
  environment_name: typing.Optional[str] = None,
42
- ) -> str: ...
124
+ ) -> str:
125
+ """mdmd:hidden"""
126
+ ...
127
+
43
128
  @staticmethod
44
129
  async def delete(
45
130
  name: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
@@ -49,9 +134,30 @@ class _NetworkFileSystem(modal._object._Object):
49
134
  remote_path: str,
50
135
  fp: typing.BinaryIO,
51
136
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
52
- ) -> int: ...
53
- def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]: ...
54
- def iterdir(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
137
+ ) -> int:
138
+ """Write from a file object to a path on the network file system, atomically.
139
+
140
+ Will create any needed parent directories automatically.
141
+
142
+ If remote_path ends with `/` it's assumed to be a directory and the
143
+ file will be uploaded with its current name to that directory.
144
+ """
145
+ ...
146
+
147
+ def read_file(self, path: str) -> collections.abc.AsyncIterator[bytes]:
148
+ """Read a file from the network file system"""
149
+ ...
150
+
151
+ def iterdir(self, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]:
152
+ """Iterate over all files in a directory in the network file system.
153
+
154
+ * Passing a directory path lists all files in the directory (names are relative to the directory)
155
+ * Passing a file path returns a list containing only that file's listing description
156
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
157
+ that glob path (using absolute paths)
158
+ """
159
+ ...
160
+
55
161
  async def add_local_file(
56
162
  self,
57
163
  local_path: typing.Union[pathlib.Path, str],
@@ -64,24 +170,104 @@ class _NetworkFileSystem(modal._object._Object):
64
170
  remote_path: typing.Union[str, pathlib.PurePosixPath, None] = None,
65
171
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
66
172
  ): ...
67
- async def listdir(self, path: str) -> list[modal.volume.FileEntry]: ...
68
- async def remove_file(self, path: str, recursive=False): ...
173
+ async def listdir(self, path: str) -> list[modal.volume.FileEntry]:
174
+ """List all files in a directory in the network file system.
175
+
176
+ * Passing a directory path lists all files in the directory (names are relative to the directory)
177
+ * Passing a file path returns a list containing only that file's listing description
178
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
179
+ that glob path (using absolute paths)
180
+ """
181
+ ...
182
+
183
+ async def remove_file(self, path: str, recursive=False):
184
+ """Remove a file in a network file system."""
185
+ ...
69
186
 
70
187
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
71
188
 
72
189
  class NetworkFileSystem(modal.object.Object):
73
- def __init__(self, *args, **kwargs): ...
190
+ """A shared, writable file system accessible by one or more Modal functions.
191
+
192
+ By attaching this file system as a mount to one or more functions, they can
193
+ share and persist data with each other.
194
+
195
+ **Usage**
196
+
197
+ ```python
198
+ import modal
199
+
200
+ nfs = modal.NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
201
+ app = modal.App()
202
+
203
+ @app.function(network_file_systems={"/root/foo": nfs})
204
+ def f():
205
+ pass
206
+
207
+ @app.function(network_file_systems={"/root/goo": nfs})
208
+ def g():
209
+ pass
210
+ ```
211
+
212
+ Also see the CLI methods for accessing network file systems:
213
+
214
+ ```
215
+ modal nfs --help
216
+ ```
217
+
218
+ A `NetworkFileSystem` can also be useful for some local scripting scenarios, e.g.:
219
+
220
+ ```python notest
221
+ nfs = modal.NetworkFileSystem.from_name("my-network-file-system")
222
+ for chunk in nfs.read_file("my_db_dump.csv"):
223
+ ...
224
+ ```
225
+ """
226
+ def __init__(self, *args, **kwargs):
227
+ """mdmd:hidden"""
228
+ ...
229
+
74
230
  @staticmethod
75
231
  def from_name(
76
232
  name: str, *, namespace=1, environment_name: typing.Optional[str] = None, create_if_missing: bool = False
77
- ) -> NetworkFileSystem: ...
233
+ ) -> NetworkFileSystem:
234
+ """Reference a NetworkFileSystem by its name, creating if necessary.
235
+
236
+ In contrast to `modal.NetworkFileSystem.lookup`, this is a lazy method
237
+ that defers hydrating the local object with metadata from Modal servers
238
+ until the first time it is actually used.
239
+
240
+ ```python notest
241
+ nfs = NetworkFileSystem.from_name("my-nfs", create_if_missing=True)
242
+
243
+ @app.function(network_file_systems={"/data": nfs})
244
+ def f():
245
+ pass
246
+ ```
247
+ """
248
+ ...
249
+
78
250
  @classmethod
79
251
  def ephemeral(
80
252
  cls: type[NetworkFileSystem],
81
253
  client: typing.Optional[modal.client.Client] = None,
82
254
  environment_name: typing.Optional[str] = None,
83
255
  _heartbeat_sleep: float = 300,
84
- ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[NetworkFileSystem]: ...
256
+ ) -> synchronicity.combined_types.AsyncAndBlockingContextManager[NetworkFileSystem]:
257
+ """Creates a new ephemeral network filesystem within a context manager:
258
+
259
+ Usage:
260
+ ```python
261
+ with modal.NetworkFileSystem.ephemeral() as nfs:
262
+ assert nfs.listdir("/") == []
263
+ ```
264
+
265
+ ```python notest
266
+ async with modal.NetworkFileSystem.ephemeral() as nfs:
267
+ assert await nfs.listdir("/") == []
268
+ ```
269
+ """
270
+ ...
85
271
 
86
272
  class __lookup_spec(typing_extensions.Protocol):
87
273
  def __call__(
@@ -92,7 +278,22 @@ class NetworkFileSystem(modal.object.Object):
92
278
  client: typing.Optional[modal.client.Client] = None,
93
279
  environment_name: typing.Optional[str] = None,
94
280
  create_if_missing: bool = False,
95
- ) -> NetworkFileSystem: ...
281
+ ) -> NetworkFileSystem:
282
+ """mdmd:hidden
283
+ Lookup a named NetworkFileSystem.
284
+
285
+ DEPRECATED: This method is deprecated in favor of `modal.NetworkFileSystem.from_name`.
286
+
287
+ In contrast to `modal.NetworkFileSystem.from_name`, this is an eager method
288
+ that will hydrate the local object with metadata from Modal servers.
289
+
290
+ ```python notest
291
+ nfs = modal.NetworkFileSystem.lookup("my-nfs")
292
+ print(nfs.listdir("/"))
293
+ ```
294
+ """
295
+ ...
296
+
96
297
  async def aio(
97
298
  self,
98
299
  /,
@@ -101,7 +302,21 @@ class NetworkFileSystem(modal.object.Object):
101
302
  client: typing.Optional[modal.client.Client] = None,
102
303
  environment_name: typing.Optional[str] = None,
103
304
  create_if_missing: bool = False,
104
- ) -> NetworkFileSystem: ...
305
+ ) -> NetworkFileSystem:
306
+ """mdmd:hidden
307
+ Lookup a named NetworkFileSystem.
308
+
309
+ DEPRECATED: This method is deprecated in favor of `modal.NetworkFileSystem.from_name`.
310
+
311
+ In contrast to `modal.NetworkFileSystem.from_name`, this is an eager method
312
+ that will hydrate the local object with metadata from Modal servers.
313
+
314
+ ```python notest
315
+ nfs = modal.NetworkFileSystem.lookup("my-nfs")
316
+ print(nfs.listdir("/"))
317
+ ```
318
+ """
319
+ ...
105
320
 
106
321
  lookup: __lookup_spec
107
322
 
@@ -113,7 +328,10 @@ class NetworkFileSystem(modal.object.Object):
113
328
  namespace=1,
114
329
  client: typing.Optional[modal.client.Client] = None,
115
330
  environment_name: typing.Optional[str] = None,
116
- ) -> str: ...
331
+ ) -> str:
332
+ """mdmd:hidden"""
333
+ ...
334
+
117
335
  async def aio(
118
336
  self,
119
337
  /,
@@ -121,7 +339,9 @@ class NetworkFileSystem(modal.object.Object):
121
339
  namespace=1,
122
340
  client: typing.Optional[modal.client.Client] = None,
123
341
  environment_name: typing.Optional[str] = None,
124
- ) -> str: ...
342
+ ) -> str:
343
+ """mdmd:hidden"""
344
+ ...
125
345
 
126
346
  create_deployed: __create_deployed_spec
127
347
 
@@ -150,26 +370,65 @@ class NetworkFileSystem(modal.object.Object):
150
370
  remote_path: str,
151
371
  fp: typing.BinaryIO,
152
372
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
153
- ) -> int: ...
373
+ ) -> int:
374
+ """Write from a file object to a path on the network file system, atomically.
375
+
376
+ Will create any needed parent directories automatically.
377
+
378
+ If remote_path ends with `/` it's assumed to be a directory and the
379
+ file will be uploaded with its current name to that directory.
380
+ """
381
+ ...
382
+
154
383
  async def aio(
155
384
  self,
156
385
  /,
157
386
  remote_path: str,
158
387
  fp: typing.BinaryIO,
159
388
  progress_cb: typing.Optional[collections.abc.Callable[..., typing.Any]] = None,
160
- ) -> int: ...
389
+ ) -> int:
390
+ """Write from a file object to a path on the network file system, atomically.
391
+
392
+ Will create any needed parent directories automatically.
393
+
394
+ If remote_path ends with `/` it's assumed to be a directory and the
395
+ file will be uploaded with its current name to that directory.
396
+ """
397
+ ...
161
398
 
162
399
  write_file: __write_file_spec[typing_extensions.Self]
163
400
 
164
401
  class __read_file_spec(typing_extensions.Protocol[SUPERSELF]):
165
- def __call__(self, /, path: str) -> typing.Iterator[bytes]: ...
166
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]: ...
402
+ def __call__(self, /, path: str) -> typing.Iterator[bytes]:
403
+ """Read a file from the network file system"""
404
+ ...
405
+
406
+ def aio(self, /, path: str) -> collections.abc.AsyncIterator[bytes]:
407
+ """Read a file from the network file system"""
408
+ ...
167
409
 
168
410
  read_file: __read_file_spec[typing_extensions.Self]
169
411
 
170
412
  class __iterdir_spec(typing_extensions.Protocol[SUPERSELF]):
171
- def __call__(self, /, path: str) -> typing.Iterator[modal.volume.FileEntry]: ...
172
- def aio(self, /, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]: ...
413
+ def __call__(self, /, path: str) -> typing.Iterator[modal.volume.FileEntry]:
414
+ """Iterate over all files in a directory in the network file system.
415
+
416
+ * Passing a directory path lists all files in the directory (names are relative to the directory)
417
+ * Passing a file path returns a list containing only that file's listing description
418
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
419
+ that glob path (using absolute paths)
420
+ """
421
+ ...
422
+
423
+ def aio(self, /, path: str) -> collections.abc.AsyncIterator[modal.volume.FileEntry]:
424
+ """Iterate over all files in a directory in the network file system.
425
+
426
+ * Passing a directory path lists all files in the directory (names are relative to the directory)
427
+ * Passing a file path returns a list containing only that file's listing description
428
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
429
+ that glob path (using absolute paths)
430
+ """
431
+ ...
173
432
 
174
433
  iterdir: __iterdir_spec[typing_extensions.Self]
175
434
 
@@ -210,13 +469,35 @@ class NetworkFileSystem(modal.object.Object):
210
469
  add_local_dir: __add_local_dir_spec[typing_extensions.Self]
211
470
 
212
471
  class __listdir_spec(typing_extensions.Protocol[SUPERSELF]):
213
- def __call__(self, /, path: str) -> list[modal.volume.FileEntry]: ...
214
- async def aio(self, /, path: str) -> list[modal.volume.FileEntry]: ...
472
+ def __call__(self, /, path: str) -> list[modal.volume.FileEntry]:
473
+ """List all files in a directory in the network file system.
474
+
475
+ * Passing a directory path lists all files in the directory (names are relative to the directory)
476
+ * Passing a file path returns a list containing only that file's listing description
477
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
478
+ that glob path (using absolute paths)
479
+ """
480
+ ...
481
+
482
+ async def aio(self, /, path: str) -> list[modal.volume.FileEntry]:
483
+ """List all files in a directory in the network file system.
484
+
485
+ * Passing a directory path lists all files in the directory (names are relative to the directory)
486
+ * Passing a file path returns a list containing only that file's listing description
487
+ * Passing a glob path (including at least one * or ** sequence) returns all files matching
488
+ that glob path (using absolute paths)
489
+ """
490
+ ...
215
491
 
216
492
  listdir: __listdir_spec[typing_extensions.Self]
217
493
 
218
494
  class __remove_file_spec(typing_extensions.Protocol[SUPERSELF]):
219
- def __call__(self, /, path: str, recursive=False): ...
220
- async def aio(self, /, path: str, recursive=False): ...
495
+ def __call__(self, /, path: str, recursive=False):
496
+ """Remove a file in a network file system."""
497
+ ...
498
+
499
+ async def aio(self, /, path: str, recursive=False):
500
+ """Remove a file in a network file system."""
501
+ ...
221
502
 
222
503
  remove_file: __remove_file_spec[typing_extensions.Self]
modal/object.pyi CHANGED
@@ -32,7 +32,10 @@ class Object:
32
32
  _is_hydrated: bool
33
33
  _is_rehydrated: bool
34
34
 
35
- def __init__(self, *args, **kwargs): ...
35
+ def __init__(self, *args, **kwargs):
36
+ """mdmd:hidden"""
37
+ ...
38
+
36
39
  @classmethod
37
40
  def __init_subclass__(cls, type_prefix: typing.Optional[str] = None): ...
38
41
 
@@ -85,7 +88,10 @@ class Object:
85
88
  def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
86
89
  def _get_metadata(self) -> typing.Optional[google.protobuf.message.Message]: ...
87
90
  def _validate_is_hydrated(self): ...
88
- def clone(self) -> typing_extensions.Self: ...
91
+ def clone(self) -> typing_extensions.Self:
92
+ """mdmd:hidden Clone a given hydrated object."""
93
+ ...
94
+
89
95
  @classmethod
90
96
  def _from_loader(
91
97
  cls,
@@ -114,18 +120,51 @@ class Object:
114
120
  def _hydrate_from_other(self, other: typing_extensions.Self): ...
115
121
  def __repr__(self): ...
116
122
  @property
117
- def local_uuid(self): ...
123
+ def local_uuid(self):
124
+ """mdmd:hidden"""
125
+ ...
126
+
118
127
  @property
119
- def object_id(self) -> str: ...
128
+ def object_id(self) -> str:
129
+ """mdmd:hidden"""
130
+ ...
131
+
120
132
  @property
121
- def client(self) -> modal.client.Client: ...
133
+ def client(self) -> modal.client.Client:
134
+ """mdmd:hidden"""
135
+ ...
136
+
122
137
  @property
123
- def is_hydrated(self) -> bool: ...
138
+ def is_hydrated(self) -> bool:
139
+ """mdmd:hidden"""
140
+ ...
141
+
124
142
  @property
125
- def deps(self) -> collections.abc.Callable[..., collections.abc.Sequence[Object]]: ...
143
+ def deps(self) -> collections.abc.Callable[..., collections.abc.Sequence[Object]]:
144
+ """mdmd:hidden"""
145
+ ...
126
146
 
127
147
  class __hydrate_spec(typing_extensions.Protocol[SUPERSELF]):
128
- def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF: ...
129
- async def aio(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF: ...
148
+ def __call__(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF:
149
+ """Synchronize the local object with its identity on the Modal server.
150
+
151
+ It is rarely necessary to call this method explicitly, as most operations
152
+ will lazily hydrate when needed. The main use case is when you need to
153
+ access object metadata, such as its ID.
154
+
155
+ *Added in v0.72.39*: This method replaces the deprecated `.resolve()` method.
156
+ """
157
+ ...
158
+
159
+ async def aio(self, /, client: typing.Optional[modal.client.Client] = None) -> SUPERSELF:
160
+ """Synchronize the local object with its identity on the Modal server.
161
+
162
+ It is rarely necessary to call this method explicitly, as most operations
163
+ will lazily hydrate when needed. The main use case is when you need to
164
+ access object metadata, such as its ID.
165
+
166
+ *Added in v0.72.39*: This method replaces the deprecated `.resolve()` method.
167
+ """
168
+ ...
130
169
 
131
170
  hydrate: __hydrate_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
  ):
@@ -341,7 +343,13 @@ async def _map_invocation(
341
343
  output = await _process_result(item.result, item.data_format, client.stub, client)
342
344
  except Exception as e:
343
345
  if return_exceptions:
344
- 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
345
353
  else:
346
354
  raise e
347
355
  return (item.idx, output)
@@ -413,6 +421,7 @@ async def _map_helper(
413
421
  kwargs={}, # any extra keyword arguments for the function
414
422
  order_outputs: bool = True, # return outputs in order
415
423
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
424
+ wrap_returned_exceptions: bool = True,
416
425
  ) -> typing.AsyncGenerator[Any, None]:
417
426
  """Core implementation that supports `_map_async()`, `_starmap_async()` and `_for_each_async()`.
418
427
 
@@ -443,7 +452,9 @@ async def _map_helper(
443
452
  # synchronicity-wrapped, since they accept executable code in the form of iterators that we don't want to run inside
444
453
  # the synchronicity thread. Instead, we delegate to `._map()` with a safer Queue as input.
445
454
  async with aclosing(
446
- 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
+ )
447
458
  ) as map_output_stream:
448
459
  async for output in map_output_stream:
449
460
  yield output
@@ -458,10 +469,16 @@ async def _map_async(
458
469
  kwargs={}, # any extra keyword arguments for the function
459
470
  order_outputs: bool = True, # return outputs in order
460
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
461
473
  ) -> typing.AsyncGenerator[Any, None]:
462
474
  async_input_gen = async_zip(*[sync_or_async_iter(it) for it in input_iterators])
463
475
  async for output in _map_helper(
464
- 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,
465
482
  ):
466
483
  yield output
467
484
 
@@ -474,6 +491,7 @@ async def _starmap_async(
474
491
  kwargs={},
475
492
  order_outputs: bool = True,
476
493
  return_exceptions: bool = False,
494
+ wrap_returned_exceptions: bool = True,
477
495
  ) -> typing.AsyncIterable[Any]:
478
496
  async for output in _map_helper(
479
497
  self,
@@ -481,6 +499,7 @@ async def _starmap_async(
481
499
  kwargs=kwargs,
482
500
  order_outputs=order_outputs,
483
501
  return_exceptions=return_exceptions,
502
+ wrap_returned_exceptions=wrap_returned_exceptions,
484
503
  ):
485
504
  yield output
486
505
 
@@ -502,6 +521,7 @@ def _map_sync(
502
521
  kwargs={}, # any extra keyword arguments for the function
503
522
  order_outputs: bool = True, # return outputs in order
504
523
  return_exceptions: bool = False, # propagate exceptions (False) or aggregate them in the results list (True)
524
+ wrap_returned_exceptions: bool = True,
505
525
  ) -> AsyncOrSyncIterable:
506
526
  """Parallel map over a set of inputs.
507
527
 
@@ -542,7 +562,12 @@ def _map_sync(
542
562
 
543
563
  return AsyncOrSyncIterable(
544
564
  _map_async(
545
- 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,
546
571
  ),
547
572
  nested_async_message=(
548
573
  "You can't iter(Function.map()) from an async function. Use async for ... in Function.map.aio() instead."
@@ -622,6 +647,7 @@ def _starmap_sync(
622
647
  kwargs={},
623
648
  order_outputs: bool = True,
624
649
  return_exceptions: bool = False,
650
+ wrap_returned_exceptions: bool = True,
625
651
  ) -> AsyncOrSyncIterable:
626
652
  """Like `map`, but spreads arguments over multiple function arguments.
627
653
 
@@ -641,7 +667,12 @@ def _starmap_sync(
641
667
  """
642
668
  return AsyncOrSyncIterable(
643
669
  _starmap_async(
644
- 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,
645
676
  ),
646
677
  nested_async_message=(
647
678
  "You can't `iter(Function.starmap())` from an async function. "
@@ -764,11 +795,12 @@ class _MapItemContext:
764
795
  delay_ms = 0
765
796
 
766
797
  # None means the maximum number of retries has been reached, so output the error
767
- if delay_ms is None:
798
+ if delay_ms is None or item.result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
768
799
  self.state = _MapItemState.COMPLETE
769
800
  return _OutputType.FAILED_COMPLETION
770
801
 
771
802
  self.state = _MapItemState.WAITING_TO_RETRY
803
+
772
804
  await retry_queue.put(now_seconds + (delay_ms / 1000), item.idx)
773
805
 
774
806
  return _OutputType.RETRYING