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.

Files changed (143) hide show
  1. modal/__init__.py +4 -4
  2. modal/__main__.py +4 -29
  3. modal/_billing.py +84 -0
  4. modal/_clustered_functions.py +1 -3
  5. modal/_container_entrypoint.py +33 -208
  6. modal/_functions.py +171 -138
  7. modal/_grpc_client.py +191 -0
  8. modal/_ipython.py +16 -6
  9. modal/_load_context.py +106 -0
  10. modal/_object.py +72 -21
  11. modal/_output.py +12 -14
  12. modal/_partial_function.py +31 -4
  13. modal/_resolver.py +44 -57
  14. modal/_runtime/container_io_manager.py +30 -28
  15. modal/_runtime/container_io_manager.pyi +42 -44
  16. modal/_runtime/gpu_memory_snapshot.py +9 -7
  17. modal/_runtime/user_code_event_loop.py +80 -0
  18. modal/_runtime/user_code_imports.py +236 -10
  19. modal/_serialization.py +2 -1
  20. modal/_traceback.py +4 -13
  21. modal/_tunnel.py +16 -11
  22. modal/_tunnel.pyi +25 -3
  23. modal/_utils/async_utils.py +337 -10
  24. modal/_utils/auth_token_manager.py +1 -4
  25. modal/_utils/blob_utils.py +29 -22
  26. modal/_utils/function_utils.py +20 -21
  27. modal/_utils/grpc_testing.py +6 -3
  28. modal/_utils/grpc_utils.py +223 -64
  29. modal/_utils/mount_utils.py +26 -1
  30. modal/_utils/name_utils.py +2 -3
  31. modal/_utils/package_utils.py +0 -1
  32. modal/_utils/rand_pb_testing.py +8 -1
  33. modal/_utils/task_command_router_client.py +524 -0
  34. modal/_vendor/cloudpickle.py +144 -48
  35. modal/app.py +285 -105
  36. modal/app.pyi +216 -53
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +6 -3
  39. modal/builder/PREVIEW.txt +2 -1
  40. modal/builder/base-images.json +4 -2
  41. modal/cli/_download.py +19 -3
  42. modal/cli/cluster.py +4 -2
  43. modal/cli/config.py +3 -1
  44. modal/cli/container.py +5 -4
  45. modal/cli/dict.py +5 -2
  46. modal/cli/entry_point.py +26 -2
  47. modal/cli/environment.py +2 -16
  48. modal/cli/launch.py +1 -76
  49. modal/cli/network_file_system.py +5 -20
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/vscode.py +1 -1
  52. modal/cli/queues.py +5 -4
  53. modal/cli/run.py +24 -204
  54. modal/cli/secret.py +1 -2
  55. modal/cli/shell.py +375 -0
  56. modal/cli/utils.py +1 -13
  57. modal/cli/volume.py +11 -17
  58. modal/client.py +16 -125
  59. modal/client.pyi +94 -144
  60. modal/cloud_bucket_mount.py +3 -1
  61. modal/cloud_bucket_mount.pyi +4 -0
  62. modal/cls.py +101 -64
  63. modal/cls.pyi +9 -8
  64. modal/config.py +21 -1
  65. modal/container_process.py +288 -12
  66. modal/container_process.pyi +99 -38
  67. modal/dict.py +72 -33
  68. modal/dict.pyi +88 -57
  69. modal/environments.py +16 -8
  70. modal/environments.pyi +6 -2
  71. modal/exception.py +154 -16
  72. modal/experimental/__init__.py +24 -53
  73. modal/experimental/flash.py +161 -74
  74. modal/experimental/flash.pyi +97 -49
  75. modal/file_io.py +50 -92
  76. modal/file_io.pyi +117 -89
  77. modal/functions.pyi +70 -87
  78. modal/image.py +82 -47
  79. modal/image.pyi +51 -30
  80. modal/io_streams.py +500 -149
  81. modal/io_streams.pyi +279 -189
  82. modal/mount.py +60 -46
  83. modal/mount.pyi +41 -17
  84. modal/network_file_system.py +19 -11
  85. modal/network_file_system.pyi +72 -39
  86. modal/object.pyi +114 -22
  87. modal/parallel_map.py +42 -44
  88. modal/parallel_map.pyi +9 -17
  89. modal/partial_function.pyi +4 -2
  90. modal/proxy.py +14 -6
  91. modal/proxy.pyi +10 -2
  92. modal/queue.py +45 -38
  93. modal/queue.pyi +88 -52
  94. modal/runner.py +96 -96
  95. modal/runner.pyi +44 -27
  96. modal/sandbox.py +225 -107
  97. modal/sandbox.pyi +226 -60
  98. modal/secret.py +58 -56
  99. modal/secret.pyi +28 -13
  100. modal/serving.py +7 -11
  101. modal/serving.pyi +7 -8
  102. modal/snapshot.py +29 -15
  103. modal/snapshot.pyi +18 -10
  104. modal/token_flow.py +1 -1
  105. modal/token_flow.pyi +4 -6
  106. modal/volume.py +102 -55
  107. modal/volume.pyi +125 -66
  108. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
  109. modal-1.3.1.dev8.dist-info/RECORD +189 -0
  110. modal_proto/api.proto +141 -70
  111. modal_proto/api_grpc.py +42 -26
  112. modal_proto/api_pb2.py +1123 -1103
  113. modal_proto/api_pb2.pyi +331 -83
  114. modal_proto/api_pb2_grpc.py +80 -48
  115. modal_proto/api_pb2_grpc.pyi +26 -18
  116. modal_proto/modal_api_grpc.py +175 -174
  117. modal_proto/task_command_router.proto +164 -0
  118. modal_proto/task_command_router_grpc.py +138 -0
  119. modal_proto/task_command_router_pb2.py +180 -0
  120. modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +148 -57
  121. modal_proto/task_command_router_pb2_grpc.py +272 -0
  122. modal_proto/task_command_router_pb2_grpc.pyi +100 -0
  123. modal_version/__init__.py +1 -1
  124. modal_version/__main__.py +1 -1
  125. modal/cli/programs/launch_instance_ssh.py +0 -94
  126. modal/cli/programs/run_marimo.py +0 -95
  127. modal-1.1.5.dev66.dist-info/RECORD +0 -191
  128. modal_proto/modal_options_grpc.py +0 -3
  129. modal_proto/options.proto +0 -19
  130. modal_proto/options_grpc.py +0 -3
  131. modal_proto/options_pb2.py +0 -35
  132. modal_proto/options_pb2.pyi +0 -20
  133. modal_proto/options_pb2_grpc.py +0 -4
  134. modal_proto/options_pb2_grpc.pyi +0 -7
  135. modal_proto/sandbox_router.proto +0 -125
  136. modal_proto/sandbox_router_grpc.py +0 -89
  137. modal_proto/sandbox_router_pb2.py +0 -128
  138. modal_proto/sandbox_router_pb2_grpc.py +0 -169
  139. modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
  140. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
  141. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
  142. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
  143. {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 _StreamReader(typing.Generic[T]):
21
- """Retrieve logs from a stream (`stdout` or `stderr`).
21
+ class _StreamReaderThroughServer(typing.Generic[T]):
22
+ """A StreamReader implementation that reads from the server."""
22
23
 
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**
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[typing.Optional[bytes], None]:
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[typing.Optional[bytes], None]: ...
104
- def __aiter__(self) -> collections.abc.AsyncIterator[T]:
71
+ def __aiter__(self) -> collections.abc.AsyncGenerator[T, None]: ...
72
+ async def aclose(self):
105
73
  """mdmd:hidden"""
106
74
  ...
107
75
 
108
- async def __anext__(self) -> T:
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 _StreamWriter:
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
- **Usage**
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
- ```python fixture:running_app
134
- from modal import Sandbox
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
- sandbox = Sandbox.create(
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
- sandbox.stdin.write(b"foo\n")
143
- sandbox.stdin.write(b"bar\n")
144
- sandbox.stdin.write_eof()
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
- _stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
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, SUPERSELF]):
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
- _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
- 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
- """mdmd:hidden"""
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
- """mdmd:hidden"""
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, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client.Client
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:running_app
370
- from modal import Sandbox
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
- sandbox.stdin.write(b"foo\n")
379
- sandbox.stdin.write(b"bar\n")
380
- sandbox.stdin.write_eof()
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[SUPERSELF]):
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[typing_extensions.Self]
530
+ drain: __drain_spec