modal 1.1.5.dev66__py3-none-any.whl → 1.3.1.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/__init__.py +4 -4
- modal/__main__.py +4 -29
- modal/_billing.py +84 -0
- modal/_clustered_functions.py +1 -3
- modal/_container_entrypoint.py +33 -208
- modal/_functions.py +171 -138
- modal/_grpc_client.py +191 -0
- modal/_ipython.py +16 -6
- modal/_load_context.py +106 -0
- modal/_object.py +72 -21
- modal/_output.py +12 -14
- modal/_partial_function.py +31 -4
- modal/_resolver.py +44 -57
- modal/_runtime/container_io_manager.py +30 -28
- modal/_runtime/container_io_manager.pyi +42 -44
- modal/_runtime/gpu_memory_snapshot.py +9 -7
- modal/_runtime/user_code_event_loop.py +80 -0
- modal/_runtime/user_code_imports.py +236 -10
- modal/_serialization.py +2 -1
- modal/_traceback.py +4 -13
- modal/_tunnel.py +16 -11
- modal/_tunnel.pyi +25 -3
- modal/_utils/async_utils.py +337 -10
- modal/_utils/auth_token_manager.py +1 -4
- modal/_utils/blob_utils.py +29 -22
- modal/_utils/function_utils.py +20 -21
- modal/_utils/grpc_testing.py +6 -3
- modal/_utils/grpc_utils.py +223 -64
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +2 -3
- modal/_utils/package_utils.py +0 -1
- modal/_utils/rand_pb_testing.py +8 -1
- modal/_utils/task_command_router_client.py +524 -0
- modal/_vendor/cloudpickle.py +144 -48
- modal/app.py +285 -105
- modal/app.pyi +216 -53
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +6 -3
- modal/builder/PREVIEW.txt +2 -1
- modal/builder/base-images.json +4 -2
- modal/cli/_download.py +19 -3
- modal/cli/cluster.py +4 -2
- modal/cli/config.py +3 -1
- modal/cli/container.py +5 -4
- modal/cli/dict.py +5 -2
- modal/cli/entry_point.py +26 -2
- modal/cli/environment.py +2 -16
- modal/cli/launch.py +1 -76
- modal/cli/network_file_system.py +5 -20
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +5 -4
- modal/cli/run.py +24 -204
- modal/cli/secret.py +1 -2
- modal/cli/shell.py +375 -0
- modal/cli/utils.py +1 -13
- modal/cli/volume.py +11 -17
- modal/client.py +16 -125
- modal/client.pyi +94 -144
- modal/cloud_bucket_mount.py +3 -1
- modal/cloud_bucket_mount.pyi +4 -0
- modal/cls.py +101 -64
- modal/cls.pyi +9 -8
- modal/config.py +21 -1
- modal/container_process.py +288 -12
- modal/container_process.pyi +99 -38
- modal/dict.py +72 -33
- modal/dict.pyi +88 -57
- modal/environments.py +16 -8
- modal/environments.pyi +6 -2
- modal/exception.py +154 -16
- modal/experimental/__init__.py +24 -53
- modal/experimental/flash.py +161 -74
- modal/experimental/flash.pyi +97 -49
- modal/file_io.py +50 -92
- modal/file_io.pyi +117 -89
- modal/functions.pyi +70 -87
- modal/image.py +82 -47
- modal/image.pyi +51 -30
- modal/io_streams.py +500 -149
- modal/io_streams.pyi +279 -189
- modal/mount.py +60 -46
- modal/mount.pyi +41 -17
- modal/network_file_system.py +19 -11
- modal/network_file_system.pyi +72 -39
- modal/object.pyi +114 -22
- modal/parallel_map.py +42 -44
- modal/parallel_map.pyi +9 -17
- modal/partial_function.pyi +4 -2
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +45 -38
- modal/queue.pyi +88 -52
- modal/runner.py +96 -96
- modal/runner.pyi +44 -27
- modal/sandbox.py +225 -107
- modal/sandbox.pyi +226 -60
- modal/secret.py +58 -56
- modal/secret.pyi +28 -13
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +29 -15
- modal/snapshot.pyi +18 -10
- modal/token_flow.py +1 -1
- modal/token_flow.pyi +4 -6
- modal/volume.py +102 -55
- modal/volume.pyi +125 -66
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
- modal-1.3.1.dev8.dist-info/RECORD +189 -0
- modal_proto/api.proto +141 -70
- modal_proto/api_grpc.py +42 -26
- modal_proto/api_pb2.py +1123 -1103
- modal_proto/api_pb2.pyi +331 -83
- modal_proto/api_pb2_grpc.py +80 -48
- modal_proto/api_pb2_grpc.pyi +26 -18
- modal_proto/modal_api_grpc.py +175 -174
- modal_proto/task_command_router.proto +164 -0
- modal_proto/task_command_router_grpc.py +138 -0
- modal_proto/task_command_router_pb2.py +180 -0
- modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +148 -57
- modal_proto/task_command_router_pb2_grpc.py +272 -0
- modal_proto/task_command_router_pb2_grpc.pyi +100 -0
- modal_version/__init__.py +1 -1
- modal_version/__main__.py +1 -1
- modal/cli/programs/launch_instance_ssh.py +0 -94
- modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.5.dev66.dist-info/RECORD +0 -191
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- modal_proto/sandbox_router.proto +0 -125
- modal_proto/sandbox_router_grpc.py +0 -89
- modal_proto/sandbox_router_pb2.py +0 -128
- modal_proto/sandbox_router_pb2_grpc.py +0 -169
- modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
modal/io_streams.pyi
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections.abc
|
|
2
|
+
import modal._utils.task_command_router_client
|
|
2
3
|
import modal.client
|
|
3
4
|
import modal.stream_type
|
|
4
5
|
import typing
|
|
@@ -17,29 +18,10 @@ def _container_process_logs_iterator(
|
|
|
17
18
|
|
|
18
19
|
T = typing.TypeVar("T")
|
|
19
20
|
|
|
20
|
-
class
|
|
21
|
-
"""
|
|
21
|
+
class _StreamReaderThroughServer(typing.Generic[T]):
|
|
22
|
+
"""A StreamReader implementation that reads from the server."""
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
statements. Just loop over the object to read in chunks.
|
|
25
|
-
|
|
26
|
-
**Usage**
|
|
27
|
-
|
|
28
|
-
```python fixture:running_app
|
|
29
|
-
from modal import Sandbox
|
|
30
|
-
|
|
31
|
-
sandbox = Sandbox.create(
|
|
32
|
-
"bash",
|
|
33
|
-
"-c",
|
|
34
|
-
"for i in $(seq 1 10); do echo foo; sleep 0.1; done",
|
|
35
|
-
app=running_app,
|
|
36
|
-
)
|
|
37
|
-
for message in sandbox.stdout:
|
|
38
|
-
print(f"Message: {message}")
|
|
39
|
-
```
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
_stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
|
|
24
|
+
_stream: typing.Optional[collections.abc.AsyncGenerator[T, None]]
|
|
43
25
|
|
|
44
26
|
def __init__(
|
|
45
27
|
self,
|
|
@@ -61,19 +43,7 @@ class _StreamReader(typing.Generic[T]):
|
|
|
61
43
|
...
|
|
62
44
|
|
|
63
45
|
async def read(self) -> T:
|
|
64
|
-
"""Fetch the entire contents of the stream until EOF.
|
|
65
|
-
|
|
66
|
-
**Usage**
|
|
67
|
-
|
|
68
|
-
```python fixture:running_app
|
|
69
|
-
from modal import Sandbox
|
|
70
|
-
|
|
71
|
-
sandbox = Sandbox.create("echo", "hello", app=running_app)
|
|
72
|
-
sandbox.wait()
|
|
73
|
-
|
|
74
|
-
print(sandbox.stdout.read())
|
|
75
|
-
```
|
|
76
|
-
"""
|
|
46
|
+
"""Fetch the entire contents of the stream until EOF."""
|
|
77
47
|
...
|
|
78
48
|
|
|
79
49
|
async def _consume_container_process_stream(self):
|
|
@@ -84,9 +54,7 @@ class _StreamReader(typing.Generic[T]):
|
|
|
84
54
|
"""Streams the container process buffer to the reader."""
|
|
85
55
|
...
|
|
86
56
|
|
|
87
|
-
def _get_logs(
|
|
88
|
-
self, skip_empty_messages: bool = True
|
|
89
|
-
) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
|
57
|
+
def _get_logs(self, skip_empty_messages: bool = True) -> collections.abc.AsyncGenerator[bytes, None]:
|
|
90
58
|
"""Streams sandbox or process logs from the server to the reader.
|
|
91
59
|
|
|
92
60
|
Logs returned by this method may contain partial or multiple lines at a time.
|
|
@@ -96,24 +64,194 @@ class _StreamReader(typing.Generic[T]):
|
|
|
96
64
|
"""
|
|
97
65
|
...
|
|
98
66
|
|
|
99
|
-
def _get_logs_by_line(self) -> collections.abc.AsyncGenerator[
|
|
67
|
+
def _get_logs_by_line(self) -> collections.abc.AsyncGenerator[bytes, None]:
|
|
100
68
|
"""Process logs from the server and yield complete lines only."""
|
|
101
69
|
...
|
|
102
70
|
|
|
103
|
-
def
|
|
104
|
-
def
|
|
71
|
+
def __aiter__(self) -> collections.abc.AsyncGenerator[T, None]: ...
|
|
72
|
+
async def aclose(self):
|
|
105
73
|
"""mdmd:hidden"""
|
|
106
74
|
...
|
|
107
75
|
|
|
108
|
-
|
|
76
|
+
def _decode_bytes_stream_to_str(
|
|
77
|
+
stream: collections.abc.AsyncGenerator[bytes, None],
|
|
78
|
+
) -> collections.abc.AsyncGenerator[str, None]:
|
|
79
|
+
"""Incrementally decode a bytes async generator as UTF-8 without breaking on chunk boundaries.
|
|
80
|
+
|
|
81
|
+
This function uses a streaming UTF-8 decoder so that multi-byte characters split across
|
|
82
|
+
chunks are handled correctly instead of raising ``UnicodeDecodeError``.
|
|
83
|
+
"""
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
def _stream_by_line(stream: collections.abc.AsyncGenerator[bytes, None]) -> collections.abc.AsyncGenerator[bytes, None]:
|
|
87
|
+
"""Yield complete lines only (ending with
|
|
88
|
+
), buffering partial lines until complete.
|
|
89
|
+
|
|
90
|
+
When this generator returns, the underlying generator is closed.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
class _StreamReaderThroughCommandRouterParams:
|
|
96
|
+
"""_StreamReaderThroughCommandRouterParams(file_descriptor: 'api_pb2.FileDescriptor.ValueType', task_id: str, object_id: str, command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient, deadline: Optional[float])"""
|
|
97
|
+
|
|
98
|
+
file_descriptor: int
|
|
99
|
+
task_id: str
|
|
100
|
+
object_id: str
|
|
101
|
+
command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient
|
|
102
|
+
deadline: typing.Optional[float]
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
file_descriptor: int,
|
|
107
|
+
task_id: str,
|
|
108
|
+
object_id: str,
|
|
109
|
+
command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
|
|
110
|
+
deadline: typing.Optional[float],
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def __repr__(self):
|
|
116
|
+
"""Return repr(self)."""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
def __eq__(self, other):
|
|
120
|
+
"""Return self==value."""
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
def _stdio_stream_from_command_router(
|
|
124
|
+
params: _StreamReaderThroughCommandRouterParams,
|
|
125
|
+
) -> collections.abc.AsyncGenerator[bytes, None]:
|
|
126
|
+
"""Stream raw bytes from the router client."""
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
class _BytesStreamReaderThroughCommandRouter:
|
|
130
|
+
"""StreamReader implementation that will read directly from the worker that
|
|
131
|
+
hosts the sandbox.
|
|
132
|
+
|
|
133
|
+
This implementation is used for non-text streams.
|
|
134
|
+
"""
|
|
135
|
+
def __init__(self, params: _StreamReaderThroughCommandRouterParams) -> None:
|
|
136
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def file_descriptor(self) -> int: ...
|
|
141
|
+
async def read(self) -> bytes: ...
|
|
142
|
+
def __aiter__(self) -> collections.abc.AsyncGenerator[bytes, None]: ...
|
|
143
|
+
async def _print_all(self, output_stream: typing.TextIO) -> None: ...
|
|
144
|
+
|
|
145
|
+
class _TextStreamReaderThroughCommandRouter:
|
|
146
|
+
"""StreamReader implementation that will read directly from the worker
|
|
147
|
+
that hosts the sandbox.
|
|
148
|
+
|
|
149
|
+
This implementation is used for text streams.
|
|
150
|
+
"""
|
|
151
|
+
def __init__(self, params: _StreamReaderThroughCommandRouterParams, by_line: bool) -> None:
|
|
152
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def file_descriptor(self) -> int: ...
|
|
157
|
+
async def read(self) -> str: ...
|
|
158
|
+
def __aiter__(self) -> collections.abc.AsyncGenerator[str, None]: ...
|
|
159
|
+
async def _print_all(self, output_stream: typing.TextIO) -> None: ...
|
|
160
|
+
|
|
161
|
+
class _StdoutPrintingStreamReaderThroughCommandRouter(typing.Generic[T]):
|
|
162
|
+
"""StreamReader implementation for StreamType.STDOUT when using the task command router.
|
|
163
|
+
|
|
164
|
+
This mirrors the behavior from the server-backed implementation: the stream is printed to
|
|
165
|
+
the local stdout immediately and is not readable via StreamReader methods.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
_reader: typing.Union[_TextStreamReaderThroughCommandRouter, _BytesStreamReaderThroughCommandRouter]
|
|
169
|
+
|
|
170
|
+
def __init__(
|
|
171
|
+
self, reader: typing.Union[_TextStreamReaderThroughCommandRouter, _BytesStreamReaderThroughCommandRouter]
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
174
|
+
...
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def file_descriptor(self) -> int: ...
|
|
178
|
+
def _start_printing_task(self) -> None: ...
|
|
179
|
+
async def read(self) -> T: ...
|
|
180
|
+
def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
|
|
181
|
+
async def __anext__(self) -> T: ...
|
|
182
|
+
async def aclose(self): ...
|
|
183
|
+
|
|
184
|
+
class _DevnullStreamReader(typing.Generic[T]):
|
|
185
|
+
"""StreamReader implementation for a stream configured with
|
|
186
|
+
StreamType.DEVNULL. Throws an error if read or any other method is
|
|
187
|
+
called.
|
|
188
|
+
"""
|
|
189
|
+
def __init__(self, file_descriptor: int) -> None:
|
|
190
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
191
|
+
...
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def file_descriptor(self) -> int: ...
|
|
195
|
+
async def read(self) -> T: ...
|
|
196
|
+
def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
|
|
197
|
+
async def __anext__(self) -> T: ...
|
|
198
|
+
async def aclose(self): ...
|
|
199
|
+
|
|
200
|
+
class _StreamReader(typing.Generic[T]):
|
|
201
|
+
"""Retrieve logs from a stream (`stdout` or `stderr`).
|
|
202
|
+
|
|
203
|
+
As an asynchronous iterable, the object supports the `for` and `async for`
|
|
204
|
+
statements. Just loop over the object to read in chunks.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
_impl: typing.Union[
|
|
208
|
+
_StreamReaderThroughServer,
|
|
209
|
+
_DevnullStreamReader,
|
|
210
|
+
_TextStreamReaderThroughCommandRouter,
|
|
211
|
+
_BytesStreamReaderThroughCommandRouter,
|
|
212
|
+
_StdoutPrintingStreamReaderThroughCommandRouter,
|
|
213
|
+
]
|
|
214
|
+
_read_gen: typing.Optional[collections.abc.AsyncGenerator[T, None]]
|
|
215
|
+
|
|
216
|
+
def __init__(
|
|
217
|
+
self,
|
|
218
|
+
file_descriptor: int,
|
|
219
|
+
object_id: str,
|
|
220
|
+
object_type: typing.Literal["sandbox", "container_process"],
|
|
221
|
+
client: modal.client._Client,
|
|
222
|
+
stream_type: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
|
223
|
+
text: bool = True,
|
|
224
|
+
by_line: bool = False,
|
|
225
|
+
deadline: typing.Optional[float] = None,
|
|
226
|
+
command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
|
|
227
|
+
task_id: typing.Optional[str] = None,
|
|
228
|
+
) -> None:
|
|
109
229
|
"""mdmd:hidden"""
|
|
110
230
|
...
|
|
111
231
|
|
|
232
|
+
@property
|
|
233
|
+
def file_descriptor(self) -> int:
|
|
234
|
+
"""Possible values are `1` for stdout and `2` for stderr."""
|
|
235
|
+
...
|
|
236
|
+
|
|
237
|
+
async def read(self) -> T:
|
|
238
|
+
"""Fetch the entire contents of the stream until EOF."""
|
|
239
|
+
...
|
|
240
|
+
|
|
241
|
+
def __aiter__(self) -> collections.abc.AsyncGenerator[T, None]: ...
|
|
242
|
+
async def __anext__(self) -> T:
|
|
243
|
+
"""Deprecated: This exists for backwards compatibility and will be removed in a future version of Modal
|
|
244
|
+
|
|
245
|
+
Only use next/anext on the return value of iter/aiter on the StreamReader object (treat streamreader as
|
|
246
|
+
an iterable, not an iterator).
|
|
247
|
+
"""
|
|
248
|
+
...
|
|
249
|
+
|
|
112
250
|
async def aclose(self):
|
|
113
251
|
"""mdmd:hidden"""
|
|
114
252
|
...
|
|
115
253
|
|
|
116
|
-
class
|
|
254
|
+
class _StreamWriterThroughServer:
|
|
117
255
|
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
|
118
256
|
def __init__(
|
|
119
257
|
self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client._Client
|
|
@@ -127,24 +265,71 @@ class _StreamWriter:
|
|
|
127
265
|
|
|
128
266
|
This is non-blocking and queues the data to an internal buffer. Must be
|
|
129
267
|
used along with the `drain()` method, which flushes the buffer.
|
|
268
|
+
"""
|
|
269
|
+
...
|
|
130
270
|
|
|
131
|
-
|
|
271
|
+
def write_eof(self) -> None:
|
|
272
|
+
"""Close the write end of the stream after the buffered data is drained.
|
|
273
|
+
|
|
274
|
+
If the process was blocked on input, it will become unblocked after
|
|
275
|
+
`write_eof()`. This method needs to be used along with the `drain()`
|
|
276
|
+
method, which flushes the EOF to the process.
|
|
277
|
+
"""
|
|
278
|
+
...
|
|
279
|
+
|
|
280
|
+
async def drain(self) -> None:
|
|
281
|
+
"""Flush the write buffer and send data to the running process.
|
|
282
|
+
|
|
283
|
+
This is a flow control method that blocks until data is sent. It returns
|
|
284
|
+
when it is appropriate to continue writing data to the stream.
|
|
285
|
+
"""
|
|
286
|
+
...
|
|
287
|
+
|
|
288
|
+
class _StreamWriterThroughCommandRouter:
|
|
289
|
+
def __init__(
|
|
290
|
+
self,
|
|
291
|
+
object_id: str,
|
|
292
|
+
command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
|
|
293
|
+
task_id: str,
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
296
|
+
...
|
|
297
|
+
|
|
298
|
+
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None: ...
|
|
299
|
+
def write_eof(self) -> None: ...
|
|
300
|
+
async def drain(self) -> None: ...
|
|
301
|
+
|
|
302
|
+
class _StreamWriter:
|
|
303
|
+
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
|
304
|
+
def __init__(
|
|
305
|
+
self,
|
|
306
|
+
object_id: str,
|
|
307
|
+
object_type: typing.Literal["sandbox", "container_process"],
|
|
308
|
+
client: modal.client._Client,
|
|
309
|
+
command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
|
|
310
|
+
task_id: typing.Optional[str] = None,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""mdmd:hidden"""
|
|
313
|
+
...
|
|
132
314
|
|
|
133
|
-
|
|
134
|
-
|
|
315
|
+
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
|
316
|
+
"""Write data to the stream but does not send it immediately.
|
|
135
317
|
|
|
136
|
-
|
|
318
|
+
This is non-blocking and queues the data to an internal buffer. Must be
|
|
319
|
+
used along with the `drain()` method, which flushes the buffer.
|
|
320
|
+
|
|
321
|
+
**Usage**
|
|
322
|
+
|
|
323
|
+
```python fixture:sandbox
|
|
324
|
+
proc = sandbox.exec(
|
|
137
325
|
"bash",
|
|
138
326
|
"-c",
|
|
139
327
|
"while read line; do echo $line; done",
|
|
140
|
-
app=running_app,
|
|
141
328
|
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
sandbox.stdin.drain()
|
|
147
|
-
sandbox.wait()
|
|
329
|
+
proc.stdin.write(b"foo\n")
|
|
330
|
+
proc.stdin.write(b"bar\n")
|
|
331
|
+
proc.stdin.write_eof()
|
|
332
|
+
proc.stdin.drain()
|
|
148
333
|
```
|
|
149
334
|
"""
|
|
150
335
|
...
|
|
@@ -181,31 +366,21 @@ class _StreamWriter:
|
|
|
181
366
|
|
|
182
367
|
T_INNER = typing.TypeVar("T_INNER", covariant=True)
|
|
183
368
|
|
|
184
|
-
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
185
|
-
|
|
186
369
|
class StreamReader(typing.Generic[T]):
|
|
187
370
|
"""Retrieve logs from a stream (`stdout` or `stderr`).
|
|
188
371
|
|
|
189
372
|
As an asynchronous iterable, the object supports the `for` and `async for`
|
|
190
373
|
statements. Just loop over the object to read in chunks.
|
|
191
|
-
|
|
192
|
-
**Usage**
|
|
193
|
-
|
|
194
|
-
```python fixture:running_app
|
|
195
|
-
from modal import Sandbox
|
|
196
|
-
|
|
197
|
-
sandbox = Sandbox.create(
|
|
198
|
-
"bash",
|
|
199
|
-
"-c",
|
|
200
|
-
"for i in $(seq 1 10); do echo foo; sleep 0.1; done",
|
|
201
|
-
app=running_app,
|
|
202
|
-
)
|
|
203
|
-
for message in sandbox.stdout:
|
|
204
|
-
print(f"Message: {message}")
|
|
205
|
-
```
|
|
206
374
|
"""
|
|
207
375
|
|
|
208
|
-
|
|
376
|
+
_impl: typing.Union[
|
|
377
|
+
_StreamReaderThroughServer,
|
|
378
|
+
_DevnullStreamReader,
|
|
379
|
+
_TextStreamReaderThroughCommandRouter,
|
|
380
|
+
_BytesStreamReaderThroughCommandRouter,
|
|
381
|
+
_StdoutPrintingStreamReaderThroughCommandRouter,
|
|
382
|
+
]
|
|
383
|
+
_read_gen: typing.Optional[collections.abc.AsyncGenerator[T, None]]
|
|
209
384
|
|
|
210
385
|
def __init__(
|
|
211
386
|
self,
|
|
@@ -217,6 +392,8 @@ class StreamReader(typing.Generic[T]):
|
|
|
217
392
|
text: bool = True,
|
|
218
393
|
by_line: bool = False,
|
|
219
394
|
deadline: typing.Optional[float] = None,
|
|
395
|
+
command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
|
|
396
|
+
task_id: typing.Optional[str] = None,
|
|
220
397
|
) -> None:
|
|
221
398
|
"""mdmd:hidden"""
|
|
222
399
|
...
|
|
@@ -226,119 +403,33 @@ class StreamReader(typing.Generic[T]):
|
|
|
226
403
|
"""Possible values are `1` for stdout and `2` for stderr."""
|
|
227
404
|
...
|
|
228
405
|
|
|
229
|
-
class __read_spec(typing_extensions.Protocol[T_INNER
|
|
406
|
+
class __read_spec(typing_extensions.Protocol[T_INNER]):
|
|
230
407
|
def __call__(self, /) -> T_INNER:
|
|
231
|
-
"""Fetch the entire contents of the stream until EOF.
|
|
232
|
-
|
|
233
|
-
**Usage**
|
|
234
|
-
|
|
235
|
-
```python fixture:running_app
|
|
236
|
-
from modal import Sandbox
|
|
237
|
-
|
|
238
|
-
sandbox = Sandbox.create("echo", "hello", app=running_app)
|
|
239
|
-
sandbox.wait()
|
|
240
|
-
|
|
241
|
-
print(sandbox.stdout.read())
|
|
242
|
-
```
|
|
243
|
-
"""
|
|
408
|
+
"""Fetch the entire contents of the stream until EOF."""
|
|
244
409
|
...
|
|
245
410
|
|
|
246
411
|
async def aio(self, /) -> T_INNER:
|
|
247
|
-
"""Fetch the entire contents of the stream until EOF.
|
|
248
|
-
|
|
249
|
-
**Usage**
|
|
250
|
-
|
|
251
|
-
```python fixture:running_app
|
|
252
|
-
from modal import Sandbox
|
|
253
|
-
|
|
254
|
-
sandbox = Sandbox.create("echo", "hello", app=running_app)
|
|
255
|
-
sandbox.wait()
|
|
256
|
-
|
|
257
|
-
print(sandbox.stdout.read())
|
|
258
|
-
```
|
|
259
|
-
"""
|
|
260
|
-
...
|
|
261
|
-
|
|
262
|
-
read: __read_spec[T, typing_extensions.Self]
|
|
263
|
-
|
|
264
|
-
class ___consume_container_process_stream_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
265
|
-
def __call__(self, /):
|
|
266
|
-
"""Consume the container process stream and store messages in the buffer."""
|
|
267
|
-
...
|
|
268
|
-
|
|
269
|
-
async def aio(self, /):
|
|
270
|
-
"""Consume the container process stream and store messages in the buffer."""
|
|
271
|
-
...
|
|
272
|
-
|
|
273
|
-
_consume_container_process_stream: ___consume_container_process_stream_spec[typing_extensions.Self]
|
|
274
|
-
|
|
275
|
-
class ___stream_container_process_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
276
|
-
def __call__(self, /) -> typing.Generator[tuple[typing.Optional[bytes], str], None, None]:
|
|
277
|
-
"""Streams the container process buffer to the reader."""
|
|
278
|
-
...
|
|
279
|
-
|
|
280
|
-
def aio(self, /) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]:
|
|
281
|
-
"""Streams the container process buffer to the reader."""
|
|
282
|
-
...
|
|
283
|
-
|
|
284
|
-
_stream_container_process: ___stream_container_process_spec[typing_extensions.Self]
|
|
285
|
-
|
|
286
|
-
class ___get_logs_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
287
|
-
def __call__(self, /, skip_empty_messages: bool = True) -> typing.Generator[typing.Optional[bytes], None, None]:
|
|
288
|
-
"""Streams sandbox or process logs from the server to the reader.
|
|
289
|
-
|
|
290
|
-
Logs returned by this method may contain partial or multiple lines at a time.
|
|
291
|
-
|
|
292
|
-
When the stream receives an EOF, it yields None. Once an EOF is received,
|
|
293
|
-
subsequent invocations will not yield logs.
|
|
294
|
-
"""
|
|
295
|
-
...
|
|
296
|
-
|
|
297
|
-
def aio(
|
|
298
|
-
self, /, skip_empty_messages: bool = True
|
|
299
|
-
) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
|
300
|
-
"""Streams sandbox or process logs from the server to the reader.
|
|
301
|
-
|
|
302
|
-
Logs returned by this method may contain partial or multiple lines at a time.
|
|
303
|
-
|
|
304
|
-
When the stream receives an EOF, it yields None. Once an EOF is received,
|
|
305
|
-
subsequent invocations will not yield logs.
|
|
306
|
-
"""
|
|
412
|
+
"""Fetch the entire contents of the stream until EOF."""
|
|
307
413
|
...
|
|
308
414
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
class ___get_logs_by_line_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
312
|
-
def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]:
|
|
313
|
-
"""Process logs from the server and yield complete lines only."""
|
|
314
|
-
...
|
|
315
|
-
|
|
316
|
-
def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
|
317
|
-
"""Process logs from the server and yield complete lines only."""
|
|
318
|
-
...
|
|
319
|
-
|
|
320
|
-
_get_logs_by_line: ___get_logs_by_line_spec[typing_extensions.Self]
|
|
321
|
-
|
|
322
|
-
class ___ensure_stream_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
323
|
-
def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]: ...
|
|
324
|
-
def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
|
|
325
|
-
|
|
326
|
-
_ensure_stream: ___ensure_stream_spec[typing_extensions.Self]
|
|
327
|
-
|
|
328
|
-
def __iter__(self) -> typing.Iterator[T]:
|
|
329
|
-
"""mdmd:hidden"""
|
|
330
|
-
...
|
|
331
|
-
|
|
332
|
-
def __aiter__(self) -> collections.abc.AsyncIterator[T]:
|
|
333
|
-
"""mdmd:hidden"""
|
|
334
|
-
...
|
|
415
|
+
read: __read_spec[T]
|
|
335
416
|
|
|
417
|
+
def __iter__(self) -> typing.Generator[T, None, None]: ...
|
|
418
|
+
def __aiter__(self) -> collections.abc.AsyncGenerator[T, None]: ...
|
|
336
419
|
def __next__(self) -> T:
|
|
337
|
-
"""
|
|
420
|
+
"""Deprecated: This exists for backwards compatibility and will be removed in a future version of Modal
|
|
421
|
+
|
|
422
|
+
Only use next/anext on the return value of iter/aiter on the StreamReader object (treat streamreader as
|
|
423
|
+
an iterable, not an iterator).
|
|
424
|
+
"""
|
|
338
425
|
...
|
|
339
426
|
|
|
340
427
|
async def __anext__(self) -> T:
|
|
341
|
-
"""
|
|
428
|
+
"""Deprecated: This exists for backwards compatibility and will be removed in a future version of Modal
|
|
429
|
+
|
|
430
|
+
Only use next/anext on the return value of iter/aiter on the StreamReader object (treat streamreader as
|
|
431
|
+
an iterable, not an iterator).
|
|
432
|
+
"""
|
|
342
433
|
...
|
|
343
434
|
|
|
344
435
|
def close(self):
|
|
@@ -352,12 +443,16 @@ class StreamReader(typing.Generic[T]):
|
|
|
352
443
|
class StreamWriter:
|
|
353
444
|
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
|
354
445
|
def __init__(
|
|
355
|
-
self,
|
|
446
|
+
self,
|
|
447
|
+
object_id: str,
|
|
448
|
+
object_type: typing.Literal["sandbox", "container_process"],
|
|
449
|
+
client: modal.client.Client,
|
|
450
|
+
command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
|
|
451
|
+
task_id: typing.Optional[str] = None,
|
|
356
452
|
) -> None:
|
|
357
453
|
"""mdmd:hidden"""
|
|
358
454
|
...
|
|
359
455
|
|
|
360
|
-
def _get_next_index(self) -> int: ...
|
|
361
456
|
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
|
362
457
|
"""Write data to the stream but does not send it immediately.
|
|
363
458
|
|
|
@@ -366,21 +461,16 @@ class StreamWriter:
|
|
|
366
461
|
|
|
367
462
|
**Usage**
|
|
368
463
|
|
|
369
|
-
```python fixture:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
sandbox = Sandbox.create(
|
|
464
|
+
```python fixture:sandbox
|
|
465
|
+
proc = sandbox.exec(
|
|
373
466
|
"bash",
|
|
374
467
|
"-c",
|
|
375
468
|
"while read line; do echo $line; done",
|
|
376
|
-
app=running_app,
|
|
377
469
|
)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
sandbox.stdin.drain()
|
|
383
|
-
sandbox.wait()
|
|
470
|
+
proc.stdin.write(b"foo\n")
|
|
471
|
+
proc.stdin.write(b"bar\n")
|
|
472
|
+
proc.stdin.write_eof()
|
|
473
|
+
proc.stdin.drain()
|
|
384
474
|
```
|
|
385
475
|
"""
|
|
386
476
|
...
|
|
@@ -394,7 +484,7 @@ class StreamWriter:
|
|
|
394
484
|
"""
|
|
395
485
|
...
|
|
396
486
|
|
|
397
|
-
class __drain_spec(typing_extensions.Protocol
|
|
487
|
+
class __drain_spec(typing_extensions.Protocol):
|
|
398
488
|
def __call__(self, /) -> None:
|
|
399
489
|
"""Flush the write buffer and send data to the running process.
|
|
400
490
|
|
|
@@ -437,4 +527,4 @@ class StreamWriter:
|
|
|
437
527
|
"""
|
|
438
528
|
...
|
|
439
529
|
|
|
440
|
-
drain: __drain_spec
|
|
530
|
+
drain: __drain_spec
|