modal 1.1.2.dev6__py3-none-any.whl → 1.1.2.dev8__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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/_functions.py +19 -0
- modal/client.pyi +2 -2
- modal/functions.pyi +33 -0
- modal/parallel_map.py +333 -101
- modal/parallel_map.pyi +105 -0
- {modal-1.1.2.dev6.dist-info → modal-1.1.2.dev8.dist-info}/METADATA +1 -1
- {modal-1.1.2.dev6.dist-info → modal-1.1.2.dev8.dist-info}/RECORD +12 -12
- modal_version/__init__.py +1 -1
- {modal-1.1.2.dev6.dist-info → modal-1.1.2.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.2.dev6.dist-info → modal-1.1.2.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.2.dev6.dist-info → modal-1.1.2.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.2.dev6.dist-info → modal-1.1.2.dev8.dist-info}/top_level.txt +0 -0
modal/_functions.py
CHANGED
|
@@ -71,6 +71,8 @@ from .mount import _get_client_mount, _Mount
|
|
|
71
71
|
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos
|
|
72
72
|
from .output import _get_output_manager
|
|
73
73
|
from .parallel_map import (
|
|
74
|
+
_experimental_spawn_map_async,
|
|
75
|
+
_experimental_spawn_map_sync,
|
|
74
76
|
_for_each_async,
|
|
75
77
|
_for_each_sync,
|
|
76
78
|
_map_async,
|
|
@@ -78,6 +80,7 @@ from .parallel_map import (
|
|
|
78
80
|
_map_invocation_inputplane,
|
|
79
81
|
_map_sync,
|
|
80
82
|
_spawn_map_async,
|
|
83
|
+
_spawn_map_invocation,
|
|
81
84
|
_spawn_map_sync,
|
|
82
85
|
_starmap_async,
|
|
83
86
|
_starmap_sync,
|
|
@@ -1543,6 +1546,21 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1543
1546
|
async for item in stream:
|
|
1544
1547
|
yield item
|
|
1545
1548
|
|
|
1549
|
+
@live_method
|
|
1550
|
+
async def _spawn_map(self, input_queue: _SynchronizedQueue) -> "_FunctionCall[ReturnType]":
|
|
1551
|
+
self._check_no_web_url("spawn_map")
|
|
1552
|
+
if self._is_generator:
|
|
1553
|
+
raise InvalidError("A generator function cannot be called with `.spawn_map(...)`.")
|
|
1554
|
+
|
|
1555
|
+
assert self._function_name
|
|
1556
|
+
function_call_id = await _spawn_map_invocation(
|
|
1557
|
+
self,
|
|
1558
|
+
input_queue,
|
|
1559
|
+
self.client,
|
|
1560
|
+
)
|
|
1561
|
+
fc: _FunctionCall[ReturnType] = _FunctionCall._new_hydrated(function_call_id, self.client, None)
|
|
1562
|
+
return fc
|
|
1563
|
+
|
|
1546
1564
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
|
1547
1565
|
invocation: Union[_Invocation, _InputPlaneInvocation]
|
|
1548
1566
|
if self._input_plane_url:
|
|
@@ -1789,6 +1807,7 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1789
1807
|
starmap = MethodWithAio(_starmap_sync, _starmap_async, synchronizer)
|
|
1790
1808
|
for_each = MethodWithAio(_for_each_sync, _for_each_async, synchronizer)
|
|
1791
1809
|
spawn_map = MethodWithAio(_spawn_map_sync, _spawn_map_async, synchronizer)
|
|
1810
|
+
experimental_spawn_map = MethodWithAio(_experimental_spawn_map_sync, _experimental_spawn_map_async, synchronizer)
|
|
1792
1811
|
|
|
1793
1812
|
|
|
1794
1813
|
class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
modal/client.pyi
CHANGED
|
@@ -33,7 +33,7 @@ class _Client:
|
|
|
33
33
|
server_url: str,
|
|
34
34
|
client_type: int,
|
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
-
version: str = "1.1.2.
|
|
36
|
+
version: str = "1.1.2.dev8",
|
|
37
37
|
):
|
|
38
38
|
"""mdmd:hidden
|
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -164,7 +164,7 @@ class Client:
|
|
|
164
164
|
server_url: str,
|
|
165
165
|
client_type: int,
|
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
-
version: str = "1.1.2.
|
|
167
|
+
version: str = "1.1.2.dev8",
|
|
168
168
|
):
|
|
169
169
|
"""mdmd:hidden
|
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
modal/functions.pyi
CHANGED
|
@@ -405,6 +405,12 @@ class Function(
|
|
|
405
405
|
|
|
406
406
|
_map: ___map_spec[typing_extensions.Self]
|
|
407
407
|
|
|
408
|
+
class ___spawn_map_spec(typing_extensions.Protocol[ReturnType_INNER, SUPERSELF]):
|
|
409
|
+
def __call__(self, /, input_queue: modal.parallel_map.SynchronizedQueue) -> FunctionCall[ReturnType_INNER]: ...
|
|
410
|
+
async def aio(self, /, input_queue: modal.parallel_map.SynchronizedQueue) -> FunctionCall[ReturnType_INNER]: ...
|
|
411
|
+
|
|
412
|
+
_spawn_map: ___spawn_map_spec[modal._functions.ReturnType, typing_extensions.Self]
|
|
413
|
+
|
|
408
414
|
class ___call_function_spec(typing_extensions.Protocol[ReturnType_INNER, SUPERSELF]):
|
|
409
415
|
def __call__(self, /, args, kwargs) -> ReturnType_INNER: ...
|
|
410
416
|
async def aio(self, /, args, kwargs) -> ReturnType_INNER: ...
|
|
@@ -693,6 +699,33 @@ class Function(
|
|
|
693
699
|
|
|
694
700
|
spawn_map: __spawn_map_spec[typing_extensions.Self]
|
|
695
701
|
|
|
702
|
+
class __experimental_spawn_map_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
703
|
+
def __call__(self, /, *input_iterators, kwargs={}) -> modal._functions._FunctionCall:
|
|
704
|
+
"""Spawn parallel execution over a set of inputs, exiting as soon as the inputs are created (without waiting
|
|
705
|
+
for the map to complete).
|
|
706
|
+
|
|
707
|
+
Takes one iterator argument per argument in the function being mapped over.
|
|
708
|
+
|
|
709
|
+
Example:
|
|
710
|
+
```python
|
|
711
|
+
@app.function()
|
|
712
|
+
def my_func(a):
|
|
713
|
+
return a ** 2
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@app.local_entrypoint()
|
|
717
|
+
def main():
|
|
718
|
+
fc = my_func.spawn_map([1, 2, 3, 4])
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Returns a FunctionCall object that can be used to retrieve results
|
|
722
|
+
"""
|
|
723
|
+
...
|
|
724
|
+
|
|
725
|
+
async def aio(self, /, *input_iterators, kwargs={}) -> modal._functions._FunctionCall: ...
|
|
726
|
+
|
|
727
|
+
experimental_spawn_map: __experimental_spawn_map_spec[typing_extensions.Self]
|
|
728
|
+
|
|
696
729
|
class FunctionCall(typing.Generic[modal._functions.ReturnType], modal.object.Object):
|
|
697
730
|
"""A reference to an executed function call.
|
|
698
731
|
|
modal/parallel_map.py
CHANGED
|
@@ -86,6 +86,263 @@ if typing.TYPE_CHECKING:
|
|
|
86
86
|
import modal.functions
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
class InputPreprocessor:
|
|
90
|
+
"""
|
|
91
|
+
Constructs FunctionPutInputsItem objects from the raw-input queue, and puts them in the processed-input queue.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
client: "modal.client._Client",
|
|
97
|
+
*,
|
|
98
|
+
raw_input_queue: _SynchronizedQueue,
|
|
99
|
+
processed_input_queue: asyncio.Queue,
|
|
100
|
+
function: "modal.functions._Function",
|
|
101
|
+
created_callback: Callable[[int], None],
|
|
102
|
+
done_callback: Callable[[], None],
|
|
103
|
+
):
|
|
104
|
+
self.client = client
|
|
105
|
+
self.function = function
|
|
106
|
+
self.inputs_created = 0
|
|
107
|
+
self.raw_input_queue = raw_input_queue
|
|
108
|
+
self.processed_input_queue = processed_input_queue
|
|
109
|
+
self.created_callback = created_callback
|
|
110
|
+
self.done_callback = done_callback
|
|
111
|
+
|
|
112
|
+
async def input_iter(self):
|
|
113
|
+
while 1:
|
|
114
|
+
raw_input = await self.raw_input_queue.get()
|
|
115
|
+
if raw_input is None: # end of input sentinel
|
|
116
|
+
break
|
|
117
|
+
yield raw_input # args, kwargs
|
|
118
|
+
|
|
119
|
+
def create_input_factory(self):
|
|
120
|
+
async def create_input(argskwargs):
|
|
121
|
+
idx = self.inputs_created
|
|
122
|
+
self.inputs_created += 1
|
|
123
|
+
self.created_callback(self.inputs_created)
|
|
124
|
+
(args, kwargs) = argskwargs
|
|
125
|
+
return await _create_input(
|
|
126
|
+
args,
|
|
127
|
+
kwargs,
|
|
128
|
+
self.client.stub,
|
|
129
|
+
max_object_size_bytes=self.function._max_object_size_bytes,
|
|
130
|
+
idx=idx,
|
|
131
|
+
method_name=self.function._use_method_name,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return create_input
|
|
135
|
+
|
|
136
|
+
async def drain_input_generator(self):
|
|
137
|
+
# Parallelize uploading blobs
|
|
138
|
+
async with aclosing(
|
|
139
|
+
async_map_ordered(self.input_iter(), self.create_input_factory(), concurrency=BLOB_MAX_PARALLELISM)
|
|
140
|
+
) as streamer:
|
|
141
|
+
async for item in streamer:
|
|
142
|
+
await self.processed_input_queue.put(item)
|
|
143
|
+
|
|
144
|
+
# close queue iterator
|
|
145
|
+
await self.processed_input_queue.put(None)
|
|
146
|
+
self.done_callback()
|
|
147
|
+
yield
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class InputPumper:
|
|
151
|
+
"""
|
|
152
|
+
Reads inputs from a queue of FunctionPutInputsItems, and sends them to the server.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
client: "modal.client._Client",
|
|
158
|
+
*,
|
|
159
|
+
input_queue: asyncio.Queue,
|
|
160
|
+
function: "modal.functions._Function",
|
|
161
|
+
function_call_id: str,
|
|
162
|
+
map_items_manager: Optional["_MapItemsManager"] = None,
|
|
163
|
+
):
|
|
164
|
+
self.client = client
|
|
165
|
+
self.function = function
|
|
166
|
+
self.map_items_manager = map_items_manager
|
|
167
|
+
self.input_queue = input_queue
|
|
168
|
+
self.inputs_sent = 0
|
|
169
|
+
self.function_call_id = function_call_id
|
|
170
|
+
|
|
171
|
+
async def pump_inputs(self):
|
|
172
|
+
assert self.client.stub
|
|
173
|
+
async for items in queue_batch_iterator(self.input_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
|
|
174
|
+
# Add items to the manager. Their state will be SENDING.
|
|
175
|
+
if self.map_items_manager is not None:
|
|
176
|
+
await self.map_items_manager.add_items(items)
|
|
177
|
+
request = api_pb2.FunctionPutInputsRequest(
|
|
178
|
+
function_id=self.function.object_id,
|
|
179
|
+
inputs=items,
|
|
180
|
+
function_call_id=self.function_call_id,
|
|
181
|
+
)
|
|
182
|
+
logger.debug(
|
|
183
|
+
f"Pushing {len(items)} inputs to server. Num queued inputs awaiting"
|
|
184
|
+
f" push is {self.input_queue.qsize()}. "
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
resp = await self._send_inputs(self.client.stub.FunctionPutInputs, request)
|
|
188
|
+
self.inputs_sent += len(items)
|
|
189
|
+
# Change item state to WAITING_FOR_OUTPUT, and set the input_id and input_jwt which are in the response.
|
|
190
|
+
if self.map_items_manager is not None:
|
|
191
|
+
self.map_items_manager.handle_put_inputs_response(resp.inputs)
|
|
192
|
+
logger.debug(
|
|
193
|
+
f"Successfully pushed {len(items)} inputs to server. "
|
|
194
|
+
f"Num queued inputs awaiting push is {self.input_queue.qsize()}."
|
|
195
|
+
)
|
|
196
|
+
yield
|
|
197
|
+
|
|
198
|
+
async def _send_inputs(
|
|
199
|
+
self,
|
|
200
|
+
fn: "modal.client.UnaryUnaryWrapper",
|
|
201
|
+
request: typing.Union[api_pb2.FunctionPutInputsRequest, api_pb2.FunctionRetryInputsRequest],
|
|
202
|
+
) -> typing.Union[api_pb2.FunctionPutInputsResponse, api_pb2.FunctionRetryInputsResponse]:
|
|
203
|
+
# with 8 retries we log the warning below about every 30 seconds which isn't too spammy.
|
|
204
|
+
retry_warning_message = RetryWarningMessage(
|
|
205
|
+
message=f"Warning: map progress for function {self.function._function_name} is limited."
|
|
206
|
+
" Common bottlenecks include slow iteration over results, or function backlogs.",
|
|
207
|
+
warning_interval=8,
|
|
208
|
+
errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
|
|
209
|
+
)
|
|
210
|
+
return await retry_transient_errors(
|
|
211
|
+
fn,
|
|
212
|
+
request,
|
|
213
|
+
max_retries=None,
|
|
214
|
+
max_delay=PUMP_INPUTS_MAX_RETRY_DELAY,
|
|
215
|
+
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
216
|
+
retry_warning_message=retry_warning_message,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class SyncInputPumper(InputPumper):
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
client: "modal.client._Client",
|
|
224
|
+
*,
|
|
225
|
+
input_queue: asyncio.Queue,
|
|
226
|
+
retry_queue: TimestampPriorityQueue,
|
|
227
|
+
function: "modal.functions._Function",
|
|
228
|
+
function_call_jwt: str,
|
|
229
|
+
function_call_id: str,
|
|
230
|
+
map_items_manager: "_MapItemsManager",
|
|
231
|
+
):
|
|
232
|
+
super().__init__(
|
|
233
|
+
client,
|
|
234
|
+
input_queue=input_queue,
|
|
235
|
+
function=function,
|
|
236
|
+
function_call_id=function_call_id,
|
|
237
|
+
map_items_manager=map_items_manager,
|
|
238
|
+
)
|
|
239
|
+
self.retry_queue = retry_queue
|
|
240
|
+
self.inputs_retried = 0
|
|
241
|
+
self.function_call_jwt = function_call_jwt
|
|
242
|
+
|
|
243
|
+
async def retry_inputs(self):
|
|
244
|
+
async for retriable_idxs in queue_batch_iterator(self.retry_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
|
|
245
|
+
# For each index, use the context in the manager to create a FunctionRetryInputsItem.
|
|
246
|
+
# This will also update the context state to RETRYING.
|
|
247
|
+
inputs: list[api_pb2.FunctionRetryInputsItem] = await self.map_items_manager.prepare_items_for_retry(
|
|
248
|
+
retriable_idxs
|
|
249
|
+
)
|
|
250
|
+
request = api_pb2.FunctionRetryInputsRequest(
|
|
251
|
+
function_call_jwt=self.function_call_jwt,
|
|
252
|
+
inputs=inputs,
|
|
253
|
+
)
|
|
254
|
+
resp = await self._send_inputs(self.client.stub.FunctionRetryInputs, request)
|
|
255
|
+
# Update the state to WAITING_FOR_OUTPUT, and update the input_jwt in the context
|
|
256
|
+
# to the new value in the response.
|
|
257
|
+
self.map_items_manager.handle_retry_response(resp.input_jwts)
|
|
258
|
+
logger.debug(f"Successfully pushed retry for {len(inputs)} to server.")
|
|
259
|
+
self.inputs_retried += len(inputs)
|
|
260
|
+
yield
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class AsyncInputPumper(InputPumper):
|
|
264
|
+
def __init__(
|
|
265
|
+
self,
|
|
266
|
+
client: "modal.client._Client",
|
|
267
|
+
*,
|
|
268
|
+
input_queue: asyncio.Queue,
|
|
269
|
+
function: "modal.functions._Function",
|
|
270
|
+
function_call_id: str,
|
|
271
|
+
):
|
|
272
|
+
super().__init__(client, input_queue=input_queue, function=function, function_call_id=function_call_id)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def _spawn_map_invocation(
|
|
276
|
+
function: "modal.functions._Function", raw_input_queue: _SynchronizedQueue, client: "modal.client._Client"
|
|
277
|
+
) -> str:
|
|
278
|
+
assert client.stub
|
|
279
|
+
request = api_pb2.FunctionMapRequest(
|
|
280
|
+
function_id=function.object_id,
|
|
281
|
+
parent_input_id=current_input_id() or "",
|
|
282
|
+
function_call_type=api_pb2.FUNCTION_CALL_TYPE_MAP,
|
|
283
|
+
function_call_invocation_type=api_pb2.FUNCTION_CALL_INVOCATION_TYPE_ASYNC,
|
|
284
|
+
)
|
|
285
|
+
response: api_pb2.FunctionMapResponse = await retry_transient_errors(client.stub.FunctionMap, request)
|
|
286
|
+
function_call_id = response.function_call_id
|
|
287
|
+
|
|
288
|
+
have_all_inputs = False
|
|
289
|
+
inputs_created = 0
|
|
290
|
+
|
|
291
|
+
def set_inputs_created(set_inputs_created):
|
|
292
|
+
nonlocal inputs_created
|
|
293
|
+
assert set_inputs_created is None or set_inputs_created > inputs_created
|
|
294
|
+
inputs_created = set_inputs_created
|
|
295
|
+
|
|
296
|
+
def set_have_all_inputs():
|
|
297
|
+
nonlocal have_all_inputs
|
|
298
|
+
have_all_inputs = True
|
|
299
|
+
|
|
300
|
+
input_queue: asyncio.Queue[api_pb2.FunctionPutInputsItem | None] = asyncio.Queue()
|
|
301
|
+
input_preprocessor = InputPreprocessor(
|
|
302
|
+
client=client,
|
|
303
|
+
raw_input_queue=raw_input_queue,
|
|
304
|
+
processed_input_queue=input_queue,
|
|
305
|
+
function=function,
|
|
306
|
+
created_callback=set_inputs_created,
|
|
307
|
+
done_callback=set_have_all_inputs,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
input_pumper = AsyncInputPumper(
|
|
311
|
+
client=client,
|
|
312
|
+
input_queue=input_queue,
|
|
313
|
+
function=function,
|
|
314
|
+
function_call_id=function_call_id,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def log_stats():
|
|
318
|
+
logger.debug(
|
|
319
|
+
f"have_all_inputs={have_all_inputs} inputs_created={inputs_created} inputs_sent={input_pumper.inputs_sent} "
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
async def log_task():
|
|
323
|
+
while True:
|
|
324
|
+
log_stats()
|
|
325
|
+
try:
|
|
326
|
+
await asyncio.sleep(10)
|
|
327
|
+
except asyncio.CancelledError:
|
|
328
|
+
# Log final stats before exiting
|
|
329
|
+
log_stats()
|
|
330
|
+
break
|
|
331
|
+
|
|
332
|
+
async def consume_generator(gen):
|
|
333
|
+
async for _ in gen:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
log_debug_stats_task = asyncio.create_task(log_task())
|
|
337
|
+
await asyncio.gather(
|
|
338
|
+
consume_generator(input_preprocessor.drain_input_generator()),
|
|
339
|
+
consume_generator(input_pumper.pump_inputs()),
|
|
340
|
+
)
|
|
341
|
+
log_debug_stats_task.cancel()
|
|
342
|
+
await log_debug_stats_task
|
|
343
|
+
return function_call_id
|
|
344
|
+
|
|
345
|
+
|
|
89
346
|
async def _map_invocation(
|
|
90
347
|
function: "modal.functions._Function",
|
|
91
348
|
raw_input_queue: _SynchronizedQueue,
|
|
@@ -117,8 +374,6 @@ async def _map_invocation(
|
|
|
117
374
|
have_all_inputs = False
|
|
118
375
|
map_done_event = asyncio.Event()
|
|
119
376
|
inputs_created = 0
|
|
120
|
-
inputs_sent = 0
|
|
121
|
-
inputs_retried = 0
|
|
122
377
|
outputs_completed = 0
|
|
123
378
|
outputs_received = 0
|
|
124
379
|
retried_outputs = 0
|
|
@@ -135,25 +390,24 @@ async def _map_invocation(
|
|
|
135
390
|
retry_policy, function_call_invocation_type, retry_queue, sync_client_retries_enabled, max_inputs_outstanding
|
|
136
391
|
)
|
|
137
392
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
max_object_size_bytes=function._max_object_size_bytes,
|
|
147
|
-
idx=idx,
|
|
148
|
-
method_name=function._use_method_name,
|
|
149
|
-
)
|
|
393
|
+
input_preprocessor = InputPreprocessor(
|
|
394
|
+
client=client,
|
|
395
|
+
raw_input_queue=raw_input_queue,
|
|
396
|
+
processed_input_queue=input_queue,
|
|
397
|
+
function=function,
|
|
398
|
+
created_callback=lambda x: update_state(set_inputs_created=x),
|
|
399
|
+
done_callback=lambda: update_state(set_have_all_inputs=True),
|
|
400
|
+
)
|
|
150
401
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
402
|
+
input_pumper = SyncInputPumper(
|
|
403
|
+
client=client,
|
|
404
|
+
input_queue=input_queue,
|
|
405
|
+
retry_queue=retry_queue,
|
|
406
|
+
function=function,
|
|
407
|
+
map_items_manager=map_items_manager,
|
|
408
|
+
function_call_jwt=function_call_jwt,
|
|
409
|
+
function_call_id=function_call_id,
|
|
410
|
+
)
|
|
157
411
|
|
|
158
412
|
def update_state(set_have_all_inputs=None, set_inputs_created=None, set_outputs_completed=None):
|
|
159
413
|
# This should be the only method that needs nonlocal of the following vars
|
|
@@ -175,84 +429,6 @@ async def _map_invocation(
|
|
|
175
429
|
# map is done
|
|
176
430
|
map_done_event.set()
|
|
177
431
|
|
|
178
|
-
async def drain_input_generator():
|
|
179
|
-
# Parallelize uploading blobs
|
|
180
|
-
async with aclosing(
|
|
181
|
-
async_map_ordered(input_iter(), create_input, concurrency=BLOB_MAX_PARALLELISM)
|
|
182
|
-
) as streamer:
|
|
183
|
-
async for item in streamer:
|
|
184
|
-
await input_queue.put(item)
|
|
185
|
-
|
|
186
|
-
# close queue iterator
|
|
187
|
-
await input_queue.put(None)
|
|
188
|
-
update_state(set_have_all_inputs=True)
|
|
189
|
-
yield
|
|
190
|
-
|
|
191
|
-
async def pump_inputs():
|
|
192
|
-
assert client.stub
|
|
193
|
-
nonlocal inputs_sent
|
|
194
|
-
async for items in queue_batch_iterator(input_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
|
|
195
|
-
# Add items to the manager. Their state will be SENDING.
|
|
196
|
-
await map_items_manager.add_items(items)
|
|
197
|
-
request = api_pb2.FunctionPutInputsRequest(
|
|
198
|
-
function_id=function.object_id,
|
|
199
|
-
inputs=items,
|
|
200
|
-
function_call_id=function_call_id,
|
|
201
|
-
)
|
|
202
|
-
logger.debug(
|
|
203
|
-
f"Pushing {len(items)} inputs to server. Num queued inputs awaiting push is {input_queue.qsize()}."
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
resp = await send_inputs(client.stub.FunctionPutInputs, request)
|
|
207
|
-
inputs_sent += len(items)
|
|
208
|
-
# Change item state to WAITING_FOR_OUTPUT, and set the input_id and input_jwt which are in the response.
|
|
209
|
-
map_items_manager.handle_put_inputs_response(resp.inputs)
|
|
210
|
-
logger.debug(
|
|
211
|
-
f"Successfully pushed {len(items)} inputs to server. "
|
|
212
|
-
f"Num queued inputs awaiting push is {input_queue.qsize()}."
|
|
213
|
-
)
|
|
214
|
-
yield
|
|
215
|
-
|
|
216
|
-
async def retry_inputs():
|
|
217
|
-
nonlocal inputs_retried
|
|
218
|
-
async for retriable_idxs in queue_batch_iterator(retry_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
|
|
219
|
-
# For each index, use the context in the manager to create a FunctionRetryInputsItem.
|
|
220
|
-
# This will also update the context state to RETRYING.
|
|
221
|
-
inputs: list[api_pb2.FunctionRetryInputsItem] = await map_items_manager.prepare_items_for_retry(
|
|
222
|
-
retriable_idxs
|
|
223
|
-
)
|
|
224
|
-
request = api_pb2.FunctionRetryInputsRequest(
|
|
225
|
-
function_call_jwt=function_call_jwt,
|
|
226
|
-
inputs=inputs,
|
|
227
|
-
)
|
|
228
|
-
resp = await send_inputs(client.stub.FunctionRetryInputs, request)
|
|
229
|
-
# Update the state to WAITING_FOR_OUTPUT, and update the input_jwt in the context
|
|
230
|
-
# to the new value in the response.
|
|
231
|
-
map_items_manager.handle_retry_response(resp.input_jwts)
|
|
232
|
-
logger.debug(f"Successfully pushed retry for {len(inputs)} to server.")
|
|
233
|
-
inputs_retried += len(inputs)
|
|
234
|
-
yield
|
|
235
|
-
|
|
236
|
-
async def send_inputs(
|
|
237
|
-
fn: "modal.client.UnaryUnaryWrapper",
|
|
238
|
-
request: typing.Union[api_pb2.FunctionPutInputsRequest, api_pb2.FunctionRetryInputsRequest],
|
|
239
|
-
) -> typing.Union[api_pb2.FunctionPutInputsResponse, api_pb2.FunctionRetryInputsResponse]:
|
|
240
|
-
# with 8 retries we log the warning below about every 30 seconds which isn't too spammy.
|
|
241
|
-
retry_warning_message = RetryWarningMessage(
|
|
242
|
-
message=f"Warning: map progress for function {function._function_name} is limited."
|
|
243
|
-
" Common bottlenecks include slow iteration over results, or function backlogs.",
|
|
244
|
-
warning_interval=8,
|
|
245
|
-
errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
|
|
246
|
-
)
|
|
247
|
-
return await retry_transient_errors(
|
|
248
|
-
fn,
|
|
249
|
-
request,
|
|
250
|
-
max_retries=None,
|
|
251
|
-
max_delay=PUMP_INPUTS_MAX_RETRY_DELAY,
|
|
252
|
-
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
253
|
-
retry_warning_message=retry_warning_message,
|
|
254
|
-
)
|
|
255
|
-
|
|
256
432
|
async def get_all_outputs():
|
|
257
433
|
assert client.stub
|
|
258
434
|
nonlocal \
|
|
@@ -395,8 +571,11 @@ async def _map_invocation(
|
|
|
395
571
|
def log_stats():
|
|
396
572
|
logger.debug(
|
|
397
573
|
f"Map stats: sync_client_retries_enabled={sync_client_retries_enabled} "
|
|
398
|
-
f"have_all_inputs={have_all_inputs}
|
|
399
|
-
f"
|
|
574
|
+
f"have_all_inputs={have_all_inputs} "
|
|
575
|
+
f"inputs_created={inputs_created} "
|
|
576
|
+
f"input_sent={input_pumper.inputs_sent} "
|
|
577
|
+
f"inputs_retried={input_pumper.inputs_retried} "
|
|
578
|
+
f"outputs_received={outputs_received} "
|
|
400
579
|
f"successful_completions={successful_completions} failed_completions={failed_completions} "
|
|
401
580
|
f"no_context_duplicates={no_context_duplicates} old_retry_duplicates={stale_retry_duplicates} "
|
|
402
581
|
f"already_complete_duplicates={already_complete_duplicates} "
|
|
@@ -415,7 +594,12 @@ async def _map_invocation(
|
|
|
415
594
|
|
|
416
595
|
log_debug_stats_task = asyncio.create_task(log_debug_stats())
|
|
417
596
|
async with aclosing(
|
|
418
|
-
async_merge(
|
|
597
|
+
async_merge(
|
|
598
|
+
input_preprocessor.drain_input_generator(),
|
|
599
|
+
input_pumper.pump_inputs(),
|
|
600
|
+
input_pumper.retry_inputs(),
|
|
601
|
+
poll_outputs(),
|
|
602
|
+
)
|
|
419
603
|
) as streamer:
|
|
420
604
|
async for response in streamer:
|
|
421
605
|
if response is not None: # type: ignore[unreachable]
|
|
@@ -962,6 +1146,54 @@ def _map_sync(
|
|
|
962
1146
|
)
|
|
963
1147
|
|
|
964
1148
|
|
|
1149
|
+
async def _experimental_spawn_map_async(self, *input_iterators, kwargs={}) -> "modal.functions._FunctionCall":
|
|
1150
|
+
async_input_gen = async_zip(*[sync_or_async_iter(it) for it in input_iterators])
|
|
1151
|
+
return await _spawn_map_helper(self, async_input_gen, kwargs)
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
async def _spawn_map_helper(
|
|
1155
|
+
self: "modal.functions.Function", async_input_gen, kwargs={}
|
|
1156
|
+
) -> "modal.functions._FunctionCall":
|
|
1157
|
+
raw_input_queue: Any = SynchronizedQueue() # type: ignore
|
|
1158
|
+
await raw_input_queue.init.aio()
|
|
1159
|
+
|
|
1160
|
+
async def feed_queue():
|
|
1161
|
+
async with aclosing(async_input_gen) as streamer:
|
|
1162
|
+
async for args in streamer:
|
|
1163
|
+
await raw_input_queue.put.aio((args, kwargs))
|
|
1164
|
+
await raw_input_queue.put.aio(None) # end-of-input sentinel
|
|
1165
|
+
|
|
1166
|
+
fc, _ = await asyncio.gather(self._spawn_map.aio(raw_input_queue), feed_queue())
|
|
1167
|
+
return fc
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def _experimental_spawn_map_sync(self, *input_iterators, kwargs={}) -> "modal.functions._FunctionCall":
|
|
1171
|
+
"""Spawn parallel execution over a set of inputs, exiting as soon as the inputs are created (without waiting
|
|
1172
|
+
for the map to complete).
|
|
1173
|
+
|
|
1174
|
+
Takes one iterator argument per argument in the function being mapped over.
|
|
1175
|
+
|
|
1176
|
+
Example:
|
|
1177
|
+
```python
|
|
1178
|
+
@app.function()
|
|
1179
|
+
def my_func(a):
|
|
1180
|
+
return a ** 2
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
@app.local_entrypoint()
|
|
1184
|
+
def main():
|
|
1185
|
+
fc = my_func.spawn_map([1, 2, 3, 4])
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
Returns a FunctionCall object that can be used to retrieve results
|
|
1189
|
+
"""
|
|
1190
|
+
|
|
1191
|
+
return run_coroutine_in_temporary_event_loop(
|
|
1192
|
+
_experimental_spawn_map_async(self, *input_iterators, kwargs=kwargs),
|
|
1193
|
+
"You can't run Function.spawn_map() from an async function. Use Function.spawn_map.aio() instead.",
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1196
|
+
|
|
965
1197
|
async def _spawn_map_async(self, *input_iterators, kwargs={}) -> None:
|
|
966
1198
|
"""This runs in an event loop on the main thread. It consumes inputs from the input iterators and creates async
|
|
967
1199
|
function calls for each.
|
modal/parallel_map.pyi
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import asyncio.events
|
|
3
|
+
import asyncio.queues
|
|
3
4
|
import collections.abc
|
|
4
5
|
import enum
|
|
5
6
|
import modal._functions
|
|
@@ -60,6 +61,84 @@ class _OutputValue:
|
|
|
60
61
|
"""Return self==value."""
|
|
61
62
|
...
|
|
62
63
|
|
|
64
|
+
class InputPreprocessor:
|
|
65
|
+
"""Constructs FunctionPutInputsItem objects from the raw-input queue, and puts them in the processed-input queue."""
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
client: modal.client._Client,
|
|
69
|
+
*,
|
|
70
|
+
raw_input_queue: _SynchronizedQueue,
|
|
71
|
+
processed_input_queue: asyncio.queues.Queue,
|
|
72
|
+
function: modal._functions._Function,
|
|
73
|
+
created_callback: collections.abc.Callable[[int], None],
|
|
74
|
+
done_callback: collections.abc.Callable[[], None],
|
|
75
|
+
):
|
|
76
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
def input_iter(self): ...
|
|
80
|
+
def create_input_factory(self): ...
|
|
81
|
+
def drain_input_generator(self): ...
|
|
82
|
+
|
|
83
|
+
class InputPumper:
|
|
84
|
+
"""Reads inputs from a queue of FunctionPutInputsItems, and sends them to the server."""
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
client: modal.client._Client,
|
|
88
|
+
*,
|
|
89
|
+
input_queue: asyncio.queues.Queue,
|
|
90
|
+
function: modal._functions._Function,
|
|
91
|
+
function_call_id: str,
|
|
92
|
+
map_items_manager: typing.Optional[_MapItemsManager] = None,
|
|
93
|
+
):
|
|
94
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def pump_inputs(self): ...
|
|
98
|
+
async def _send_inputs(
|
|
99
|
+
self,
|
|
100
|
+
fn: modal.client.UnaryUnaryWrapper,
|
|
101
|
+
request: typing.Union[
|
|
102
|
+
modal_proto.api_pb2.FunctionPutInputsRequest, modal_proto.api_pb2.FunctionRetryInputsRequest
|
|
103
|
+
],
|
|
104
|
+
) -> typing.Union[
|
|
105
|
+
modal_proto.api_pb2.FunctionPutInputsResponse, modal_proto.api_pb2.FunctionRetryInputsResponse
|
|
106
|
+
]: ...
|
|
107
|
+
|
|
108
|
+
class SyncInputPumper(InputPumper):
|
|
109
|
+
"""Reads inputs from a queue of FunctionPutInputsItems, and sends them to the server."""
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
client: modal.client._Client,
|
|
113
|
+
*,
|
|
114
|
+
input_queue: asyncio.queues.Queue,
|
|
115
|
+
retry_queue: modal._utils.async_utils.TimestampPriorityQueue,
|
|
116
|
+
function: modal._functions._Function,
|
|
117
|
+
function_call_jwt: str,
|
|
118
|
+
function_call_id: str,
|
|
119
|
+
map_items_manager: _MapItemsManager,
|
|
120
|
+
):
|
|
121
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
def retry_inputs(self): ...
|
|
125
|
+
|
|
126
|
+
class AsyncInputPumper(InputPumper):
|
|
127
|
+
"""Reads inputs from a queue of FunctionPutInputsItems, and sends them to the server."""
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
client: modal.client._Client,
|
|
131
|
+
*,
|
|
132
|
+
input_queue: asyncio.queues.Queue,
|
|
133
|
+
function: modal._functions._Function,
|
|
134
|
+
function_call_id: str,
|
|
135
|
+
):
|
|
136
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
async def _spawn_map_invocation(
|
|
140
|
+
function: modal._functions._Function, raw_input_queue: _SynchronizedQueue, client: modal.client._Client
|
|
141
|
+
) -> str: ...
|
|
63
142
|
def _map_invocation(
|
|
64
143
|
function: modal._functions._Function,
|
|
65
144
|
raw_input_queue: _SynchronizedQueue,
|
|
@@ -179,6 +258,32 @@ def _map_sync(
|
|
|
179
258
|
"""
|
|
180
259
|
...
|
|
181
260
|
|
|
261
|
+
async def _experimental_spawn_map_async(self, *input_iterators, kwargs={}) -> modal._functions._FunctionCall: ...
|
|
262
|
+
async def _spawn_map_helper(
|
|
263
|
+
self: modal.functions.Function, async_input_gen, kwargs={}
|
|
264
|
+
) -> modal._functions._FunctionCall: ...
|
|
265
|
+
def _experimental_spawn_map_sync(self, *input_iterators, kwargs={}) -> modal._functions._FunctionCall:
|
|
266
|
+
"""Spawn parallel execution over a set of inputs, exiting as soon as the inputs are created (without waiting
|
|
267
|
+
for the map to complete).
|
|
268
|
+
|
|
269
|
+
Takes one iterator argument per argument in the function being mapped over.
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
```python
|
|
273
|
+
@app.function()
|
|
274
|
+
def my_func(a):
|
|
275
|
+
return a ** 2
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@app.local_entrypoint()
|
|
279
|
+
def main():
|
|
280
|
+
fc = my_func.spawn_map([1, 2, 3, 4])
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Returns a FunctionCall object that can be used to retrieve results
|
|
284
|
+
"""
|
|
285
|
+
...
|
|
286
|
+
|
|
182
287
|
async def _spawn_map_async(self, *input_iterators, kwargs={}) -> None:
|
|
183
288
|
"""This runs in an event loop on the main thread. It consumes inputs from the input iterators and creates async
|
|
184
289
|
function calls for each.
|
|
@@ -3,7 +3,7 @@ modal/__main__.py,sha256=45H-GtwzaDfN-1nP4_HYvzN3s7AG_HXR4-ynrsjO_OI,2803
|
|
|
3
3
|
modal/_clustered_functions.py,sha256=zmrKbptRbqp4euS3LWncKaLXb8Kjj4YreusOzpEpRMk,2856
|
|
4
4
|
modal/_clustered_functions.pyi,sha256=_wtFjWocGf1WgI-qYBpbJPArNkg2H9JV7BVaGgMesEQ,1103
|
|
5
5
|
modal/_container_entrypoint.py,sha256=1qBMNY_E9ICC_sRCtillMxmKPsmxJl1J0_qOAG8rH-0,28288
|
|
6
|
-
modal/_functions.py,sha256=
|
|
6
|
+
modal/_functions.py,sha256=avez1MO1DhKWq1o9rxUcVvyV5Jhzrny5j7z2sreVeto,83519
|
|
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=nCkQeLibSuvVAEIheGaLnUfN5PIh1CGpJCnzPIXymGY,11563
|
|
@@ -22,7 +22,7 @@ modal/app.py,sha256=kpq4kXp7pch688y6g55QYAC10wqPTU5FXKoWPMirA3E,47899
|
|
|
22
22
|
modal/app.pyi,sha256=-jKXlGDBWRPVsuenBhdMRqawK-L2eiJ7gHbmSblhltg,43525
|
|
23
23
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
|
24
24
|
modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
|
|
25
|
-
modal/client.pyi,sha256=
|
|
25
|
+
modal/client.pyi,sha256=imkUJz7TJe8UvcXL2TFQP58MT3rN3efYEXLbM91cBxo,15829
|
|
26
26
|
modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
|
|
27
27
|
modal/cloud_bucket_mount.pyi,sha256=-qSfYAQvIoO_l2wsCCGTG5ZUwQieNKXdAO00yP1-LYU,7394
|
|
28
28
|
modal/cls.py,sha256=7A0xGnugQzm8dOfnKMjLjtqekRlRtQ0jPFRYgq6xdUM,40018
|
|
@@ -39,7 +39,7 @@ modal/file_io.py,sha256=BVqAJ0sgPUfN8QsYztWiGB4j56he60TncM02KsylnCw,21449
|
|
|
39
39
|
modal/file_io.pyi,sha256=cPT_hsplE5iLCXhYOLn1Sp9eDdk7DxdFmicQHanJZyg,15918
|
|
40
40
|
modal/file_pattern_matcher.py,sha256=A_Kdkej6q7YQyhM_2-BvpFmPqJ0oHb54B6yf9VqvPVE,8116
|
|
41
41
|
modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
|
|
42
|
-
modal/functions.pyi,sha256=
|
|
42
|
+
modal/functions.pyi,sha256=65HxorqpspknohUdxFYzKIdL1-P3JYSQLQEhcmhWgpw,36161
|
|
43
43
|
modal/gpu.py,sha256=Fe5ORvVPDIstSq1xjmM6OoNgLYFWvogP9r5BgmD3hYg,6769
|
|
44
44
|
modal/image.py,sha256=A83nmo0zfCUwgvJh0LZ7Yc1sYvDnZLl_phbKxN-9HIw,103144
|
|
45
45
|
modal/image.pyi,sha256=oH-GCHVEwD5fOX0K_IaWN5RKZlYwX82z-K4wxx8aN3c,68541
|
|
@@ -52,8 +52,8 @@ modal/network_file_system.pyi,sha256=Td_IobHr84iLo_9LZKQ4tNdUB60yjX8QWBaFiUvhfi8
|
|
|
52
52
|
modal/object.py,sha256=bTeskuY8JFrESjU4_UL_nTwYlBQdOLmVaOX3X6EMxsg,164
|
|
53
53
|
modal/object.pyi,sha256=sgbaq_d3QSmnPKg5jRbMG3dOceKs0l54kHUAhAyZKAE,6796
|
|
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=2ujy7eRHK7N2sdyoKjq_ynWUyeoN9M9BD5XpB3KjMLk,66948
|
|
56
|
+
modal/parallel_map.pyi,sha256=MMjGNm-Q4J_rhjZ3441WOvQn4yrs0l0nACiKublZdu8,15373
|
|
57
57
|
modal/partial_function.py,sha256=aIdlGfTjjgqY6Fpr-biCjvRU9W542_S5N2xkNN_rYGM,1127
|
|
58
58
|
modal/partial_function.pyi,sha256=lqqOzZ9-QvHTDWKQ_oAYYOvsXgTOBKhO9u-RI98JbUk,13986
|
|
59
59
|
modal/proxy.py,sha256=NQJJMGo-D2IfmeU0vb10WWaE4oTLcuf9jTeEJvactOg,1446
|
|
@@ -151,7 +151,7 @@ modal/experimental/__init__.py,sha256=nuc7AL4r_Fs08DD5dciWFZhrV1nanwoClOfdTcudU0
|
|
|
151
151
|
modal/experimental/flash.py,sha256=viXQumCIFp5VFsPFURdFTBTjP_QnsAi8nSWXAMmfjeQ,19744
|
|
152
152
|
modal/experimental/flash.pyi,sha256=A8_qJGtGoXEzKDdHbvhmCw7oqfneFEvJQK3ZdTOvUdU,10830
|
|
153
153
|
modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
|
|
154
|
-
modal-1.1.2.
|
|
154
|
+
modal-1.1.2.dev8.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
155
155
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
|
156
156
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
|
157
157
|
modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
|
|
@@ -174,10 +174,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
|
|
|
174
174
|
modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
175
175
|
modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
|
|
176
176
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
177
|
-
modal_version/__init__.py,sha256=
|
|
177
|
+
modal_version/__init__.py,sha256=5coFSQVh4RHZPK33wsGOm-c_5mvGBfY0tgwxeUxksDQ,120
|
|
178
178
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
|
179
|
-
modal-1.1.2.
|
|
180
|
-
modal-1.1.2.
|
|
181
|
-
modal-1.1.2.
|
|
182
|
-
modal-1.1.2.
|
|
183
|
-
modal-1.1.2.
|
|
179
|
+
modal-1.1.2.dev8.dist-info/METADATA,sha256=O6TIIwCW9xjzp-RHB3_f2uGSNFp6l-JN7vJR7CPn1cs,2459
|
|
180
|
+
modal-1.1.2.dev8.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
181
|
+
modal-1.1.2.dev8.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
|
182
|
+
modal-1.1.2.dev8.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
|
183
|
+
modal-1.1.2.dev8.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|