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.
Files changed (67) hide show
  1. modal/_clustered_functions.pyi +13 -3
  2. modal/_functions.py +84 -46
  3. modal/_partial_function.py +1 -1
  4. modal/_runtime/container_io_manager.pyi +222 -40
  5. modal/_runtime/execution_context.pyi +60 -6
  6. modal/_serialization.py +25 -2
  7. modal/_tunnel.pyi +380 -12
  8. modal/_utils/async_utils.py +1 -1
  9. modal/_utils/blob_utils.py +56 -19
  10. modal/_utils/function_utils.py +33 -7
  11. modal/_utils/grpc_utils.py +11 -4
  12. modal/app.py +5 -5
  13. modal/app.pyi +658 -48
  14. modal/cli/run.py +2 -1
  15. modal/client.pyi +224 -36
  16. modal/cloud_bucket_mount.pyi +192 -4
  17. modal/cls.py +7 -7
  18. modal/cls.pyi +442 -35
  19. modal/container_process.pyi +103 -14
  20. modal/dict.py +4 -4
  21. modal/dict.pyi +453 -51
  22. modal/environments.pyi +41 -9
  23. modal/exception.py +6 -2
  24. modal/experimental/__init__.py +90 -0
  25. modal/experimental/ipython.py +11 -7
  26. modal/file_io.pyi +236 -45
  27. modal/functions.pyi +573 -65
  28. modal/gpu.py +1 -1
  29. modal/image.py +1 -1
  30. modal/image.pyi +1256 -74
  31. modal/io_streams.py +8 -4
  32. modal/io_streams.pyi +348 -38
  33. modal/mount.pyi +261 -31
  34. modal/network_file_system.py +3 -3
  35. modal/network_file_system.pyi +307 -26
  36. modal/object.pyi +48 -9
  37. modal/parallel_map.py +93 -19
  38. modal/parallel_map.pyi +160 -15
  39. modal/partial_function.pyi +255 -14
  40. modal/proxy.py +1 -1
  41. modal/proxy.pyi +28 -3
  42. modal/queue.py +4 -4
  43. modal/queue.pyi +447 -30
  44. modal/runner.pyi +160 -22
  45. modal/sandbox.py +8 -7
  46. modal/sandbox.pyi +310 -50
  47. modal/schedule.py +1 -1
  48. modal/secret.py +2 -2
  49. modal/secret.pyi +164 -15
  50. modal/snapshot.pyi +25 -4
  51. modal/token_flow.pyi +28 -8
  52. modal/volume.py +41 -4
  53. modal/volume.pyi +693 -59
  54. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/METADATA +3 -3
  55. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/RECORD +67 -67
  56. modal_proto/api.proto +56 -0
  57. modal_proto/api_grpc.py +48 -0
  58. modal_proto/api_pb2.py +874 -780
  59. modal_proto/api_pb2.pyi +194 -8
  60. modal_proto/api_pb2_grpc.py +100 -0
  61. modal_proto/api_pb2_grpc.pyi +32 -0
  62. modal_proto/modal_api_grpc.py +3 -0
  63. modal_version/__init__.py +1 -1
  64. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/WHEEL +0 -0
  65. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/entry_points.txt +0 -0
  66. {modal-1.0.4.dev12.dist-info → modal-1.0.5.dist-info}/licenses/LICENSE +0 -0
  67. {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 __aiter__(self) -> AsyncIterator[T]:
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
- assert self._stream is not None
320
+ stream = self._ensure_stream()
317
321
 
318
- value = await self._stream.__anext__()
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
- 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]: ...
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
- def _get_logs_by_line(self) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
38
- def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
39
- async def __anext__(self) -> T: ...
40
- async def aclose(self): ...
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
- def write_eof(self) -> None: ...
49
- async def drain(self) -> None: ...
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
- async def aio(self, /) -> T_INNER: ...
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
- async def aio(self, /): ...
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
- def aio(self, /) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]: ...
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
- self, /, skip_empty_messages: bool = True
92
- ) -> typing.Generator[typing.Optional[bytes], None, None]: ...
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
- _get_logs_by_line: ___get_logs_by_line_spec[typing_extensions.Self]
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 __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): ...
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
- def write_eof(self) -> None: ...
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
- async def aio(self, /) -> None: ...
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]