modal 1.0.3.dev24__py3-none-any.whl → 1.0.3.dev26__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/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.3.dev24",
34
+ version: str = "1.0.3.dev26",
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.3.dev24",
97
+ version: str = "1.0.3.dev26",
98
98
  ): ...
99
99
  def is_closed(self) -> bool: ...
100
100
  @property
modal/parallel_map.py CHANGED
@@ -3,6 +3,7 @@ import asyncio
3
3
  import enum
4
4
  import time
5
5
  import typing
6
+ from asyncio import FIRST_COMPLETED
6
7
  from dataclasses import dataclass
7
8
  from typing import Any, Callable, Optional
8
9
 
@@ -110,6 +111,7 @@ async def _map_invocation(
110
111
  max_inputs_outstanding = response.max_inputs_outstanding or MAX_INPUTS_OUTSTANDING_DEFAULT
111
112
 
112
113
  have_all_inputs = False
114
+ map_done_event = asyncio.Event()
113
115
  inputs_created = 0
114
116
  inputs_sent = 0
115
117
  inputs_retried = 0
@@ -122,10 +124,6 @@ async def _map_invocation(
122
124
  stale_retry_duplicates = 0
123
125
  no_context_duplicates = 0
124
126
 
125
- def count_update():
126
- if count_update_callback is not None:
127
- count_update_callback(outputs_completed, inputs_created)
128
-
129
127
  retry_queue = TimestampPriorityQueue()
130
128
  completed_outputs: set[str] = set() # Set of input_ids whose outputs are complete (expecting no more values)
131
129
  input_queue: asyncio.Queue[api_pb2.FunctionPutInputsItem | None] = asyncio.Queue()
@@ -134,9 +132,8 @@ async def _map_invocation(
134
132
  )
135
133
 
136
134
  async def create_input(argskwargs):
137
- nonlocal inputs_created
138
135
  idx = inputs_created
139
- inputs_created += 1
136
+ update_state(set_inputs_created=inputs_created + 1)
140
137
  (args, kwargs) = argskwargs
141
138
  return await _create_input(args, kwargs, client.stub, idx=idx, method_name=function._use_method_name)
142
139
 
@@ -147,9 +144,27 @@ async def _map_invocation(
147
144
  break
148
145
  yield raw_input # args, kwargs
149
146
 
150
- async def drain_input_generator():
151
- nonlocal have_all_inputs
147
+ def update_state(set_have_all_inputs=None, set_inputs_created=None, set_outputs_completed=None):
148
+ # This should be the only method that needs nonlocal of the following vars
149
+ nonlocal have_all_inputs, inputs_created, outputs_completed
150
+ assert set_have_all_inputs is not False # not allowed
151
+ assert set_inputs_created is None or set_inputs_created > inputs_created
152
+ assert set_outputs_completed is None or set_outputs_completed > outputs_completed
153
+ if set_have_all_inputs is not None:
154
+ have_all_inputs = set_have_all_inputs
155
+ if set_inputs_created is not None:
156
+ inputs_created = set_inputs_created
157
+ if set_outputs_completed is not None:
158
+ outputs_completed = set_outputs_completed
159
+
160
+ if count_update_callback is not None:
161
+ count_update_callback(outputs_completed, inputs_created)
162
+
163
+ if have_all_inputs and outputs_completed >= inputs_created:
164
+ # map is done
165
+ map_done_event.set()
152
166
 
167
+ async def drain_input_generator():
153
168
  # Parallelize uploading blobs
154
169
  async with aclosing(
155
170
  async_map_ordered(input_iter(), create_input, concurrency=BLOB_MAX_PARALLELISM)
@@ -159,12 +174,12 @@ async def _map_invocation(
159
174
 
160
175
  # close queue iterator
161
176
  await input_queue.put(None)
162
- have_all_inputs = True
177
+ update_state(set_have_all_inputs=True)
163
178
  yield
164
179
 
165
180
  async def pump_inputs():
166
181
  assert client.stub
167
- nonlocal inputs_created, inputs_sent
182
+ nonlocal inputs_sent
168
183
  async for items in queue_batch_iterator(input_queue, max_batch_size=MAP_INVOCATION_CHUNK_SIZE):
169
184
  # Add items to the manager. Their state will be SENDING.
170
185
  await map_items_manager.add_items(items)
@@ -178,7 +193,6 @@ async def _map_invocation(
178
193
  )
179
194
 
180
195
  resp = await send_inputs(client.stub.FunctionPutInputs, request)
181
- count_update()
182
196
  inputs_sent += len(items)
183
197
  # Change item state to WAITING_FOR_OUTPUT, and set the input_id and input_jwt which are in the response.
184
198
  map_items_manager.handle_put_inputs_response(resp.inputs)
@@ -231,11 +245,8 @@ async def _map_invocation(
231
245
  async def get_all_outputs():
232
246
  assert client.stub
233
247
  nonlocal \
234
- inputs_created, \
235
248
  successful_completions, \
236
249
  failed_completions, \
237
- outputs_completed, \
238
- have_all_inputs, \
239
250
  outputs_received, \
240
251
  already_complete_duplicates, \
241
252
  no_context_duplicates, \
@@ -244,7 +255,7 @@ async def _map_invocation(
244
255
 
245
256
  last_entry_id = "0-0"
246
257
 
247
- while not have_all_inputs or outputs_completed < inputs_created:
258
+ while not map_done_event.is_set():
248
259
  logger.debug(f"Requesting outputs. Have {outputs_completed} outputs, {inputs_created} inputs.")
249
260
  # Get input_jwts of all items in the WAITING_FOR_OUTPUT state.
250
261
  # The server uses these to track for lost inputs.
@@ -258,12 +269,26 @@ async def _map_invocation(
258
269
  requested_at=time.time(),
259
270
  input_jwts=input_jwts,
260
271
  )
261
- response = await retry_transient_errors(
262
- client.stub.FunctionGetOutputs,
263
- request,
264
- max_retries=20,
265
- attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
272
+ get_response_task = asyncio.create_task(
273
+ retry_transient_errors(
274
+ client.stub.FunctionGetOutputs,
275
+ request,
276
+ max_retries=20,
277
+ attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
278
+ )
266
279
  )
280
+ map_done_task = asyncio.create_task(map_done_event.wait())
281
+ done, pending = await asyncio.wait([get_response_task, map_done_task], return_when=FIRST_COMPLETED)
282
+ if get_response_task in done:
283
+ map_done_task.cancel()
284
+ response = get_response_task.result()
285
+ else:
286
+ assert map_done_event.is_set()
287
+ # map is done, cancel the pending call
288
+ get_response_task.cancel()
289
+ # not strictly necessary - don't leave dangling task
290
+ await asyncio.gather(get_response_task, return_exceptions=True)
291
+ return
267
292
 
268
293
  last_entry_id = response.last_entry_id
269
294
  now_seconds = int(time.time())
@@ -288,7 +313,7 @@ async def _map_invocation(
288
313
 
289
314
  if output_type == _OutputType.SUCCESSFUL_COMPLETION or output_type == _OutputType.FAILED_COMPLETION:
290
315
  completed_outputs.add(item.input_id)
291
- outputs_completed += 1
316
+ update_state(set_outputs_completed=outputs_completed + 1)
292
317
  yield item
293
318
 
294
319
  async def get_all_outputs_and_clean_up():
@@ -328,7 +353,6 @@ async def _map_invocation(
328
353
  async_map_ordered(get_all_outputs_and_clean_up(), fetch_output, concurrency=BLOB_MAX_PARALLELISM)
329
354
  ) as streamer:
330
355
  async for idx, output in streamer:
331
- count_update()
332
356
  if not order_outputs:
333
357
  yield _OutputValue(output)
334
358
  else:
@@ -401,7 +425,7 @@ async def _map_helper(
401
425
  """
402
426
 
403
427
  raw_input_queue: Any = SynchronizedQueue() # type: ignore
404
- raw_input_queue.init()
428
+ await raw_input_queue.init.aio()
405
429
 
406
430
  async def feed_queue():
407
431
  async with aclosing(async_input_gen) as streamer:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.3.dev24
3
+ Version: 1.0.3.dev26
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 @@ 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=43v25eJLQ_Z9I34eUX--3bAkw88bBRuiy-Y9jFxZMSY,8459
25
+ modal/client.pyi,sha256=wad4VLK22aPQB63PENKOOxITmZsc_mA2xgk8q3JPaJg,8459
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=dBbeARwOWftlKd1cwtM0cHFtQWSWkwVXwVmOV4w0SyI,37907
@@ -52,7 +52,7 @@ 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=zU2zL8_9PmmNC9Ny2GB7K2_HbAdPU7RiVLN0GtzaDls,35923
55
+ modal/parallel_map.py,sha256=zlJTh3NuqlrmxkcZMSXerM2dKytYX3Pr737vBLr9AX4,37428
56
56
  modal/parallel_map.pyi,sha256=mhYGQmufQEJbjNrX7vNhBS2gUdfBrpmuWNUHth_Dz6U,6140
57
57
  modal/partial_function.py,sha256=SwuAAj2wj4SO6F6nkSnwNZrczEmm9w9YdlQTHh6hr04,1195
58
58
  modal/partial_function.pyi,sha256=NFWz1aCAs2B3-GnPf1cTatWRZOLnYpFKCnjP_X9iNRs,6411
@@ -147,7 +147,7 @@ modal/requirements/2024.10.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddR
147
147
  modal/requirements/PREVIEW.txt,sha256=qD-5cVIVM9wXesJ6JC89Ew-3m2KjEElUz3jaw_MddRo,296
148
148
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
149
149
  modal/requirements/base-images.json,sha256=57vMSqzMbLBxw5tFWSaMiIkkVEps4JfX5PAtXGnkS4U,740
150
- modal-1.0.3.dev24.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
150
+ modal-1.0.3.dev26.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=LglP0cU1I6VAn0uI9VPGDXvhz2h45TCqu4ebPFoUM_s,121
173
+ modal_version/__init__.py,sha256=GIYda175HQtvkJCOUV4zp0tKpdnHT76NUXHNTm_B2kY,121
174
174
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
175
- modal-1.0.3.dev24.dist-info/METADATA,sha256=dPiMg28XeMsI9ozHMRgkuxbMg02Bblt0Vukjq58ddyM,2455
176
- modal-1.0.3.dev24.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
177
- modal-1.0.3.dev24.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
- modal-1.0.3.dev24.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
- modal-1.0.3.dev24.dist-info/RECORD,,
175
+ modal-1.0.3.dev26.dist-info/METADATA,sha256=IyUCFOSBhrLE8Rnpt1buHnoZCL627KE3CQSHrNgwOxA,2455
176
+ modal-1.0.3.dev26.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
177
+ modal-1.0.3.dev26.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
+ modal-1.0.3.dev26.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
179
+ modal-1.0.3.dev26.dist-info/RECORD,,
modal_version/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
  """Supplies the current version of the modal client library."""
3
3
 
4
- __version__ = "1.0.3.dev24"
4
+ __version__ = "1.0.3.dev26"