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