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.

Files changed (160) hide show
  1. modal/__init__.py +0 -2
  2. modal/__main__.py +3 -4
  3. modal/_billing.py +80 -0
  4. modal/_clustered_functions.py +7 -3
  5. modal/_clustered_functions.pyi +15 -3
  6. modal/_container_entrypoint.py +51 -69
  7. modal/_functions.py +508 -240
  8. modal/_grpc_client.py +171 -0
  9. modal/_load_context.py +105 -0
  10. modal/_object.py +81 -21
  11. modal/_output.py +58 -45
  12. modal/_partial_function.py +48 -73
  13. modal/_pty.py +7 -3
  14. modal/_resolver.py +26 -46
  15. modal/_runtime/asgi.py +4 -3
  16. modal/_runtime/container_io_manager.py +358 -220
  17. modal/_runtime/container_io_manager.pyi +296 -101
  18. modal/_runtime/execution_context.py +18 -2
  19. modal/_runtime/execution_context.pyi +64 -7
  20. modal/_runtime/gpu_memory_snapshot.py +262 -57
  21. modal/_runtime/user_code_imports.py +28 -58
  22. modal/_serialization.py +90 -6
  23. modal/_traceback.py +42 -1
  24. modal/_tunnel.pyi +380 -12
  25. modal/_utils/async_utils.py +84 -29
  26. modal/_utils/auth_token_manager.py +111 -0
  27. modal/_utils/blob_utils.py +181 -58
  28. modal/_utils/deprecation.py +19 -0
  29. modal/_utils/function_utils.py +91 -47
  30. modal/_utils/grpc_utils.py +89 -66
  31. modal/_utils/mount_utils.py +26 -1
  32. modal/_utils/name_utils.py +17 -3
  33. modal/_utils/task_command_router_client.py +536 -0
  34. modal/_utils/time_utils.py +34 -6
  35. modal/app.py +256 -88
  36. modal/app.pyi +909 -92
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +18 -0
  39. modal/builder/PREVIEW.txt +18 -0
  40. modal/builder/base-images.json +58 -0
  41. modal/cli/_download.py +19 -3
  42. modal/cli/_traceback.py +3 -2
  43. modal/cli/app.py +4 -4
  44. modal/cli/cluster.py +15 -7
  45. modal/cli/config.py +5 -3
  46. modal/cli/container.py +7 -6
  47. modal/cli/dict.py +22 -16
  48. modal/cli/entry_point.py +12 -5
  49. modal/cli/environment.py +5 -4
  50. modal/cli/import_refs.py +3 -3
  51. modal/cli/launch.py +102 -5
  52. modal/cli/network_file_system.py +11 -12
  53. modal/cli/profile.py +3 -2
  54. modal/cli/programs/launch_instance_ssh.py +94 -0
  55. modal/cli/programs/run_jupyter.py +1 -1
  56. modal/cli/programs/run_marimo.py +95 -0
  57. modal/cli/programs/vscode.py +1 -1
  58. modal/cli/queues.py +57 -26
  59. modal/cli/run.py +91 -23
  60. modal/cli/secret.py +48 -22
  61. modal/cli/token.py +7 -8
  62. modal/cli/utils.py +4 -7
  63. modal/cli/volume.py +31 -25
  64. modal/client.py +15 -85
  65. modal/client.pyi +183 -62
  66. modal/cloud_bucket_mount.py +5 -3
  67. modal/cloud_bucket_mount.pyi +197 -5
  68. modal/cls.py +200 -126
  69. modal/cls.pyi +446 -68
  70. modal/config.py +29 -11
  71. modal/container_process.py +319 -19
  72. modal/container_process.pyi +190 -20
  73. modal/dict.py +290 -71
  74. modal/dict.pyi +835 -83
  75. modal/environments.py +15 -27
  76. modal/environments.pyi +46 -24
  77. modal/exception.py +14 -2
  78. modal/experimental/__init__.py +194 -40
  79. modal/experimental/flash.py +618 -0
  80. modal/experimental/flash.pyi +380 -0
  81. modal/experimental/ipython.py +11 -7
  82. modal/file_io.py +29 -36
  83. modal/file_io.pyi +251 -53
  84. modal/file_pattern_matcher.py +56 -16
  85. modal/functions.pyi +673 -92
  86. modal/gpu.py +1 -1
  87. modal/image.py +528 -176
  88. modal/image.pyi +1572 -145
  89. modal/io_streams.py +458 -128
  90. modal/io_streams.pyi +433 -52
  91. modal/mount.py +216 -151
  92. modal/mount.pyi +225 -78
  93. modal/network_file_system.py +45 -62
  94. modal/network_file_system.pyi +277 -56
  95. modal/object.pyi +93 -17
  96. modal/parallel_map.py +942 -129
  97. modal/parallel_map.pyi +294 -15
  98. modal/partial_function.py +0 -2
  99. modal/partial_function.pyi +234 -19
  100. modal/proxy.py +17 -8
  101. modal/proxy.pyi +36 -3
  102. modal/queue.py +270 -65
  103. modal/queue.pyi +817 -57
  104. modal/runner.py +115 -101
  105. modal/runner.pyi +205 -49
  106. modal/sandbox.py +512 -136
  107. modal/sandbox.pyi +845 -111
  108. modal/schedule.py +1 -1
  109. modal/secret.py +300 -70
  110. modal/secret.pyi +589 -34
  111. modal/serving.py +7 -11
  112. modal/serving.pyi +7 -8
  113. modal/snapshot.py +11 -8
  114. modal/snapshot.pyi +25 -4
  115. modal/token_flow.py +4 -4
  116. modal/token_flow.pyi +28 -8
  117. modal/volume.py +416 -158
  118. modal/volume.pyi +1117 -121
  119. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +10 -9
  120. modal-1.2.3.dev7.dist-info/RECORD +195 -0
  121. modal_docs/mdmd/mdmd.py +17 -4
  122. modal_proto/api.proto +534 -79
  123. modal_proto/api_grpc.py +337 -1
  124. modal_proto/api_pb2.py +1522 -968
  125. modal_proto/api_pb2.pyi +1619 -134
  126. modal_proto/api_pb2_grpc.py +699 -4
  127. modal_proto/api_pb2_grpc.pyi +226 -14
  128. modal_proto/modal_api_grpc.py +175 -154
  129. modal_proto/sandbox_router.proto +145 -0
  130. modal_proto/sandbox_router_grpc.py +105 -0
  131. modal_proto/sandbox_router_pb2.py +149 -0
  132. modal_proto/sandbox_router_pb2.pyi +333 -0
  133. modal_proto/sandbox_router_pb2_grpc.py +203 -0
  134. modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
  135. modal_proto/task_command_router.proto +144 -0
  136. modal_proto/task_command_router_grpc.py +105 -0
  137. modal_proto/task_command_router_pb2.py +149 -0
  138. modal_proto/task_command_router_pb2.pyi +333 -0
  139. modal_proto/task_command_router_pb2_grpc.py +203 -0
  140. modal_proto/task_command_router_pb2_grpc.pyi +75 -0
  141. modal_version/__init__.py +1 -1
  142. modal/requirements/PREVIEW.txt +0 -16
  143. modal/requirements/base-images.json +0 -26
  144. modal-1.0.3.dev10.dist-info/RECORD +0 -179
  145. modal_proto/modal_options_grpc.py +0 -3
  146. modal_proto/options.proto +0 -19
  147. modal_proto/options_grpc.py +0 -3
  148. modal_proto/options_pb2.py +0 -35
  149. modal_proto/options_pb2.pyi +0 -20
  150. modal_proto/options_pb2_grpc.py +0 -4
  151. modal_proto/options_pb2_grpc.pyi +0 -7
  152. /modal/{requirements → builder}/2023.12.312.txt +0 -0
  153. /modal/{requirements → builder}/2023.12.txt +0 -0
  154. /modal/{requirements → builder}/2024.04.txt +0 -0
  155. /modal/{requirements → builder}/2024.10.txt +0 -0
  156. /modal/{requirements → builder}/README.md +0 -0
  157. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
  158. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
  159. {modal-1.0.3.dev10.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
  160. {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, file_descriptor: int, client: modal.client._Client, last_index: int
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 _StreamReader(typing.Generic[T]):
17
- _stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
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
- ) -> None: ...
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 _StreamWriter:
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
- _stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
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
- ) -> None: ...
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
- async def aio(self, /) -> T_INNER: ...
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
- class ___consume_container_process_stream_spec(typing_extensions.Protocol[SUPERSELF]):
78
- def __call__(self, /): ...
79
- async def aio(self, /): ...
394
+ def __iter__(self) -> typing.Iterator[T]:
395
+ """mdmd:hidden"""
396
+ ...
80
397
 
81
- _consume_container_process_stream: ___consume_container_process_stream_spec[typing_extensions.Self]
398
+ def __aiter__(self) -> collections.abc.AsyncIterator[T]:
399
+ """mdmd:hidden"""
400
+ ...
82
401
 
83
- class ___stream_container_process_spec(typing_extensions.Protocol[SUPERSELF]):
84
- def __call__(self, /) -> typing.Generator[tuple[typing.Optional[bytes], str], None, None]: ...
85
- def aio(self, /) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]: ...
402
+ def __next__(self) -> T:
403
+ """mdmd:hidden"""
404
+ ...
86
405
 
87
- _stream_container_process: ___stream_container_process_spec[typing_extensions.Self]
406
+ async def __anext__(self) -> T:
407
+ """mdmd:hidden"""
408
+ ...
88
409
 
89
- class ___get_logs_spec(typing_extensions.Protocol[SUPERSELF]):
90
- def __call__(
91
- self, /, skip_empty_messages: bool = True
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
- _get_logs: ___get_logs_spec[typing_extensions.Self]
414
+ async def aclose(self):
415
+ """mdmd:hidden"""
416
+ ...
98
417
 
99
- class ___get_logs_by_line_spec(typing_extensions.Protocol[SUPERSELF]):
100
- def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]: ...
101
- def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
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
- _get_logs_by_line: ___get_logs_by_line_spec[typing_extensions.Self]
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
- def __iter__(self) -> typing.Iterator[T]: ...
106
- def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
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
- class StreamWriter:
113
- def __init__(
114
- self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client.Client
115
- ) -> None: ...
116
- def _get_next_index(self) -> int: ...
117
- def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None: ...
118
- def write_eof(self) -> None: ...
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
- async def aio(self, /) -> None: ...
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]