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 +44 -35
- modal/_utils/async_utils.py +1 -1
- modal/_utils/function_utils.py +3 -3
- modal/client.pyi +2 -2
- modal/exception.py +4 -0
- modal/functions.pyi +28 -9
- modal/parallel_map.py +51 -16
- modal/parallel_map.pyi +11 -1
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dev2.dist-info}/METADATA +2 -2
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dev2.dist-info}/RECORD +15 -15
- modal_version/__init__.py +1 -1
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dev2.dist-info}/WHEEL +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dev2.dist-info}/entry_points.txt +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dev2.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dev2.dist-info}/top_level.txt +0 -0
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) ->
|
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
|
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
|
-
|
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
|
-
|
288
|
-
|
289
|
-
|
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
|
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
|
-
|
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
|
-
|
403
|
-
if await_response.
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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,
|
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
|
)
|
modal/_utils/async_utils.py
CHANGED
@@ -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):
|
modal/_utils/function_utils.py
CHANGED
@@ -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
|
-
|
501
|
-
raise
|
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.
|
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.
|
97
|
+
version: str = "1.0.5.dev2",
|
98
98
|
): ...
|
99
99
|
def is_closed(self) -> bool: ...
|
100
100
|
@property
|
modal/exception.py
CHANGED
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,
|
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,
|
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[
|
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.
|
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[
|
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.
|
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[
|
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.
|
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,
|
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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
-
|
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
|
-
|
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(
|
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,
|
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,
|
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,
|
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,
|
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.
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=
|
56
|
-
modal/parallel_map.pyi,sha256=
|
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=
|
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=
|
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.
|
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=
|
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.
|
176
|
-
modal-1.0.
|
177
|
-
modal-1.0.
|
178
|
-
modal-1.0.
|
179
|
-
modal-1.0.
|
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
File without changes
|
File without changes
|
File without changes
|
File without changes
|