modal 1.0.6.dev28__py3-none-any.whl → 1.0.6.dev29__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/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.6.dev28",
34
+ version: str = "1.0.6.dev29",
35
35
  ):
36
36
  """mdmd:hidden
37
37
  The Modal client object is not intended to be instantiated directly by users.
@@ -160,7 +160,7 @@ class Client:
160
160
  server_url: str,
161
161
  client_type: int,
162
162
  credentials: typing.Optional[tuple[str, str]],
163
- version: str = "1.0.6.dev28",
163
+ version: str = "1.0.6.dev29",
164
164
  ):
165
165
  """mdmd:hidden
166
166
  The Modal client object is not intended to be instantiated directly by users.
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2024
2
2
  import asyncio
3
3
  import platform
4
+ import time
4
5
  from typing import Generic, Optional, TypeVar
5
6
 
6
7
  from modal_proto import api_pb2
@@ -21,6 +22,7 @@ class _ContainerProcess(Generic[T]):
21
22
  _stdout: _StreamReader[T]
22
23
  _stderr: _StreamReader[T]
23
24
  _stdin: _StreamWriter
25
+ _exec_deadline: Optional[float] = None
24
26
  _text: bool
25
27
  _by_line: bool
26
28
  _returncode: Optional[int] = None
@@ -31,11 +33,13 @@ class _ContainerProcess(Generic[T]):
31
33
  client: _Client,
32
34
  stdout: StreamType = StreamType.PIPE,
33
35
  stderr: StreamType = StreamType.PIPE,
36
+ exec_deadline: Optional[float] = None,
34
37
  text: bool = True,
35
38
  by_line: bool = False,
36
39
  ) -> None:
37
40
  self._process_id = process_id
38
41
  self._client = client
42
+ self._exec_deadline = exec_deadline
39
43
  self._text = text
40
44
  self._by_line = by_line
41
45
  self._stdout = _StreamReader[T](
@@ -92,6 +96,11 @@ class _ContainerProcess(Generic[T]):
92
96
  """
93
97
  if self._returncode is not None:
94
98
  return self._returncode
99
+ if self._exec_deadline and time.monotonic() >= self._exec_deadline:
100
+ # TODO(matt): In the future, it would be nice to raise a ContainerExecTimeoutError to make it
101
+ # clear to the user that their sandbox terminated due to a timeout
102
+ self._returncode = -1
103
+ return self._returncode
95
104
 
96
105
  req = api_pb2.ContainerExecWaitRequest(exec_id=self._process_id, timeout=0)
97
106
  resp: api_pb2.ContainerExecWaitResponse = await retry_transient_errors(self._client.stub.ContainerExecWait, req)
@@ -102,22 +111,33 @@ class _ContainerProcess(Generic[T]):
102
111
 
103
112
  return None
104
113
 
105
- async def wait(self) -> int:
106
- """Wait for the container process to finish running. Returns the exit code."""
107
-
108
- if self._returncode is not None:
109
- return self._returncode
110
-
114
+ async def _wait_for_completion(self) -> int:
111
115
  while True:
112
116
  req = api_pb2.ContainerExecWaitRequest(exec_id=self._process_id, timeout=10)
113
117
  resp: api_pb2.ContainerExecWaitResponse = await retry_transient_errors(
114
118
  self._client.stub.ContainerExecWait, req
115
119
  )
116
120
  if resp.completed:
117
- self._returncode = resp.exit_code
118
- return self._returncode
121
+ return resp.exit_code
122
+
123
+ async def wait(self) -> int:
124
+ """Wait for the container process to finish running. Returns the exit code."""
125
+ if self._returncode is not None:
126
+ return self._returncode
127
+
128
+ try:
129
+ timeout = None
130
+ if self._exec_deadline:
131
+ timeout = self._exec_deadline - time.monotonic()
132
+ if timeout <= 0:
133
+ raise TimeoutError()
134
+ self._returncode = await asyncio.wait_for(self._wait_for_completion(), timeout=timeout)
135
+ except (asyncio.TimeoutError, TimeoutError):
136
+ self._returncode = -1
137
+ return self._returncode
119
138
 
120
139
  async def attach(self):
140
+ """mdmd:hidden"""
121
141
  if platform.system() == "Windows":
122
142
  print("interactive exec is not currently supported on Windows.")
123
143
  return
@@ -31,6 +31,7 @@ class _ContainerProcess(typing.Generic[T]):
31
31
  _stdout: modal.io_streams._StreamReader[T]
32
32
  _stderr: modal.io_streams._StreamReader[T]
33
33
  _stdin: modal.io_streams._StreamWriter
34
+ _exec_deadline: typing.Optional[float]
34
35
  _text: bool
35
36
  _by_line: bool
36
37
  _returncode: typing.Optional[int]
@@ -41,6 +42,7 @@ class _ContainerProcess(typing.Generic[T]):
41
42
  client: modal.client._Client,
42
43
  stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
43
44
  stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
45
+ exec_deadline: typing.Optional[float] = None,
44
46
  text: bool = True,
45
47
  by_line: bool = False,
46
48
  ) -> None:
@@ -75,11 +77,14 @@ class _ContainerProcess(typing.Generic[T]):
75
77
  """
76
78
  ...
77
79
 
80
+ async def _wait_for_completion(self) -> int: ...
78
81
  async def wait(self) -> int:
79
82
  """Wait for the container process to finish running. Returns the exit code."""
80
83
  ...
81
84
 
82
- async def attach(self): ...
85
+ async def attach(self):
86
+ """mdmd:hidden"""
87
+ ...
83
88
 
84
89
  SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
85
90
 
@@ -108,6 +113,7 @@ class ContainerProcess(typing.Generic[T]):
108
113
  _stdout: modal.io_streams.StreamReader[T]
109
114
  _stderr: modal.io_streams.StreamReader[T]
110
115
  _stdin: modal.io_streams.StreamWriter
116
+ _exec_deadline: typing.Optional[float]
111
117
  _text: bool
112
118
  _by_line: bool
113
119
  _returncode: typing.Optional[int]
@@ -118,6 +124,7 @@ class ContainerProcess(typing.Generic[T]):
118
124
  client: modal.client.Client,
119
125
  stdout: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
120
126
  stderr: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
127
+ exec_deadline: typing.Optional[float] = None,
121
128
  text: bool = True,
122
129
  by_line: bool = False,
123
130
  ) -> None: ...
@@ -157,6 +164,12 @@ class ContainerProcess(typing.Generic[T]):
157
164
 
158
165
  poll: __poll_spec[typing_extensions.Self]
159
166
 
167
+ class ___wait_for_completion_spec(typing_extensions.Protocol[SUPERSELF]):
168
+ def __call__(self, /) -> int: ...
169
+ async def aio(self, /) -> int: ...
170
+
171
+ _wait_for_completion: ___wait_for_completion_spec[typing_extensions.Self]
172
+
160
173
  class __wait_spec(typing_extensions.Protocol[SUPERSELF]):
161
174
  def __call__(self, /) -> int:
162
175
  """Wait for the container process to finish running. Returns the exit code."""
@@ -169,7 +182,12 @@ class ContainerProcess(typing.Generic[T]):
169
182
  wait: __wait_spec[typing_extensions.Self]
170
183
 
171
184
  class __attach_spec(typing_extensions.Protocol[SUPERSELF]):
172
- def __call__(self, /): ...
173
- async def aio(self, /): ...
185
+ def __call__(self, /):
186
+ """mdmd:hidden"""
187
+ ...
188
+
189
+ async def aio(self, /):
190
+ """mdmd:hidden"""
191
+ ...
174
192
 
175
193
  attach: __attach_spec[typing_extensions.Self]
modal/sandbox.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # Copyright Modal Labs 2022
2
2
  import asyncio
3
3
  import os
4
+ import time
4
5
  from collections.abc import AsyncGenerator, Sequence
5
6
  from typing import TYPE_CHECKING, AsyncIterator, Literal, Optional, Union, overload
6
7
 
@@ -49,6 +50,10 @@ _default_image: _Image = _Image.debian_slim()
49
50
  # e.g. 'runsc exec ...'. So we use 2**16 as the limit.
50
51
  ARG_MAX_BYTES = 2**16
51
52
 
53
+
54
+ # This buffer extends the user-supplied timeout on ContainerExec-related RPCs. This was introduced to
55
+ # give any in-flight status codes/IO data more time to reach the client before the stream is closed.
56
+ CONTAINER_EXEC_TIMEOUT_BUFFER = 5
52
57
  if TYPE_CHECKING:
53
58
  import modal.app
54
59
 
@@ -665,7 +670,16 @@ class _Sandbox(_Object, type_prefix="sb"):
665
670
  )
666
671
  resp = await retry_transient_errors(self._client.stub.ContainerExec, req)
667
672
  by_line = bufsize == 1
668
- return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line)
673
+ exec_deadline = time.monotonic() + int(timeout) + CONTAINER_EXEC_TIMEOUT_BUFFER if timeout else None
674
+ return _ContainerProcess(
675
+ resp.exec_id,
676
+ self._client,
677
+ stdout=stdout,
678
+ stderr=stderr,
679
+ text=text,
680
+ exec_deadline=exec_deadline,
681
+ by_line=by_line,
682
+ )
669
683
 
670
684
  async def _experimental_snapshot(self) -> _SandboxSnapshot:
671
685
  await self._get_task_id()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.0.6.dev28
3
+ Version: 1.0.6.dev29
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,14 +22,14 @@ modal/app.py,sha256=fCKq3TJ2Y5LB2WKNs6pp_5XECNH5avUL01jQljuoYRU,46603
22
22
  modal/app.pyi,sha256=Z6wi_dkXywiaM2rvAvguj2Wgu9ZgPjMSLl1nH1a7EYI,42243
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=bOAJKP6U4pNl6nqRcrNKygIFvRoMfAfQZQD1gyL8a8A,15081
25
+ modal/client.pyi,sha256=kOwnZMykQ3S9Yi5-VXdUKycQW9cYb9zfAdbvqdMDsFg,15081
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=EFrM949jNXJpmwB2G_1d28b8IpHShfKIEIaiPkZqeOU,39881
29
29
  modal/cls.pyi,sha256=_tZ5qrlL-ZDEcD-mf9BZkkNH5XPr4SmGTEQ-RVmqF3I,27772
30
30
  modal/config.py,sha256=e8sQ4RgwgJ_45S302vWUWs_wqRlKyEt3tU898RiaDKE,12073
31
- modal/container_process.py,sha256=PDvjcyZ6eeN8foKQgR0WJ66Sg3lt7OFhK7Y_Akz6k5w,5846
32
- modal/container_process.pyi,sha256=E81Zqa6OZ8k_I5SXHb2xz3Sn4vd9SEKd5gyNSr7nBW8,5428
31
+ modal/container_process.py,sha256=dfqa6YFRhNQ4XeZ8cS5DVh-VpzfavA2asrXwrLRHYXg,6780
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
35
35
  modal/environments.py,sha256=gHFNLG78bqgizpQ4w_elz27QOqmcgAonFsmLs7NjUJ4,6804
@@ -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=G_QWzIkVMZL7XOYTdblNvMSQ7dy3hOV9ev9OFWpgqn8,37006
68
+ modal/sandbox.py,sha256=NS2ShX-JxsRYczCe-hz7Iu6TKxJq8OXLMWGx04FJHls,37489
69
69
  modal/sandbox.pyi,sha256=AiZlmZdYHrlqnT8Ba8K5BxNWI1W_oIIkNMhQHF2zqIU,38469
70
70
  modal/schedule.py,sha256=ng0g0AqNY5GQI9KhkXZQ5Wam5G42glbkqVQsNpBtbDE,3078
71
71
  modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
@@ -148,7 +148,7 @@ modal/requirements/2025.06.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqr
148
148
  modal/requirements/PREVIEW.txt,sha256=KxDaVTOwatHvboDo4lorlgJ7-n-MfAwbPwxJ0zcJqrs,312
149
149
  modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,854
150
150
  modal/requirements/base-images.json,sha256=JYSDAgHTl-WrV_TZW5icY-IJEnbe2eQ4CZ_KN6EOZKU,1304
151
- modal-1.0.6.dev28.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
151
+ modal-1.0.6.dev29.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
152
152
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
153
153
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
154
154
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -171,10 +171,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
171
171
  modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
172
172
  modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
173
173
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
174
- modal_version/__init__.py,sha256=PijnDhVVBPiglC-Nb0a6WJVbSmIXqDnSOkkI41TLZw8,121
174
+ modal_version/__init__.py,sha256=4Q7BJww6Va2uUZa6Qogsj9rOLXqxgVTPBeTz5Xgewsw,121
175
175
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
176
- modal-1.0.6.dev28.dist-info/METADATA,sha256=UBi0UEIjplPCBhy7hGLRlexDvUqXQlqq-oaPM51-8Vk,2462
177
- modal-1.0.6.dev28.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
178
- modal-1.0.6.dev28.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
179
- modal-1.0.6.dev28.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
180
- modal-1.0.6.dev28.dist-info/RECORD,,
176
+ modal-1.0.6.dev29.dist-info/METADATA,sha256=NZKx4E7tS0ppkOvq5MCO1luFnlsgGJX8YzdHSCxHIdY,2462
177
+ modal-1.0.6.dev29.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
178
+ modal-1.0.6.dev29.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
179
+ modal-1.0.6.dev29.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
180
+ modal-1.0.6.dev29.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.dev28"
4
+ __version__ = "1.0.6.dev29"