modal 1.2.1.dev8__py3-none-any.whl → 1.2.2.dev19__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/_clustered_functions.py +1 -3
- modal/_container_entrypoint.py +4 -1
- modal/_functions.py +33 -49
- modal/_grpc_client.py +148 -0
- modal/_output.py +3 -4
- modal/_partial_function.py +22 -2
- modal/_runtime/container_io_manager.py +21 -22
- modal/_utils/async_utils.py +12 -3
- modal/_utils/auth_token_manager.py +1 -4
- modal/_utils/blob_utils.py +3 -4
- modal/_utils/function_utils.py +4 -0
- modal/_utils/grpc_utils.py +80 -51
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/task_command_router_client.py +536 -0
- modal/app.py +7 -5
- modal/cli/cluster.py +4 -2
- modal/cli/config.py +3 -1
- modal/cli/container.py +5 -4
- modal/cli/entry_point.py +1 -0
- modal/cli/launch.py +1 -2
- modal/cli/network_file_system.py +1 -4
- modal/cli/queues.py +1 -2
- modal/cli/secret.py +1 -2
- modal/client.py +5 -115
- modal/client.pyi +2 -91
- modal/cls.py +1 -2
- modal/config.py +3 -1
- modal/container_process.py +287 -11
- modal/container_process.pyi +95 -32
- modal/dict.py +12 -12
- modal/environments.py +1 -2
- modal/exception.py +4 -0
- modal/experimental/__init__.py +2 -3
- modal/experimental/flash.py +27 -57
- modal/experimental/flash.pyi +6 -20
- modal/file_io.py +13 -27
- modal/functions.pyi +6 -6
- modal/image.py +24 -3
- modal/image.pyi +4 -0
- modal/io_streams.py +433 -127
- modal/io_streams.pyi +236 -171
- modal/mount.py +4 -4
- modal/network_file_system.py +5 -6
- modal/parallel_map.py +29 -31
- modal/parallel_map.pyi +3 -9
- modal/partial_function.pyi +4 -1
- modal/queue.py +17 -18
- modal/runner.py +12 -11
- modal/sandbox.py +148 -42
- modal/sandbox.pyi +139 -0
- modal/secret.py +4 -5
- modal/snapshot.py +1 -4
- modal/token_flow.py +1 -1
- modal/volume.py +22 -22
- {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/METADATA +1 -1
- {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/RECORD +70 -68
- modal_proto/api.proto +2 -24
- modal_proto/api_grpc.py +0 -32
- modal_proto/api_pb2.py +838 -878
- modal_proto/api_pb2.pyi +8 -70
- modal_proto/api_pb2_grpc.py +0 -67
- modal_proto/api_pb2_grpc.pyi +0 -22
- modal_proto/modal_api_grpc.py +175 -177
- modal_proto/sandbox_router.proto +0 -4
- modal_proto/sandbox_router_pb2.pyi +0 -4
- modal_version/__init__.py +1 -1
- {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/WHEEL +0 -0
- {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/entry_points.txt +0 -0
- {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/licenses/LICENSE +0 -0
- {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.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
|
-
"""
|
|
22
|
-
|
|
23
|
-
As an asynchronous iterable, the object supports the `for` and `async for`
|
|
24
|
-
statements. Just loop over the object to read in chunks.
|
|
25
|
-
|
|
26
|
-
**Usage**
|
|
21
|
+
class _StreamReaderThroughServer(typing.Generic[T]):
|
|
22
|
+
"""A StreamReader implementation that reads from the server."""
|
|
27
23
|
|
|
28
|
-
|
|
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,11 +64,158 @@ 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 _ensure_stream(self) -> collections.abc.AsyncGenerator[
|
|
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
|
+
|
|
140
|
+
@property
|
|
141
|
+
def file_descriptor(self) -> int: ...
|
|
142
|
+
async def read(self) -> T: ...
|
|
143
|
+
def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
|
|
144
|
+
async def __anext__(self) -> T: ...
|
|
145
|
+
async def aclose(self): ...
|
|
146
|
+
|
|
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
|
+
|
|
104
219
|
def __aiter__(self) -> collections.abc.AsyncIterator[T]:
|
|
105
220
|
"""mdmd:hidden"""
|
|
106
221
|
...
|
|
@@ -113,7 +228,7 @@ class _StreamReader(typing.Generic[T]):
|
|
|
113
228
|
"""mdmd:hidden"""
|
|
114
229
|
...
|
|
115
230
|
|
|
116
|
-
class
|
|
231
|
+
class _StreamWriterThroughServer:
|
|
117
232
|
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
|
118
233
|
def __init__(
|
|
119
234
|
self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client._Client
|
|
@@ -127,24 +242,71 @@ class _StreamWriter:
|
|
|
127
242
|
|
|
128
243
|
This is non-blocking and queues the data to an internal buffer. Must be
|
|
129
244
|
used along with the `drain()` method, which flushes the buffer.
|
|
245
|
+
"""
|
|
246
|
+
...
|
|
130
247
|
|
|
131
|
-
|
|
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
|
+
...
|
|
132
274
|
|
|
133
|
-
|
|
134
|
-
|
|
275
|
+
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None: ...
|
|
276
|
+
def write_eof(self) -> None: ...
|
|
277
|
+
async def drain(self) -> None: ...
|
|
135
278
|
|
|
136
|
-
|
|
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(
|
|
137
302
|
"bash",
|
|
138
303
|
"-c",
|
|
139
304
|
"while read line; do echo $line; done",
|
|
140
|
-
app=running_app,
|
|
141
305
|
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
sandbox.stdin.drain()
|
|
147
|
-
sandbox.wait()
|
|
306
|
+
proc.stdin.write(b"foo\n")
|
|
307
|
+
proc.stdin.write(b"bar\n")
|
|
308
|
+
proc.stdin.write_eof()
|
|
309
|
+
proc.stdin.drain()
|
|
148
310
|
```
|
|
149
311
|
"""
|
|
150
312
|
...
|
|
@@ -188,24 +350,14 @@ class StreamReader(typing.Generic[T]):
|
|
|
188
350
|
|
|
189
351
|
As an asynchronous iterable, the object supports the `for` and `async for`
|
|
190
352
|
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
353
|
"""
|
|
207
354
|
|
|
208
|
-
|
|
355
|
+
_impl: typing.Union[
|
|
356
|
+
_StreamReaderThroughServer,
|
|
357
|
+
_DevnullStreamReader,
|
|
358
|
+
_TextStreamReaderThroughCommandRouter,
|
|
359
|
+
_BytesStreamReaderThroughCommandRouter,
|
|
360
|
+
]
|
|
209
361
|
|
|
210
362
|
def __init__(
|
|
211
363
|
self,
|
|
@@ -217,6 +369,8 @@ class StreamReader(typing.Generic[T]):
|
|
|
217
369
|
text: bool = True,
|
|
218
370
|
by_line: bool = False,
|
|
219
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,
|
|
220
374
|
) -> None:
|
|
221
375
|
"""mdmd:hidden"""
|
|
222
376
|
...
|
|
@@ -228,103 +382,15 @@ class StreamReader(typing.Generic[T]):
|
|
|
228
382
|
|
|
229
383
|
class __read_spec(typing_extensions.Protocol[T_INNER, SUPERSELF]):
|
|
230
384
|
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
|
-
"""
|
|
385
|
+
"""Fetch the entire contents of the stream until EOF."""
|
|
244
386
|
...
|
|
245
387
|
|
|
246
388
|
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
|
-
"""
|
|
389
|
+
"""Fetch the entire contents of the stream until EOF."""
|
|
260
390
|
...
|
|
261
391
|
|
|
262
392
|
read: __read_spec[T, typing_extensions.Self]
|
|
263
393
|
|
|
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
|
-
"""
|
|
307
|
-
...
|
|
308
|
-
|
|
309
|
-
_get_logs: ___get_logs_spec[typing_extensions.Self]
|
|
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
394
|
def __iter__(self) -> typing.Iterator[T]:
|
|
329
395
|
"""mdmd:hidden"""
|
|
330
396
|
...
|
|
@@ -352,12 +418,16 @@ class StreamReader(typing.Generic[T]):
|
|
|
352
418
|
class StreamWriter:
|
|
353
419
|
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
|
354
420
|
def __init__(
|
|
355
|
-
self,
|
|
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,
|
|
356
427
|
) -> None:
|
|
357
428
|
"""mdmd:hidden"""
|
|
358
429
|
...
|
|
359
430
|
|
|
360
|
-
def _get_next_index(self) -> int: ...
|
|
361
431
|
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
|
362
432
|
"""Write data to the stream but does not send it immediately.
|
|
363
433
|
|
|
@@ -366,21 +436,16 @@ class StreamWriter:
|
|
|
366
436
|
|
|
367
437
|
**Usage**
|
|
368
438
|
|
|
369
|
-
```python fixture:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
sandbox = Sandbox.create(
|
|
439
|
+
```python fixture:sandbox
|
|
440
|
+
proc = sandbox.exec(
|
|
373
441
|
"bash",
|
|
374
442
|
"-c",
|
|
375
443
|
"while read line; do echo $line; done",
|
|
376
|
-
app=running_app,
|
|
377
444
|
)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
sandbox.stdin.drain()
|
|
383
|
-
sandbox.wait()
|
|
445
|
+
proc.stdin.write(b"foo\n")
|
|
446
|
+
proc.stdin.write(b"bar\n")
|
|
447
|
+
proc.stdin.write_eof()
|
|
448
|
+
proc.stdin.drain()
|
|
384
449
|
```
|
|
385
450
|
"""
|
|
386
451
|
...
|
modal/mount.py
CHANGED
|
@@ -24,7 +24,7 @@ from ._object import _get_environment_name, _Object
|
|
|
24
24
|
from ._resolver import Resolver
|
|
25
25
|
from ._utils.async_utils import TaskContext, aclosing, async_map, synchronize_api
|
|
26
26
|
from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
|
|
27
|
-
from ._utils.grpc_utils import
|
|
27
|
+
from ._utils.grpc_utils import Retry
|
|
28
28
|
from ._utils.name_utils import check_object_name
|
|
29
29
|
from ._utils.package_utils import get_module_mount_info
|
|
30
30
|
from .client import _Client
|
|
@@ -518,7 +518,7 @@ class _Mount(_Object, type_prefix="mo"):
|
|
|
518
518
|
|
|
519
519
|
request = api_pb2.MountPutFileRequest(sha256_hex=file_spec.sha256_hex)
|
|
520
520
|
accounted_hashes.add(file_spec.sha256_hex)
|
|
521
|
-
response = await
|
|
521
|
+
response = await resolver.client.stub.MountPutFile(request, retry=Retry(base_delay=1))
|
|
522
522
|
|
|
523
523
|
if response.exists:
|
|
524
524
|
n_finished += 1
|
|
@@ -544,7 +544,7 @@ class _Mount(_Object, type_prefix="mo"):
|
|
|
544
544
|
|
|
545
545
|
start_time = time.monotonic()
|
|
546
546
|
while time.monotonic() - start_time < MOUNT_PUT_FILE_CLIENT_TIMEOUT:
|
|
547
|
-
response = await
|
|
547
|
+
response = await resolver.client.stub.MountPutFile(request2, retry=Retry(base_delay=1))
|
|
548
548
|
if response.exists:
|
|
549
549
|
n_finished += 1
|
|
550
550
|
return mount_file
|
|
@@ -591,7 +591,7 @@ class _Mount(_Object, type_prefix="mo"):
|
|
|
591
591
|
environment_name=resolver.environment_name,
|
|
592
592
|
)
|
|
593
593
|
|
|
594
|
-
resp = await
|
|
594
|
+
resp = await resolver.client.stub.MountGetOrCreate(req, retry=Retry(base_delay=1))
|
|
595
595
|
status_row.finish(f"Created mount {message_label}")
|
|
596
596
|
|
|
597
597
|
logger.debug(f"Uploaded {total_uploads} new files and {total_bytes} bytes in {time.monotonic() - t0}s")
|
modal/network_file_system.py
CHANGED
|
@@ -22,7 +22,6 @@ from ._resolver import Resolver
|
|
|
22
22
|
from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
|
|
23
23
|
from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
|
|
24
24
|
from ._utils.deprecation import warn_if_passing_namespace
|
|
25
|
-
from ._utils.grpc_utils import retry_transient_errors
|
|
26
25
|
from ._utils.hash_utils import get_sha256_hex
|
|
27
26
|
from ._utils.name_utils import check_object_name
|
|
28
27
|
from .client import _Client
|
|
@@ -188,14 +187,14 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
188
187
|
environment_name=_get_environment_name(environment_name),
|
|
189
188
|
object_creation_type=api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS,
|
|
190
189
|
)
|
|
191
|
-
resp = await
|
|
190
|
+
resp = await client.stub.SharedVolumeGetOrCreate(request)
|
|
192
191
|
return resp.shared_volume_id
|
|
193
192
|
|
|
194
193
|
@staticmethod
|
|
195
194
|
async def delete(name: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
|
|
196
195
|
obj = await _NetworkFileSystem.from_name(name, environment_name=environment_name).hydrate(client)
|
|
197
196
|
req = api_pb2.SharedVolumeDeleteRequest(shared_volume_id=obj.object_id)
|
|
198
|
-
await
|
|
197
|
+
await obj._client.stub.SharedVolumeDelete(req)
|
|
199
198
|
|
|
200
199
|
@live_method
|
|
201
200
|
async def write_file(self, remote_path: str, fp: BinaryIO, progress_cb: Optional[Callable[..., Any]] = None) -> int:
|
|
@@ -235,7 +234,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
235
234
|
|
|
236
235
|
t0 = time.monotonic()
|
|
237
236
|
while time.monotonic() - t0 < NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT:
|
|
238
|
-
response = await
|
|
237
|
+
response = await self._client.stub.SharedVolumePutFile(req)
|
|
239
238
|
if response.exists:
|
|
240
239
|
break
|
|
241
240
|
else:
|
|
@@ -248,7 +247,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
248
247
|
"""Read a file from the network file system"""
|
|
249
248
|
req = api_pb2.SharedVolumeGetFileRequest(shared_volume_id=self.object_id, path=path)
|
|
250
249
|
try:
|
|
251
|
-
response = await
|
|
250
|
+
response = await self._client.stub.SharedVolumeGetFile(req)
|
|
252
251
|
except modal.exception.NotFoundError as exc:
|
|
253
252
|
raise FileNotFoundError(exc.args[0])
|
|
254
253
|
|
|
@@ -333,7 +332,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
|
333
332
|
"""Remove a file in a network file system."""
|
|
334
333
|
req = api_pb2.SharedVolumeRemoveFileRequest(shared_volume_id=self.object_id, path=path, recursive=recursive)
|
|
335
334
|
try:
|
|
336
|
-
await
|
|
335
|
+
await self._client.stub.SharedVolumeRemoveFile(req)
|
|
337
336
|
except modal.exception.NotFoundError as exc:
|
|
338
337
|
raise FileNotFoundError(exc.args[0])
|
|
339
338
|
|