modal 1.0.4.dev12__py3-none-any.whl → 1.0.5__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.pyi +13 -3
- modal/_functions.py +84 -46
- modal/_partial_function.py +1 -1
- modal/_runtime/container_io_manager.pyi +222 -40
- modal/_runtime/execution_context.pyi +60 -6
- modal/_serialization.py +25 -2
- modal/_tunnel.pyi +380 -12
- modal/_utils/async_utils.py +1 -1
- modal/_utils/blob_utils.py +56 -19
- modal/_utils/function_utils.py +33 -7
- modal/_utils/grpc_utils.py +11 -4
- modal/app.py +5 -5
- modal/app.pyi +658 -48
- modal/cli/run.py +2 -1
- modal/client.pyi +224 -36
- modal/cloud_bucket_mount.pyi +192 -4
- modal/cls.py +7 -7
- modal/cls.pyi +442 -35
- modal/container_process.pyi +103 -14
- modal/dict.py +4 -4
- modal/dict.pyi +453 -51
- modal/environments.pyi +41 -9
- modal/exception.py +6 -2
- modal/experimental/__init__.py +90 -0
- modal/experimental/ipython.py +11 -7
- modal/file_io.pyi +236 -45
- modal/functions.pyi +573 -65
- modal/gpu.py +1 -1
- modal/image.py +1 -1
- modal/image.pyi +1256 -74
- modal/io_streams.py +8 -4
- modal/io_streams.pyi +348 -38
- modal/mount.pyi +261 -31
- modal/network_file_system.py +3 -3
- modal/network_file_system.pyi +307 -26
- modal/object.pyi +48 -9
- modal/parallel_map.py +93 -19
- modal/parallel_map.pyi +160 -15
- modal/partial_function.pyi +255 -14
- modal/proxy.py +1 -1
- modal/proxy.pyi +28 -3
- modal/queue.py +4 -4
- modal/queue.pyi +447 -30
- modal/runner.pyi +160 -22
- modal/sandbox.py +8 -7
- modal/sandbox.pyi +310 -50
- modal/schedule.py +1 -1
- modal/secret.py +2 -2
- modal/secret.pyi +164 -15
- modal/snapshot.pyi +25 -4
- modal/token_flow.pyi +28 -8
- modal/volume.py +41 -4
- modal/volume.pyi +693 -59
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/METADATA +3 -3
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/RECORD +67 -67
- modal_proto/api.proto +56 -0
- modal_proto/api_grpc.py +48 -0
- modal_proto/api_pb2.py +874 -780
- modal_proto/api_pb2.pyi +194 -8
- modal_proto/api_pb2_grpc.py +100 -0
- modal_proto/api_pb2_grpc.pyi +32 -0
- modal_proto/modal_api_grpc.py +3 -0
- modal_version/__init__.py +1 -1
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/WHEEL +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/entry_points.txt +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/top_level.txt +0 -0
modal/io_streams.py
CHANGED
@@ -302,20 +302,24 @@ class _StreamReader(Generic[T]):
|
|
302
302
|
line, self._line_buffer = self._line_buffer.split(b"\n", 1)
|
303
303
|
yield line + b"\n"
|
304
304
|
|
305
|
-
def
|
306
|
-
"""mdmd:hidden"""
|
305
|
+
def _ensure_stream(self) -> AsyncGenerator[Optional[bytes], None]:
|
307
306
|
if not self._stream:
|
308
307
|
if self._by_line:
|
309
308
|
self._stream = self._get_logs_by_line()
|
310
309
|
else:
|
311
310
|
self._stream = self._get_logs()
|
311
|
+
return self._stream
|
312
|
+
|
313
|
+
def __aiter__(self) -> AsyncIterator[T]:
|
314
|
+
"""mdmd:hidden"""
|
315
|
+
self._ensure_stream()
|
312
316
|
return self
|
313
317
|
|
314
318
|
async def __anext__(self) -> T:
|
315
319
|
"""mdmd:hidden"""
|
316
|
-
|
320
|
+
stream = self._ensure_stream()
|
317
321
|
|
318
|
-
value = await
|
322
|
+
value = await stream.__anext__()
|
319
323
|
|
320
324
|
# The stream yields None if it receives an EOF batch.
|
321
325
|
if value is None:
|
modal/io_streams.pyi
CHANGED
@@ -14,6 +14,27 @@ def _container_process_logs_iterator(
|
|
14
14
|
T = typing.TypeVar("T")
|
15
15
|
|
16
16
|
class _StreamReader(typing.Generic[T]):
|
17
|
+
"""Retrieve logs from a stream (`stdout` or `stderr`).
|
18
|
+
|
19
|
+
As an asynchronous iterable, the object supports the `for` and `async for`
|
20
|
+
statements. Just loop over the object to read in chunks.
|
21
|
+
|
22
|
+
**Usage**
|
23
|
+
|
24
|
+
```python fixture:running_app
|
25
|
+
from modal import Sandbox
|
26
|
+
|
27
|
+
sandbox = Sandbox.create(
|
28
|
+
"bash",
|
29
|
+
"-c",
|
30
|
+
"for i in $(seq 1 10); do echo foo; sleep 0.1; done",
|
31
|
+
app=running_app,
|
32
|
+
)
|
33
|
+
for message in sandbox.stdout:
|
34
|
+
print(f"Message: {message}")
|
35
|
+
```
|
36
|
+
"""
|
37
|
+
|
17
38
|
_stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
|
18
39
|
|
19
40
|
def __init__(
|
@@ -25,34 +46,160 @@ class _StreamReader(typing.Generic[T]):
|
|
25
46
|
stream_type: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
26
47
|
text: bool = True,
|
27
48
|
by_line: bool = False,
|
28
|
-
) -> None:
|
49
|
+
) -> None:
|
50
|
+
"""mdmd:hidden"""
|
51
|
+
...
|
52
|
+
|
29
53
|
@property
|
30
|
-
def file_descriptor(self) -> int:
|
31
|
-
|
32
|
-
|
33
|
-
|
54
|
+
def file_descriptor(self) -> int:
|
55
|
+
"""Possible values are `1` for stdout and `2` for stderr."""
|
56
|
+
...
|
57
|
+
|
58
|
+
async def read(self) -> T:
|
59
|
+
"""Fetch the entire contents of the stream until EOF.
|
60
|
+
|
61
|
+
**Usage**
|
62
|
+
|
63
|
+
```python fixture:running_app
|
64
|
+
from modal import Sandbox
|
65
|
+
|
66
|
+
sandbox = Sandbox.create("echo", "hello", app=running_app)
|
67
|
+
sandbox.wait()
|
68
|
+
|
69
|
+
print(sandbox.stdout.read())
|
70
|
+
```
|
71
|
+
"""
|
72
|
+
...
|
73
|
+
|
74
|
+
async def _consume_container_process_stream(self):
|
75
|
+
"""Consume the container process stream and store messages in the buffer."""
|
76
|
+
...
|
77
|
+
|
78
|
+
def _stream_container_process(self) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]:
|
79
|
+
"""Streams the container process buffer to the reader."""
|
80
|
+
...
|
81
|
+
|
34
82
|
def _get_logs(
|
35
83
|
self, skip_empty_messages: bool = True
|
36
|
-
) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
84
|
+
) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
85
|
+
"""Streams sandbox or process logs from the server to the reader.
|
86
|
+
|
87
|
+
Logs returned by this method may contain partial or multiple lines at a time.
|
88
|
+
|
89
|
+
When the stream receives an EOF, it yields None. Once an EOF is received,
|
90
|
+
subsequent invocations will not yield logs.
|
91
|
+
"""
|
92
|
+
...
|
93
|
+
|
94
|
+
def _get_logs_by_line(self) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
95
|
+
"""Process logs from the server and yield complete lines only."""
|
96
|
+
...
|
97
|
+
|
98
|
+
def _ensure_stream(self) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
|
99
|
+
def __aiter__(self) -> collections.abc.AsyncIterator[T]:
|
100
|
+
"""mdmd:hidden"""
|
101
|
+
...
|
102
|
+
|
103
|
+
async def __anext__(self) -> T:
|
104
|
+
"""mdmd:hidden"""
|
105
|
+
...
|
106
|
+
|
107
|
+
async def aclose(self):
|
108
|
+
"""mdmd:hidden"""
|
109
|
+
...
|
41
110
|
|
42
111
|
class _StreamWriter:
|
112
|
+
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
43
113
|
def __init__(
|
44
114
|
self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client._Client
|
45
|
-
) -> None:
|
115
|
+
) -> None:
|
116
|
+
"""mdmd:hidden"""
|
117
|
+
...
|
118
|
+
|
46
119
|
def _get_next_index(self) -> int: ...
|
47
|
-
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
48
|
-
|
49
|
-
|
120
|
+
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
121
|
+
"""Write data to the stream but does not send it immediately.
|
122
|
+
|
123
|
+
This is non-blocking and queues the data to an internal buffer. Must be
|
124
|
+
used along with the `drain()` method, which flushes the buffer.
|
125
|
+
|
126
|
+
**Usage**
|
127
|
+
|
128
|
+
```python fixture:running_app
|
129
|
+
from modal import Sandbox
|
130
|
+
|
131
|
+
sandbox = Sandbox.create(
|
132
|
+
"bash",
|
133
|
+
"-c",
|
134
|
+
"while read line; do echo $line; done",
|
135
|
+
app=running_app,
|
136
|
+
)
|
137
|
+
sandbox.stdin.write(b"foo\n")
|
138
|
+
sandbox.stdin.write(b"bar\n")
|
139
|
+
sandbox.stdin.write_eof()
|
140
|
+
|
141
|
+
sandbox.stdin.drain()
|
142
|
+
sandbox.wait()
|
143
|
+
```
|
144
|
+
"""
|
145
|
+
...
|
146
|
+
|
147
|
+
def write_eof(self) -> None:
|
148
|
+
"""Close the write end of the stream after the buffered data is drained.
|
149
|
+
|
150
|
+
If the process was blocked on input, it will become unblocked after
|
151
|
+
`write_eof()`. This method needs to be used along with the `drain()`
|
152
|
+
method, which flushes the EOF to the process.
|
153
|
+
"""
|
154
|
+
...
|
155
|
+
|
156
|
+
async def drain(self) -> None:
|
157
|
+
"""Flush the write buffer and send data to the running process.
|
158
|
+
|
159
|
+
This is a flow control method that blocks until data is sent. It returns
|
160
|
+
when it is appropriate to continue writing data to the stream.
|
161
|
+
|
162
|
+
**Usage**
|
163
|
+
|
164
|
+
```python notest
|
165
|
+
writer.write(data)
|
166
|
+
writer.drain()
|
167
|
+
```
|
168
|
+
|
169
|
+
Async usage:
|
170
|
+
```python notest
|
171
|
+
writer.write(data) # not a blocking operation
|
172
|
+
await writer.drain.aio()
|
173
|
+
```
|
174
|
+
"""
|
175
|
+
...
|
50
176
|
|
51
177
|
T_INNER = typing.TypeVar("T_INNER", covariant=True)
|
52
178
|
|
53
179
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
54
180
|
|
55
181
|
class StreamReader(typing.Generic[T]):
|
182
|
+
"""Retrieve logs from a stream (`stdout` or `stderr`).
|
183
|
+
|
184
|
+
As an asynchronous iterable, the object supports the `for` and `async for`
|
185
|
+
statements. Just loop over the object to read in chunks.
|
186
|
+
|
187
|
+
**Usage**
|
188
|
+
|
189
|
+
```python fixture:running_app
|
190
|
+
from modal import Sandbox
|
191
|
+
|
192
|
+
sandbox = Sandbox.create(
|
193
|
+
"bash",
|
194
|
+
"-c",
|
195
|
+
"for i in $(seq 1 10); do echo foo; sleep 0.1; done",
|
196
|
+
app=running_app,
|
197
|
+
)
|
198
|
+
for message in sandbox.stdout:
|
199
|
+
print(f"Message: {message}")
|
200
|
+
```
|
201
|
+
"""
|
202
|
+
|
56
203
|
_stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
|
57
204
|
|
58
205
|
def __init__(
|
@@ -64,61 +211,224 @@ class StreamReader(typing.Generic[T]):
|
|
64
211
|
stream_type: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
|
65
212
|
text: bool = True,
|
66
213
|
by_line: bool = False,
|
67
|
-
) -> None:
|
214
|
+
) -> None:
|
215
|
+
"""mdmd:hidden"""
|
216
|
+
...
|
217
|
+
|
68
218
|
@property
|
69
|
-
def file_descriptor(self) -> int:
|
219
|
+
def file_descriptor(self) -> int:
|
220
|
+
"""Possible values are `1` for stdout and `2` for stderr."""
|
221
|
+
...
|
70
222
|
|
71
223
|
class __read_spec(typing_extensions.Protocol[T_INNER, SUPERSELF]):
|
72
|
-
def __call__(self, /) -> T_INNER:
|
73
|
-
|
224
|
+
def __call__(self, /) -> T_INNER:
|
225
|
+
"""Fetch the entire contents of the stream until EOF.
|
226
|
+
|
227
|
+
**Usage**
|
228
|
+
|
229
|
+
```python fixture:running_app
|
230
|
+
from modal import Sandbox
|
231
|
+
|
232
|
+
sandbox = Sandbox.create("echo", "hello", app=running_app)
|
233
|
+
sandbox.wait()
|
234
|
+
|
235
|
+
print(sandbox.stdout.read())
|
236
|
+
```
|
237
|
+
"""
|
238
|
+
...
|
239
|
+
|
240
|
+
async def aio(self, /) -> T_INNER:
|
241
|
+
"""Fetch the entire contents of the stream until EOF.
|
242
|
+
|
243
|
+
**Usage**
|
244
|
+
|
245
|
+
```python fixture:running_app
|
246
|
+
from modal import Sandbox
|
247
|
+
|
248
|
+
sandbox = Sandbox.create("echo", "hello", app=running_app)
|
249
|
+
sandbox.wait()
|
250
|
+
|
251
|
+
print(sandbox.stdout.read())
|
252
|
+
```
|
253
|
+
"""
|
254
|
+
...
|
74
255
|
|
75
256
|
read: __read_spec[T, typing_extensions.Self]
|
76
257
|
|
77
258
|
class ___consume_container_process_stream_spec(typing_extensions.Protocol[SUPERSELF]):
|
78
|
-
def __call__(self, /):
|
79
|
-
|
259
|
+
def __call__(self, /):
|
260
|
+
"""Consume the container process stream and store messages in the buffer."""
|
261
|
+
...
|
262
|
+
|
263
|
+
async def aio(self, /):
|
264
|
+
"""Consume the container process stream and store messages in the buffer."""
|
265
|
+
...
|
80
266
|
|
81
267
|
_consume_container_process_stream: ___consume_container_process_stream_spec[typing_extensions.Self]
|
82
268
|
|
83
269
|
class ___stream_container_process_spec(typing_extensions.Protocol[SUPERSELF]):
|
84
|
-
def __call__(self, /) -> typing.Generator[tuple[typing.Optional[bytes], str], None, None]:
|
85
|
-
|
270
|
+
def __call__(self, /) -> typing.Generator[tuple[typing.Optional[bytes], str], None, None]:
|
271
|
+
"""Streams the container process buffer to the reader."""
|
272
|
+
...
|
273
|
+
|
274
|
+
def aio(self, /) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]:
|
275
|
+
"""Streams the container process buffer to the reader."""
|
276
|
+
...
|
86
277
|
|
87
278
|
_stream_container_process: ___stream_container_process_spec[typing_extensions.Self]
|
88
279
|
|
89
280
|
class ___get_logs_spec(typing_extensions.Protocol[SUPERSELF]):
|
90
|
-
def __call__(
|
91
|
-
|
92
|
-
|
281
|
+
def __call__(self, /, skip_empty_messages: bool = True) -> typing.Generator[typing.Optional[bytes], None, None]:
|
282
|
+
"""Streams sandbox or process logs from the server to the reader.
|
283
|
+
|
284
|
+
Logs returned by this method may contain partial or multiple lines at a time.
|
285
|
+
|
286
|
+
When the stream receives an EOF, it yields None. Once an EOF is received,
|
287
|
+
subsequent invocations will not yield logs.
|
288
|
+
"""
|
289
|
+
...
|
290
|
+
|
93
291
|
def aio(
|
94
292
|
self, /, skip_empty_messages: bool = True
|
95
|
-
) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
293
|
+
) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
294
|
+
"""Streams sandbox or process logs from the server to the reader.
|
295
|
+
|
296
|
+
Logs returned by this method may contain partial or multiple lines at a time.
|
297
|
+
|
298
|
+
When the stream receives an EOF, it yields None. Once an EOF is received,
|
299
|
+
subsequent invocations will not yield logs.
|
300
|
+
"""
|
301
|
+
...
|
96
302
|
|
97
303
|
_get_logs: ___get_logs_spec[typing_extensions.Self]
|
98
304
|
|
99
305
|
class ___get_logs_by_line_spec(typing_extensions.Protocol[SUPERSELF]):
|
306
|
+
def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]:
|
307
|
+
"""Process logs from the server and yield complete lines only."""
|
308
|
+
...
|
309
|
+
|
310
|
+
def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
|
311
|
+
"""Process logs from the server and yield complete lines only."""
|
312
|
+
...
|
313
|
+
|
314
|
+
_get_logs_by_line: ___get_logs_by_line_spec[typing_extensions.Self]
|
315
|
+
|
316
|
+
class ___ensure_stream_spec(typing_extensions.Protocol[SUPERSELF]):
|
100
317
|
def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]: ...
|
101
318
|
def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
|
102
319
|
|
103
|
-
|
320
|
+
_ensure_stream: ___ensure_stream_spec[typing_extensions.Self]
|
321
|
+
|
322
|
+
def __iter__(self) -> typing.Iterator[T]:
|
323
|
+
"""mdmd:hidden"""
|
324
|
+
...
|
325
|
+
|
326
|
+
def __aiter__(self) -> collections.abc.AsyncIterator[T]:
|
327
|
+
"""mdmd:hidden"""
|
328
|
+
...
|
104
329
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
def
|
110
|
-
|
330
|
+
def __next__(self) -> T:
|
331
|
+
"""mdmd:hidden"""
|
332
|
+
...
|
333
|
+
|
334
|
+
async def __anext__(self) -> T:
|
335
|
+
"""mdmd:hidden"""
|
336
|
+
...
|
337
|
+
|
338
|
+
def close(self):
|
339
|
+
"""mdmd:hidden"""
|
340
|
+
...
|
341
|
+
|
342
|
+
async def aclose(self):
|
343
|
+
"""mdmd:hidden"""
|
344
|
+
...
|
111
345
|
|
112
346
|
class StreamWriter:
|
347
|
+
"""Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
|
113
348
|
def __init__(
|
114
349
|
self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client.Client
|
115
|
-
) -> None:
|
350
|
+
) -> None:
|
351
|
+
"""mdmd:hidden"""
|
352
|
+
...
|
353
|
+
|
116
354
|
def _get_next_index(self) -> int: ...
|
117
|
-
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
118
|
-
|
355
|
+
def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
|
356
|
+
"""Write data to the stream but does not send it immediately.
|
357
|
+
|
358
|
+
This is non-blocking and queues the data to an internal buffer. Must be
|
359
|
+
used along with the `drain()` method, which flushes the buffer.
|
360
|
+
|
361
|
+
**Usage**
|
362
|
+
|
363
|
+
```python fixture:running_app
|
364
|
+
from modal import Sandbox
|
365
|
+
|
366
|
+
sandbox = Sandbox.create(
|
367
|
+
"bash",
|
368
|
+
"-c",
|
369
|
+
"while read line; do echo $line; done",
|
370
|
+
app=running_app,
|
371
|
+
)
|
372
|
+
sandbox.stdin.write(b"foo\n")
|
373
|
+
sandbox.stdin.write(b"bar\n")
|
374
|
+
sandbox.stdin.write_eof()
|
375
|
+
|
376
|
+
sandbox.stdin.drain()
|
377
|
+
sandbox.wait()
|
378
|
+
```
|
379
|
+
"""
|
380
|
+
...
|
381
|
+
|
382
|
+
def write_eof(self) -> None:
|
383
|
+
"""Close the write end of the stream after the buffered data is drained.
|
384
|
+
|
385
|
+
If the process was blocked on input, it will become unblocked after
|
386
|
+
`write_eof()`. This method needs to be used along with the `drain()`
|
387
|
+
method, which flushes the EOF to the process.
|
388
|
+
"""
|
389
|
+
...
|
119
390
|
|
120
391
|
class __drain_spec(typing_extensions.Protocol[SUPERSELF]):
|
121
|
-
def __call__(self, /) -> None:
|
122
|
-
|
392
|
+
def __call__(self, /) -> None:
|
393
|
+
"""Flush the write buffer and send data to the running process.
|
394
|
+
|
395
|
+
This is a flow control method that blocks until data is sent. It returns
|
396
|
+
when it is appropriate to continue writing data to the stream.
|
397
|
+
|
398
|
+
**Usage**
|
399
|
+
|
400
|
+
```python notest
|
401
|
+
writer.write(data)
|
402
|
+
writer.drain()
|
403
|
+
```
|
404
|
+
|
405
|
+
Async usage:
|
406
|
+
```python notest
|
407
|
+
writer.write(data) # not a blocking operation
|
408
|
+
await writer.drain.aio()
|
409
|
+
```
|
410
|
+
"""
|
411
|
+
...
|
412
|
+
|
413
|
+
async def aio(self, /) -> None:
|
414
|
+
"""Flush the write buffer and send data to the running process.
|
415
|
+
|
416
|
+
This is a flow control method that blocks until data is sent. It returns
|
417
|
+
when it is appropriate to continue writing data to the stream.
|
418
|
+
|
419
|
+
**Usage**
|
420
|
+
|
421
|
+
```python notest
|
422
|
+
writer.write(data)
|
423
|
+
writer.drain()
|
424
|
+
```
|
425
|
+
|
426
|
+
Async usage:
|
427
|
+
```python notest
|
428
|
+
writer.write(data) # not a blocking operation
|
429
|
+
await writer.drain.aio()
|
430
|
+
```
|
431
|
+
"""
|
432
|
+
...
|
123
433
|
|
124
434
|
drain: __drain_spec[typing_extensions.Self]
|