modal 1.2.1.dev8__py3-none-any.whl → 1.2.2.dev19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. modal/_clustered_functions.py +1 -3
  2. modal/_container_entrypoint.py +4 -1
  3. modal/_functions.py +33 -49
  4. modal/_grpc_client.py +148 -0
  5. modal/_output.py +3 -4
  6. modal/_partial_function.py +22 -2
  7. modal/_runtime/container_io_manager.py +21 -22
  8. modal/_utils/async_utils.py +12 -3
  9. modal/_utils/auth_token_manager.py +1 -4
  10. modal/_utils/blob_utils.py +3 -4
  11. modal/_utils/function_utils.py +4 -0
  12. modal/_utils/grpc_utils.py +80 -51
  13. modal/_utils/mount_utils.py +26 -1
  14. modal/_utils/task_command_router_client.py +536 -0
  15. modal/app.py +7 -5
  16. modal/cli/cluster.py +4 -2
  17. modal/cli/config.py +3 -1
  18. modal/cli/container.py +5 -4
  19. modal/cli/entry_point.py +1 -0
  20. modal/cli/launch.py +1 -2
  21. modal/cli/network_file_system.py +1 -4
  22. modal/cli/queues.py +1 -2
  23. modal/cli/secret.py +1 -2
  24. modal/client.py +5 -115
  25. modal/client.pyi +2 -91
  26. modal/cls.py +1 -2
  27. modal/config.py +3 -1
  28. modal/container_process.py +287 -11
  29. modal/container_process.pyi +95 -32
  30. modal/dict.py +12 -12
  31. modal/environments.py +1 -2
  32. modal/exception.py +4 -0
  33. modal/experimental/__init__.py +2 -3
  34. modal/experimental/flash.py +27 -57
  35. modal/experimental/flash.pyi +6 -20
  36. modal/file_io.py +13 -27
  37. modal/functions.pyi +6 -6
  38. modal/image.py +24 -3
  39. modal/image.pyi +4 -0
  40. modal/io_streams.py +433 -127
  41. modal/io_streams.pyi +236 -171
  42. modal/mount.py +4 -4
  43. modal/network_file_system.py +5 -6
  44. modal/parallel_map.py +29 -31
  45. modal/parallel_map.pyi +3 -9
  46. modal/partial_function.pyi +4 -1
  47. modal/queue.py +17 -18
  48. modal/runner.py +12 -11
  49. modal/sandbox.py +148 -42
  50. modal/sandbox.pyi +139 -0
  51. modal/secret.py +4 -5
  52. modal/snapshot.py +1 -4
  53. modal/token_flow.py +1 -1
  54. modal/volume.py +22 -22
  55. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/METADATA +1 -1
  56. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/RECORD +70 -68
  57. modal_proto/api.proto +2 -24
  58. modal_proto/api_grpc.py +0 -32
  59. modal_proto/api_pb2.py +838 -878
  60. modal_proto/api_pb2.pyi +8 -70
  61. modal_proto/api_pb2_grpc.py +0 -67
  62. modal_proto/api_pb2_grpc.pyi +0 -22
  63. modal_proto/modal_api_grpc.py +175 -177
  64. modal_proto/sandbox_router.proto +0 -4
  65. modal_proto/sandbox_router_pb2.pyi +0 -4
  66. modal_version/__init__.py +1 -1
  67. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/WHEEL +0 -0
  68. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/entry_points.txt +0 -0
  69. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/licenses/LICENSE +0 -0
  70. {modal-1.2.1.dev8.dist-info → modal-1.2.2.dev19.dist-info}/top_level.txt +0 -0
modal/io_streams.pyi CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections.abc
2
+ import modal._utils.task_command_router_client
2
3
  import modal.client
3
4
  import modal.stream_type
4
5
  import typing
@@ -17,29 +18,10 @@ def _container_process_logs_iterator(
17
18
 
18
19
  T = typing.TypeVar("T")
19
20
 
20
- class _StreamReader(typing.Generic[T]):
21
- """Retrieve logs from a stream (`stdout` or `stderr`).
22
-
23
- As an asynchronous iterable, the object supports the `for` and `async for`
24
- statements. Just loop over the object to read in chunks.
25
-
26
- **Usage**
21
+ class _StreamReaderThroughServer(typing.Generic[T]):
22
+ """A StreamReader implementation that reads from the server."""
27
23
 
28
- ```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,11 +64,158 @@ 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]: ...
71
+ def _ensure_stream(self) -> collections.abc.AsyncGenerator[T, None]: ...
72
+ async def __anext__(self) -> T:
73
+ """mdmd:hidden"""
74
+ ...
75
+
76
+ async def aclose(self):
77
+ """mdmd:hidden"""
78
+ ...
79
+
80
+ def _decode_bytes_stream_to_str(
81
+ stream: collections.abc.AsyncGenerator[bytes, None],
82
+ ) -> collections.abc.AsyncGenerator[str, None]:
83
+ """Incrementally decode a bytes async generator as UTF-8 without breaking on chunk boundaries.
84
+
85
+ This function uses a streaming UTF-8 decoder so that multi-byte characters split across
86
+ chunks are handled correctly instead of raising ``UnicodeDecodeError``.
87
+ """
88
+ ...
89
+
90
+ def _stream_by_line(stream: collections.abc.AsyncGenerator[bytes, None]) -> collections.abc.AsyncGenerator[bytes, None]:
91
+ """Yield complete lines only (ending with
92
+ ), buffering partial lines until complete.
93
+ """
94
+ ...
95
+
96
+ class _StreamReaderThroughCommandRouterParams:
97
+ """_StreamReaderThroughCommandRouterParams(file_descriptor: 'api_pb2.FileDescriptor.ValueType', task_id: str, object_id: str, command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient, deadline: Optional[float])"""
98
+
99
+ file_descriptor: int
100
+ task_id: str
101
+ object_id: str
102
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient
103
+ deadline: typing.Optional[float]
104
+
105
+ def __init__(
106
+ self,
107
+ file_descriptor: int,
108
+ task_id: str,
109
+ object_id: str,
110
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
111
+ deadline: typing.Optional[float],
112
+ ) -> None:
113
+ """Initialize self. See help(type(self)) for accurate signature."""
114
+ ...
115
+
116
+ def __repr__(self):
117
+ """Return repr(self)."""
118
+ ...
119
+
120
+ def __eq__(self, other):
121
+ """Return self==value."""
122
+ ...
123
+
124
+ def _stdio_stream_from_command_router(
125
+ params: _StreamReaderThroughCommandRouterParams,
126
+ ) -> collections.abc.AsyncGenerator[bytes, None]:
127
+ """Stream raw bytes from the router client."""
128
+ ...
129
+
130
+ class _BytesStreamReaderThroughCommandRouter(typing.Generic[T]):
131
+ """StreamReader implementation that will read directly from the worker that
132
+ hosts the sandbox.
133
+
134
+ This implementation is used for non-text streams.
135
+ """
136
+ def __init__(self, params: _StreamReaderThroughCommandRouterParams) -> None:
137
+ """Initialize self. See help(type(self)) for accurate signature."""
138
+ ...
139
+
140
+ @property
141
+ def file_descriptor(self) -> int: ...
142
+ async def read(self) -> T: ...
143
+ def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
144
+ async def __anext__(self) -> T: ...
145
+ async def aclose(self): ...
146
+
147
+ class _TextStreamReaderThroughCommandRouter(typing.Generic[T]):
148
+ """StreamReader implementation that will read directly from the worker
149
+ that hosts the sandbox.
150
+
151
+ This implementation is used for text streams.
152
+ """
153
+ def __init__(self, params: _StreamReaderThroughCommandRouterParams, by_line: bool) -> None:
154
+ """Initialize self. See help(type(self)) for accurate signature."""
155
+ ...
156
+
157
+ @property
158
+ def file_descriptor(self) -> int: ...
159
+ async def read(self) -> T: ...
160
+ def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
161
+ async def __anext__(self) -> T: ...
162
+ async def aclose(self): ...
163
+
164
+ class _DevnullStreamReader(typing.Generic[T]):
165
+ """StreamReader implementation for a stream configured with
166
+ StreamType.DEVNULL. Throws an error if read or any other method is
167
+ called.
168
+ """
169
+ def __init__(self, file_descriptor: int) -> None:
170
+ """Initialize self. See help(type(self)) for accurate signature."""
171
+ ...
172
+
173
+ @property
174
+ def file_descriptor(self) -> int: ...
175
+ async def read(self) -> T: ...
176
+ def __aiter__(self) -> collections.abc.AsyncIterator[T]: ...
177
+ async def __anext__(self) -> T: ...
178
+ async def aclose(self): ...
179
+
180
+ class _StreamReader(typing.Generic[T]):
181
+ """Retrieve logs from a stream (`stdout` or `stderr`).
182
+
183
+ As an asynchronous iterable, the object supports the `for` and `async for`
184
+ statements. Just loop over the object to read in chunks.
185
+ """
186
+
187
+ _impl: typing.Union[
188
+ _StreamReaderThroughServer,
189
+ _DevnullStreamReader,
190
+ _TextStreamReaderThroughCommandRouter,
191
+ _BytesStreamReaderThroughCommandRouter,
192
+ ]
193
+
194
+ def __init__(
195
+ self,
196
+ file_descriptor: int,
197
+ object_id: str,
198
+ object_type: typing.Literal["sandbox", "container_process"],
199
+ client: modal.client._Client,
200
+ stream_type: modal.stream_type.StreamType = modal.stream_type.StreamType.PIPE,
201
+ text: bool = True,
202
+ by_line: bool = False,
203
+ deadline: typing.Optional[float] = None,
204
+ command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
205
+ task_id: typing.Optional[str] = None,
206
+ ) -> None:
207
+ """mdmd:hidden"""
208
+ ...
209
+
210
+ @property
211
+ def file_descriptor(self) -> int:
212
+ """Possible values are `1` for stdout and `2` for stderr."""
213
+ ...
214
+
215
+ async def read(self) -> T:
216
+ """Fetch the entire contents of the stream until EOF."""
217
+ ...
218
+
104
219
  def __aiter__(self) -> collections.abc.AsyncIterator[T]:
105
220
  """mdmd:hidden"""
106
221
  ...
@@ -113,7 +228,7 @@ class _StreamReader(typing.Generic[T]):
113
228
  """mdmd:hidden"""
114
229
  ...
115
230
 
116
- class _StreamWriter:
231
+ class _StreamWriterThroughServer:
117
232
  """Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
118
233
  def __init__(
119
234
  self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client._Client
@@ -127,24 +242,71 @@ class _StreamWriter:
127
242
 
128
243
  This is non-blocking and queues the data to an internal buffer. Must be
129
244
  used along with the `drain()` method, which flushes the buffer.
245
+ """
246
+ ...
130
247
 
131
- **Usage**
248
+ def write_eof(self) -> None:
249
+ """Close the write end of the stream after the buffered data is drained.
250
+
251
+ If the process was blocked on input, it will become unblocked after
252
+ `write_eof()`. This method needs to be used along with the `drain()`
253
+ method, which flushes the EOF to the process.
254
+ """
255
+ ...
256
+
257
+ async def drain(self) -> None:
258
+ """Flush the write buffer and send data to the running process.
259
+
260
+ This is a flow control method that blocks until data is sent. It returns
261
+ when it is appropriate to continue writing data to the stream.
262
+ """
263
+ ...
264
+
265
+ class _StreamWriterThroughCommandRouter:
266
+ def __init__(
267
+ self,
268
+ object_id: str,
269
+ command_router_client: modal._utils.task_command_router_client.TaskCommandRouterClient,
270
+ task_id: str,
271
+ ) -> None:
272
+ """Initialize self. See help(type(self)) for accurate signature."""
273
+ ...
132
274
 
133
- ```python fixture:running_app
134
- from modal import Sandbox
275
+ def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None: ...
276
+ def write_eof(self) -> None: ...
277
+ async def drain(self) -> None: ...
135
278
 
136
- sandbox = Sandbox.create(
279
+ class _StreamWriter:
280
+ """Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
281
+ def __init__(
282
+ self,
283
+ object_id: str,
284
+ object_type: typing.Literal["sandbox", "container_process"],
285
+ client: modal.client._Client,
286
+ command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
287
+ task_id: typing.Optional[str] = None,
288
+ ) -> None:
289
+ """mdmd:hidden"""
290
+ ...
291
+
292
+ def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
293
+ """Write data to the stream but does not send it immediately.
294
+
295
+ This is non-blocking and queues the data to an internal buffer. Must be
296
+ used along with the `drain()` method, which flushes the buffer.
297
+
298
+ **Usage**
299
+
300
+ ```python fixture:sandbox
301
+ proc = sandbox.exec(
137
302
  "bash",
138
303
  "-c",
139
304
  "while read line; do echo $line; done",
140
- app=running_app,
141
305
  )
142
- 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()
306
+ proc.stdin.write(b"foo\n")
307
+ proc.stdin.write(b"bar\n")
308
+ proc.stdin.write_eof()
309
+ proc.stdin.drain()
148
310
  ```
149
311
  """
150
312
  ...
@@ -188,24 +350,14 @@ class StreamReader(typing.Generic[T]):
188
350
 
189
351
  As an asynchronous iterable, the object supports the `for` and `async for`
190
352
  statements. Just loop over the object to read in chunks.
191
-
192
- **Usage**
193
-
194
- ```python fixture:running_app
195
- from modal import Sandbox
196
-
197
- sandbox = Sandbox.create(
198
- "bash",
199
- "-c",
200
- "for i in $(seq 1 10); do echo foo; sleep 0.1; done",
201
- app=running_app,
202
- )
203
- for message in sandbox.stdout:
204
- print(f"Message: {message}")
205
- ```
206
353
  """
207
354
 
208
- _stream: typing.Optional[collections.abc.AsyncGenerator[typing.Optional[bytes], None]]
355
+ _impl: typing.Union[
356
+ _StreamReaderThroughServer,
357
+ _DevnullStreamReader,
358
+ _TextStreamReaderThroughCommandRouter,
359
+ _BytesStreamReaderThroughCommandRouter,
360
+ ]
209
361
 
210
362
  def __init__(
211
363
  self,
@@ -217,6 +369,8 @@ class StreamReader(typing.Generic[T]):
217
369
  text: bool = True,
218
370
  by_line: bool = False,
219
371
  deadline: typing.Optional[float] = None,
372
+ command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
373
+ task_id: typing.Optional[str] = None,
220
374
  ) -> None:
221
375
  """mdmd:hidden"""
222
376
  ...
@@ -228,103 +382,15 @@ class StreamReader(typing.Generic[T]):
228
382
 
229
383
  class __read_spec(typing_extensions.Protocol[T_INNER, SUPERSELF]):
230
384
  def __call__(self, /) -> T_INNER:
231
- """Fetch the entire contents of the stream until EOF.
232
-
233
- **Usage**
234
-
235
- ```python fixture:running_app
236
- from modal import Sandbox
237
-
238
- sandbox = Sandbox.create("echo", "hello", app=running_app)
239
- sandbox.wait()
240
-
241
- print(sandbox.stdout.read())
242
- ```
243
- """
385
+ """Fetch the entire contents of the stream until EOF."""
244
386
  ...
245
387
 
246
388
  async def aio(self, /) -> T_INNER:
247
- """Fetch the entire contents of the stream until EOF.
248
-
249
- **Usage**
250
-
251
- ```python fixture:running_app
252
- from modal import Sandbox
253
-
254
- sandbox = Sandbox.create("echo", "hello", app=running_app)
255
- sandbox.wait()
256
-
257
- print(sandbox.stdout.read())
258
- ```
259
- """
389
+ """Fetch the entire contents of the stream until EOF."""
260
390
  ...
261
391
 
262
392
  read: __read_spec[T, typing_extensions.Self]
263
393
 
264
- class ___consume_container_process_stream_spec(typing_extensions.Protocol[SUPERSELF]):
265
- def __call__(self, /):
266
- """Consume the container process stream and store messages in the buffer."""
267
- ...
268
-
269
- async def aio(self, /):
270
- """Consume the container process stream and store messages in the buffer."""
271
- ...
272
-
273
- _consume_container_process_stream: ___consume_container_process_stream_spec[typing_extensions.Self]
274
-
275
- class ___stream_container_process_spec(typing_extensions.Protocol[SUPERSELF]):
276
- def __call__(self, /) -> typing.Generator[tuple[typing.Optional[bytes], str], None, None]:
277
- """Streams the container process buffer to the reader."""
278
- ...
279
-
280
- def aio(self, /) -> collections.abc.AsyncGenerator[tuple[typing.Optional[bytes], str], None]:
281
- """Streams the container process buffer to the reader."""
282
- ...
283
-
284
- _stream_container_process: ___stream_container_process_spec[typing_extensions.Self]
285
-
286
- class ___get_logs_spec(typing_extensions.Protocol[SUPERSELF]):
287
- def __call__(self, /, skip_empty_messages: bool = True) -> typing.Generator[typing.Optional[bytes], None, None]:
288
- """Streams sandbox or process logs from the server to the reader.
289
-
290
- Logs returned by this method may contain partial or multiple lines at a time.
291
-
292
- When the stream receives an EOF, it yields None. Once an EOF is received,
293
- subsequent invocations will not yield logs.
294
- """
295
- ...
296
-
297
- def aio(
298
- self, /, skip_empty_messages: bool = True
299
- ) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
300
- """Streams sandbox or process logs from the server to the reader.
301
-
302
- Logs returned by this method may contain partial or multiple lines at a time.
303
-
304
- When the stream receives an EOF, it yields None. Once an EOF is received,
305
- subsequent invocations will not yield logs.
306
- """
307
- ...
308
-
309
- _get_logs: ___get_logs_spec[typing_extensions.Self]
310
-
311
- class ___get_logs_by_line_spec(typing_extensions.Protocol[SUPERSELF]):
312
- def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]:
313
- """Process logs from the server and yield complete lines only."""
314
- ...
315
-
316
- def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]:
317
- """Process logs from the server and yield complete lines only."""
318
- ...
319
-
320
- _get_logs_by_line: ___get_logs_by_line_spec[typing_extensions.Self]
321
-
322
- class ___ensure_stream_spec(typing_extensions.Protocol[SUPERSELF]):
323
- def __call__(self, /) -> typing.Generator[typing.Optional[bytes], None, None]: ...
324
- def aio(self, /) -> collections.abc.AsyncGenerator[typing.Optional[bytes], None]: ...
325
-
326
- _ensure_stream: ___ensure_stream_spec[typing_extensions.Self]
327
-
328
394
  def __iter__(self) -> typing.Iterator[T]:
329
395
  """mdmd:hidden"""
330
396
  ...
@@ -352,12 +418,16 @@ class StreamReader(typing.Generic[T]):
352
418
  class StreamWriter:
353
419
  """Provides an interface to buffer and write logs to a sandbox or container process stream (`stdin`)."""
354
420
  def __init__(
355
- self, object_id: str, object_type: typing.Literal["sandbox", "container_process"], client: modal.client.Client
421
+ self,
422
+ object_id: str,
423
+ object_type: typing.Literal["sandbox", "container_process"],
424
+ client: modal.client.Client,
425
+ command_router_client: typing.Optional[modal._utils.task_command_router_client.TaskCommandRouterClient] = None,
426
+ task_id: typing.Optional[str] = None,
356
427
  ) -> None:
357
428
  """mdmd:hidden"""
358
429
  ...
359
430
 
360
- def _get_next_index(self) -> int: ...
361
431
  def write(self, data: typing.Union[bytes, bytearray, memoryview, str]) -> None:
362
432
  """Write data to the stream but does not send it immediately.
363
433
 
@@ -366,21 +436,16 @@ class StreamWriter:
366
436
 
367
437
  **Usage**
368
438
 
369
- ```python fixture:running_app
370
- from modal import Sandbox
371
-
372
- sandbox = Sandbox.create(
439
+ ```python fixture:sandbox
440
+ proc = sandbox.exec(
373
441
  "bash",
374
442
  "-c",
375
443
  "while read line; do echo $line; done",
376
- app=running_app,
377
444
  )
378
- 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()
445
+ proc.stdin.write(b"foo\n")
446
+ proc.stdin.write(b"bar\n")
447
+ proc.stdin.write_eof()
448
+ proc.stdin.drain()
384
449
  ```
385
450
  """
386
451
  ...
modal/mount.py CHANGED
@@ -24,7 +24,7 @@ from ._object import _get_environment_name, _Object
24
24
  from ._resolver import Resolver
25
25
  from ._utils.async_utils import TaskContext, aclosing, async_map, synchronize_api
26
26
  from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
27
- from ._utils.grpc_utils import retry_transient_errors
27
+ from ._utils.grpc_utils import Retry
28
28
  from ._utils.name_utils import check_object_name
29
29
  from ._utils.package_utils import get_module_mount_info
30
30
  from .client import _Client
@@ -518,7 +518,7 @@ class _Mount(_Object, type_prefix="mo"):
518
518
 
519
519
  request = api_pb2.MountPutFileRequest(sha256_hex=file_spec.sha256_hex)
520
520
  accounted_hashes.add(file_spec.sha256_hex)
521
- response = await retry_transient_errors(resolver.client.stub.MountPutFile, request, base_delay=1)
521
+ response = await resolver.client.stub.MountPutFile(request, retry=Retry(base_delay=1))
522
522
 
523
523
  if response.exists:
524
524
  n_finished += 1
@@ -544,7 +544,7 @@ class _Mount(_Object, type_prefix="mo"):
544
544
 
545
545
  start_time = time.monotonic()
546
546
  while time.monotonic() - start_time < MOUNT_PUT_FILE_CLIENT_TIMEOUT:
547
- response = await retry_transient_errors(resolver.client.stub.MountPutFile, request2, base_delay=1)
547
+ response = await resolver.client.stub.MountPutFile(request2, retry=Retry(base_delay=1))
548
548
  if response.exists:
549
549
  n_finished += 1
550
550
  return mount_file
@@ -591,7 +591,7 @@ class _Mount(_Object, type_prefix="mo"):
591
591
  environment_name=resolver.environment_name,
592
592
  )
593
593
 
594
- resp = await retry_transient_errors(resolver.client.stub.MountGetOrCreate, req, base_delay=1)
594
+ resp = await resolver.client.stub.MountGetOrCreate(req, retry=Retry(base_delay=1))
595
595
  status_row.finish(f"Created mount {message_label}")
596
596
 
597
597
  logger.debug(f"Uploaded {total_uploads} new files and {total_bytes} bytes in {time.monotonic() - t0}s")
@@ -22,7 +22,6 @@ from ._resolver import Resolver
22
22
  from ._utils.async_utils import TaskContext, aclosing, async_map, sync_or_async_iter, synchronize_api
23
23
  from ._utils.blob_utils import LARGE_FILE_LIMIT, blob_iter, blob_upload_file
24
24
  from ._utils.deprecation import warn_if_passing_namespace
25
- from ._utils.grpc_utils import retry_transient_errors
26
25
  from ._utils.hash_utils import get_sha256_hex
27
26
  from ._utils.name_utils import check_object_name
28
27
  from .client import _Client
@@ -188,14 +187,14 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
188
187
  environment_name=_get_environment_name(environment_name),
189
188
  object_creation_type=api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS,
190
189
  )
191
- resp = await retry_transient_errors(client.stub.SharedVolumeGetOrCreate, request)
190
+ resp = await client.stub.SharedVolumeGetOrCreate(request)
192
191
  return resp.shared_volume_id
193
192
 
194
193
  @staticmethod
195
194
  async def delete(name: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
196
195
  obj = await _NetworkFileSystem.from_name(name, environment_name=environment_name).hydrate(client)
197
196
  req = api_pb2.SharedVolumeDeleteRequest(shared_volume_id=obj.object_id)
198
- await retry_transient_errors(obj._client.stub.SharedVolumeDelete, req)
197
+ await obj._client.stub.SharedVolumeDelete(req)
199
198
 
200
199
  @live_method
201
200
  async def write_file(self, remote_path: str, fp: BinaryIO, progress_cb: Optional[Callable[..., Any]] = None) -> int:
@@ -235,7 +234,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
235
234
 
236
235
  t0 = time.monotonic()
237
236
  while time.monotonic() - t0 < NETWORK_FILE_SYSTEM_PUT_FILE_CLIENT_TIMEOUT:
238
- response = await retry_transient_errors(self._client.stub.SharedVolumePutFile, req)
237
+ response = await self._client.stub.SharedVolumePutFile(req)
239
238
  if response.exists:
240
239
  break
241
240
  else:
@@ -248,7 +247,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
248
247
  """Read a file from the network file system"""
249
248
  req = api_pb2.SharedVolumeGetFileRequest(shared_volume_id=self.object_id, path=path)
250
249
  try:
251
- response = await retry_transient_errors(self._client.stub.SharedVolumeGetFile, req)
250
+ response = await self._client.stub.SharedVolumeGetFile(req)
252
251
  except modal.exception.NotFoundError as exc:
253
252
  raise FileNotFoundError(exc.args[0])
254
253
 
@@ -333,7 +332,7 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
333
332
  """Remove a file in a network file system."""
334
333
  req = api_pb2.SharedVolumeRemoveFileRequest(shared_volume_id=self.object_id, path=path, recursive=recursive)
335
334
  try:
336
- await retry_transient_errors(self._client.stub.SharedVolumeRemoveFile, req)
335
+ await self._client.stub.SharedVolumeRemoveFile(req)
337
336
  except modal.exception.NotFoundError as exc:
338
337
  raise FileNotFoundError(exc.args[0])
339
338