modal 1.0.6.dev46__py3-none-any.whl → 1.0.6.dev50__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/cli/run.py CHANGED
@@ -207,7 +207,7 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
207
207
  return f
208
208
 
209
209
 
210
- def _get_click_command_for_function(app: App, function: Function):
210
+ def _get_click_command_for_function(app: App, function: Function, ctx: click.Context):
211
211
  if function.is_generator:
212
212
  raise InvalidError("`modal run` is not supported for generator functions")
213
213
 
@@ -216,7 +216,10 @@ def _get_click_command_for_function(app: App, function: Function):
216
216
  signature: CliRunnableSignature = _get_cli_runnable_signature(sig, type_hints)
217
217
 
218
218
  def _inner(args, click_kwargs):
219
- return function.remote(*args, **click_kwargs)
219
+ if ctx.obj["detach"]:
220
+ return function.spawn(*args, **click_kwargs).get()
221
+ else:
222
+ return function.remote(*args, **click_kwargs)
220
223
 
221
224
  f = _make_click_function(app, signature, _inner)
222
225
 
@@ -230,7 +233,7 @@ def _get_click_command_for_function(app: App, function: Function):
230
233
  return click.command(with_click_options)
231
234
 
232
235
 
233
- def _get_click_command_for_cls(app: App, method_ref: MethodReference):
236
+ def _get_click_command_for_cls(app: App, method_ref: MethodReference, ctx: click.Context):
234
237
  parameters: dict[str, ParameterMetadata]
235
238
  cls = method_ref.cls
236
239
  method_name = method_ref.method_name
@@ -271,7 +274,10 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference):
271
274
 
272
275
  instance = cls(**cls_kwargs)
273
276
  method: Function = getattr(instance, method_name)
274
- return method.remote(*args, **fun_kwargs)
277
+ if ctx.obj["detach"]:
278
+ return method.spawn(*args, **fun_kwargs).get()
279
+ else:
280
+ return method.remote(*args, **fun_kwargs)
275
281
 
276
282
  f = _make_click_function(app, fun_signature, _inner)
277
283
  with_click_options = _add_click_options(f, parameters)
@@ -376,9 +382,9 @@ class RunGroup(click.Group):
376
382
  if isinstance(runnable, LocalEntrypoint):
377
383
  click_command = _get_click_command_for_local_entrypoint(app, runnable)
378
384
  elif isinstance(runnable, Function):
379
- click_command = _get_click_command_for_function(app, runnable)
385
+ click_command = _get_click_command_for_function(app, runnable, ctx)
380
386
  elif isinstance(runnable, MethodReference):
381
- click_command = _get_click_command_for_cls(app, runnable)
387
+ click_command = _get_click_command_for_cls(app, runnable, ctx)
382
388
  else:
383
389
  # This should be unreachable...
384
390
  raise ValueError(f"{runnable} is neither function, local entrypoint or class/method")
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.0.6.dev46",
36
+ version: str = "1.0.6.dev50",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -163,7 +163,7 @@ class Client:
163
163
  server_url: str,
164
164
  client_type: int,
165
165
  credentials: typing.Optional[tuple[str, str]],
166
- version: str = "1.0.6.dev46",
166
+ version: str = "1.0.6.dev50",
167
167
  ):
168
168
  """mdmd:hidden
169
169
  The Modal client object is not intended to be instantiated directly by users.
@@ -50,6 +50,7 @@ class _ContainerProcess(Generic[T]):
50
50
  stream_type=stdout,
51
51
  text=text,
52
52
  by_line=by_line,
53
+ deadline=exec_deadline,
53
54
  )
54
55
  self._stderr = _StreamReader[T](
55
56
  api_pb2.FILE_DESCRIPTOR_STDERR,
@@ -59,6 +60,7 @@ class _ContainerProcess(Generic[T]):
59
60
  stream_type=stderr,
60
61
  text=text,
61
62
  by_line=by_line,
63
+ deadline=exec_deadline,
62
64
  )
63
65
  self._stdin = _StreamWriter(process_id, "container_process", self._client)
64
66
 
modal/io_streams.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import asyncio
3
+ import time
3
4
  from collections.abc import AsyncGenerator, AsyncIterator
4
5
  from typing import (
5
6
  TYPE_CHECKING,
@@ -50,6 +51,7 @@ async def _container_process_logs_iterator(
50
51
  file_descriptor: "api_pb2.FileDescriptor.ValueType",
51
52
  client: _Client,
52
53
  last_index: int,
54
+ deadline: Optional[float] = None,
53
55
  ) -> AsyncGenerator[tuple[Optional[bytes], int], None]:
54
56
  req = api_pb2.ContainerExecGetOutputRequest(
55
57
  exec_id=process_id,
@@ -58,7 +60,18 @@ async def _container_process_logs_iterator(
58
60
  get_raw_bytes=True,
59
61
  last_batch_index=last_index,
60
62
  )
61
- async for batch in client.stub.ContainerExecGetOutput.unary_stream(req):
63
+
64
+ stream = client.stub.ContainerExecGetOutput.unary_stream(req)
65
+ while True:
66
+ # Check deadline before attempting to receive the next batch
67
+ try:
68
+ remaining = (deadline - time.monotonic()) if deadline else None
69
+ batch = await asyncio.wait_for(stream.__anext__(), timeout=remaining)
70
+ except asyncio.TimeoutError:
71
+ yield None, -1
72
+ break
73
+ except StopAsyncIteration:
74
+ break
62
75
  if batch.HasField("exit_code"):
63
76
  yield None, batch.batch_index
64
77
  break
@@ -102,6 +115,7 @@ class _StreamReader(Generic[T]):
102
115
  stream_type: StreamType = StreamType.PIPE,
103
116
  text: bool = True,
104
117
  by_line: bool = False,
118
+ deadline: Optional[float] = None,
105
119
  ) -> None:
106
120
  """mdmd:hidden"""
107
121
  self._file_descriptor = file_descriptor
@@ -111,6 +125,7 @@ class _StreamReader(Generic[T]):
111
125
  self._stream = None
112
126
  self._last_entry_id: str = ""
113
127
  self._line_buffer = b""
128
+ self._deadline = deadline
114
129
 
115
130
  # Sandbox logs are streamed to the client as strings, so StreamReaders reading
116
131
  # them must have text mode enabled.
@@ -187,11 +202,12 @@ class _StreamReader(Generic[T]):
187
202
  retries_remaining = 10
188
203
  last_index = 0
189
204
  while not completed:
205
+ if self._deadline and time.monotonic() >= self._deadline:
206
+ break
190
207
  try:
191
208
  iterator = _container_process_logs_iterator(
192
- self._object_id, self._file_descriptor, self._client, last_index
209
+ self._object_id, self._file_descriptor, self._client, last_index, self._deadline
193
210
  )
194
-
195
211
  async for message, batch_index in iterator:
196
212
  if self._stream_type == StreamType.STDOUT and message:
197
213
  print(message.decode("utf-8"), end="")
modal/io_streams.pyi CHANGED
@@ -8,7 +8,11 @@ def _sandbox_logs_iterator(
8
8
  sandbox_id: str, file_descriptor: int, last_entry_id: str, client: modal.client._Client
9
9
  ) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]: ...
10
10
  def _container_process_logs_iterator(
11
- process_id: str, file_descriptor: int, client: modal.client._Client, last_index: int
11
+ process_id: str,
12
+ file_descriptor: int,
13
+ client: modal.client._Client,
14
+ last_index: int,
15
+ deadline: typing.Optional[float] = None,
12
16
  ) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], int], None]: ...
13
17
 
14
18
  T = typing.TypeVar("T")
@@ -46,6 +50,7 @@ class _StreamReader(typing.Generic[T]):
46
50
  stream_type: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
47
51
  text: bool = True,
48
52
  by_line: bool = False,
53
+ deadline: typing.Optional[float] = None,
49
54
  ) -> None:
50
55
  """mdmd:hidden"""
51
56
  ...
@@ -211,6 +216,7 @@ class StreamReader(typing.Generic[T]):
211
216
  stream_type: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
212
217
  text: bool = True,
213
218
  by_line: bool = False,
219
+ deadline: typing.Optional[float] = None,
214
220
  ) -> None:
215
221
  """mdmd:hidden"""
216
222
  ...
modal/sandbox.py CHANGED
@@ -50,10 +50,11 @@ _default_image: _Image = _Image.debian_slim()
50
50
  # e.g. 'runsc exec ...'. So we use 2**16 as the limit.
51
51
  ARG_MAX_BYTES = 2**16
52
52
 
53
-
54
53
  # This buffer extends the user-supplied timeout on ContainerExec-related RPCs. This was introduced to
55
54
  # give any in-flight status codes/IO data more time to reach the client before the stream is closed.
56
55
  CONTAINER_EXEC_TIMEOUT_BUFFER = 5
56
+
57
+
57
58
  if TYPE_CHECKING:
58
59
  import modal.app
59
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.6.dev46
3
+ Version: 1.0.6.dev50
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,13 +22,13 @@ modal/app.py,sha256=U0sPiHpphcRHLnoLYh2IrU2RSpRFX9BE5uHb7h42STs,47478
22
22
  modal/app.pyi,sha256=cXiSTu2bwu6csAUdkOlh7mr9tPvtaS2qWSEhlC1UxAg,43787
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=5QyM7VJjsFbHf6E91ar3A2KY9mx03wdtGlNJvfTKUVs,17087
25
- modal/client.pyi,sha256=XrFEHUFGAKbIeQugGthiLTV7TI3SPBhAdPAUKOsUXbM,15270
25
+ modal/client.pyi,sha256=CSXv8f_xO-nkEZ7u6VyimMmdBlQZ2UBEFCeGpIKvCpg,15270
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=B5EtzpBXemH718YvgXaYjuTKvairvqfXJ7IwLZ_6vVA,40034
29
29
  modal/cls.pyi,sha256=_tZ5qrlL-ZDEcD-mf9BZkkNH5XPr4SmGTEQ-RVmqF3I,27772
30
30
  modal/config.py,sha256=FqVewLPVVR4feq_46JBENiCzqTuXKpnvQZxaeWbS39g,12009
31
- modal/container_process.py,sha256=dfqa6YFRhNQ4XeZ8cS5DVh-VpzfavA2asrXwrLRHYXg,6780
31
+ modal/container_process.py,sha256=1m4NPg0lgZmlG9OTkhHbjafqlqoo4lLv-A-X5lV21yo,6852
32
32
  modal/container_process.pyi,sha256=9m-st3hCUlNN1GOTctfPPvIvoLtEl7FbuGWwif5-7YU,6037
33
33
  modal/dict.py,sha256=wVIkHPFvR8WDoh5c6jT0UstZYmJTpCTM8drkwwjLiAc,14387
34
34
  modal/dict.pyi,sha256=gs3J7X5yG3J1L6rW0s3_7yRn8qAfY0f4n5-sqaDZY2g,20853
@@ -43,8 +43,8 @@ modal/functions.pyi,sha256=FJe_91dSrMCRNVT-YV1UhtxFKzIvL_C5q8xdk08-wT8,34840
43
43
  modal/gpu.py,sha256=Fe5ORvVPDIstSq1xjmM6OoNgLYFWvogP9r5BgmD3hYg,6769
44
44
  modal/image.py,sha256=qTJ6pTcLfYRh112wId7CCNWWmm077w6JoIqxE8BiCoo,102261
45
45
  modal/image.pyi,sha256=TVy-rnSAP2WgQ5zf_sQLFzb-99Qg9LiQNGXR9psFA_o,68107
46
- modal/io_streams.py,sha256=FUDpBsVK8isqwyC7DtAcQZhaHlMFSaNZGhYJOg-SFW0,15590
47
- modal/io_streams.pyi,sha256=5b3b93ztZeR8IpJtNIGffX24QLPgocE4-gAps8y7CKU,13824
46
+ modal/io_streams.py,sha256=25ifqMoixhQ3brpnvK2E5z-5n2MOqtYxh8X1tXbahQc,16204
47
+ modal/io_streams.pyi,sha256=aOun_jUFKHSJyUY6-7gKvNoxzcULsa8_hxdtEO7v-gk,13980
48
48
  modal/mount.py,sha256=q-pPeVxAmte-G_LDpbFwaNs2Rb2MIpscfnCXzkhxrOI,36734
49
49
  modal/mount.pyi,sha256=n6AuS8J3bTCQj750nVZZdVBvzCAlSM2fyxAt_5LLFik,20264
50
50
  modal/network_file_system.py,sha256=92U94Wk2fP40LlgLDIHkTqQ-zc21YxaG6SdFQy8SudU,14731
@@ -65,7 +65,7 @@ modal/retries.py,sha256=IvNLDM0f_GLUDD5VgEDoN09C88yoxSrCquinAuxT1Sc,5205
65
65
  modal/runner.py,sha256=ostdzYpQb-20tlD6dIq7bpWTkZkOhjJBNuMNektqnJA,24068
66
66
  modal/runner.pyi,sha256=lbwLljm1cC8d6PcNvmYQhkE8501V9fg0bYqqKX6G4r4,8489
67
67
  modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
68
- modal/sandbox.py,sha256=q7kpGNustlL6Lw7KQbzhw_XwDFn0qBfCoEP7lTW3wYY,37583
68
+ modal/sandbox.py,sha256=hKuGVBB-JTn-Oq4hCxNS3rR6ismQ_TgcSKZv5--iDz8,37584
69
69
  modal/sandbox.pyi,sha256=AyROza8ZUUxs6MO1f3l8zDjTkp6O46H132xUwBUixIc,38565
70
70
  modal/schedule.py,sha256=ng0g0AqNY5GQI9KhkXZQ5Wam5G42glbkqVQsNpBtbDE,3078
71
71
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
@@ -131,7 +131,7 @@ modal/cli/launch.py,sha256=0_sBu6bv2xJEPWi-rbGS6Ri9ggnkWQvrGlgpYSUBMyY,3097
131
131
  modal/cli/network_file_system.py,sha256=1_BF95WPLHh7x37lr0JBx5nS8NsKXCDZKt0L2F5fHgo,8104
132
132
  modal/cli/profile.py,sha256=0TYhgRSGUvQZ5LH9nkl6iZllEvAjDniES264dE57wOM,3201
133
133
  modal/cli/queues.py,sha256=1OzC9HdCkbNz6twF3US4FZmIhuVRQ01GOfBY42ux61A,4533
134
- modal/cli/run.py,sha256=nicjV2Ei-UxH9bcilUPeqlv1b15TSHAermaoaDJVBxc,24678
134
+ modal/cli/run.py,sha256=96m6fpJKbjtva4xzJut0pxS36Z5WCMq0umpAry96im0,24946
135
135
  modal/cli/secret.py,sha256=2bngl3Gb6THXkQ2eWZIN9pOHeOFJqiSNo_waUCVYgns,6611
136
136
  modal/cli/token.py,sha256=NAmQzKBfEHkcldWKeFxAVIqQBoo1RTp7_A4yc7-8qM0,1911
137
137
  modal/cli/utils.py,sha256=p3ru9mlrvyCg6WWUcTWIJfZtYN3-MsXio_vy3dJ_8WU,3302
@@ -151,7 +151,7 @@ modal/requirements/2025.06.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqr
151
151
  modal/requirements/PREVIEW.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqrs,312
152
152
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
153
153
  modal/requirements/base-images.json,sha256=JYSDAgHTl-WrV_TZW5icY-IJEnbe2eQ4CZ_KN6EOZKU,1304
154
- modal-1.0.6.dev46.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
154
+ modal-1.0.6.dev50.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=0WqZMgsOL5_G5EGQHJemZ47NHtj2QHvSoYKIh-b50uE,121
177
+ modal_version/__init__.py,sha256=K2p8aB57qu9ubXiiRDFcIGXUpyyqBFqs1eiMIu3nRhU,121
178
178
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
179
- modal-1.0.6.dev46.dist-info/METADATA,sha256=p5E_YxlzIzOOrExGLNTD5n1ybGVVi7zrz7aYFVy7Y3o,2462
180
- modal-1.0.6.dev46.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
181
- modal-1.0.6.dev46.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
182
- modal-1.0.6.dev46.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
183
- modal-1.0.6.dev46.dist-info/RECORD,,
179
+ modal-1.0.6.dev50.dist-info/METADATA,sha256=uQyufZ5YxBlR7YhSg_hUGdpcUpjwTinnw4ITBYt0Enc,2462
180
+ modal-1.0.6.dev50.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
181
+ modal-1.0.6.dev50.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
182
+ modal-1.0.6.dev50.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
183
+ modal-1.0.6.dev50.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.6.dev46"
4
+ __version__ = "1.0.6.dev50"