modal 0.71.8__py3-none-any.whl → 0.71.10__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/client.pyi +2 -2
- modal/file_io.py +101 -83
- modal/file_io.pyi +4 -23
- modal/functions.pyi +6 -6
- modal/io_streams.py +27 -15
- {modal-0.71.8.dist-info → modal-0.71.10.dist-info}/METADATA +1 -1
- {modal-0.71.8.dist-info → modal-0.71.10.dist-info}/RECORD +19 -20
- modal_proto/api.proto +6 -0
- modal_proto/api_grpc.py +16 -0
- modal_proto/api_pb2.py +83 -71
- modal_proto/api_pb2.pyi +17 -0
- modal_proto/api_pb2_grpc.py +33 -0
- modal_proto/api_pb2_grpc.pyi +10 -0
- modal_proto/modal_api_grpc.py +1 -0
- modal_version/_version_generated.py +1 -1
- modal/io_streams_helper.py +0 -53
- {modal-0.71.8.dist-info → modal-0.71.10.dist-info}/LICENSE +0 -0
- {modal-0.71.8.dist-info → modal-0.71.10.dist-info}/WHEEL +0 -0
- {modal-0.71.8.dist-info → modal-0.71.10.dist-info}/entry_points.txt +0 -0
- {modal-0.71.8.dist-info → modal-0.71.10.dist-info}/top_level.txt +0 -0
modal/client.pyi
CHANGED
@@ -26,7 +26,7 @@ class _Client:
|
|
26
26
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
27
27
|
|
28
28
|
def __init__(
|
29
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.10"
|
30
30
|
): ...
|
31
31
|
def is_closed(self) -> bool: ...
|
32
32
|
@property
|
@@ -81,7 +81,7 @@ class Client:
|
|
81
81
|
_stub: typing.Optional[modal_proto.api_grpc.ModalClientStub]
|
82
82
|
|
83
83
|
def __init__(
|
84
|
-
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.71.10"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/file_io.py
CHANGED
@@ -12,8 +12,9 @@ import json
|
|
12
12
|
|
13
13
|
from grpclib.exceptions import GRPCError, StreamTerminatedError
|
14
14
|
|
15
|
+
from modal._utils.async_utils import TaskContext
|
15
16
|
from modal._utils.grpc_utils import retry_transient_errors
|
16
|
-
from modal.
|
17
|
+
from modal.exception import ClientClosed
|
17
18
|
from modal_proto import api_pb2
|
18
19
|
|
19
20
|
from ._utils.async_utils import synchronize_api
|
@@ -56,7 +57,8 @@ async def _delete_bytes(file: "_FileIO", start: Optional[int] = None, end: Optio
|
|
56
57
|
if start is not None and end is not None:
|
57
58
|
if start >= end:
|
58
59
|
raise ValueError("start must be less than end")
|
59
|
-
resp = await
|
60
|
+
resp = await retry_transient_errors(
|
61
|
+
file._client.stub.ContainerFilesystemExec,
|
60
62
|
api_pb2.ContainerFilesystemExecRequest(
|
61
63
|
file_delete_bytes_request=api_pb2.ContainerFileDeleteBytesRequest(
|
62
64
|
file_descriptor=file._file_descriptor,
|
@@ -64,7 +66,7 @@ async def _delete_bytes(file: "_FileIO", start: Optional[int] = None, end: Optio
|
|
64
66
|
end_exclusive=end,
|
65
67
|
),
|
66
68
|
task_id=file._task_id,
|
67
|
-
)
|
69
|
+
),
|
68
70
|
)
|
69
71
|
await file._wait(resp.exec_id)
|
70
72
|
|
@@ -83,7 +85,8 @@ async def _replace_bytes(file: "_FileIO", data: bytes, start: Optional[int] = No
|
|
83
85
|
raise InvalidError("start must be less than end")
|
84
86
|
if len(data) > WRITE_CHUNK_SIZE:
|
85
87
|
raise InvalidError("Write request payload exceeds 16 MiB limit")
|
86
|
-
resp = await
|
88
|
+
resp = await retry_transient_errors(
|
89
|
+
file._client.stub.ContainerFilesystemExec,
|
87
90
|
api_pb2.ContainerFilesystemExecRequest(
|
88
91
|
file_write_replace_bytes_request=api_pb2.ContainerFileWriteReplaceBytesRequest(
|
89
92
|
file_descriptor=file._file_descriptor,
|
@@ -92,7 +95,7 @@ async def _replace_bytes(file: "_FileIO", data: bytes, start: Optional[int] = No
|
|
92
95
|
end_exclusive=end,
|
93
96
|
),
|
94
97
|
task_id=file._task_id,
|
95
|
-
)
|
98
|
+
),
|
96
99
|
)
|
97
100
|
await file._wait(resp.exec_id)
|
98
101
|
|
@@ -140,9 +143,13 @@ class _FileIO(Generic[T]):
|
|
140
143
|
|
141
144
|
_task_id: str = ""
|
142
145
|
_file_descriptor: str = ""
|
143
|
-
_client:
|
146
|
+
_client: _Client
|
144
147
|
_watch_output_buffer: list[Optional[bytes]] = []
|
145
148
|
|
149
|
+
def __init__(self, client: _Client, task_id: str) -> None:
|
150
|
+
self._client = client
|
151
|
+
self._task_id = task_id
|
152
|
+
|
146
153
|
def _validate_mode(self, mode: str) -> None:
|
147
154
|
if not any(char in mode for char in "rwax"):
|
148
155
|
raise ValueError(f"Invalid file mode: {mode}")
|
@@ -175,7 +182,6 @@ class _FileIO(Generic[T]):
|
|
175
182
|
exec_id=exec_id,
|
176
183
|
timeout=55,
|
177
184
|
)
|
178
|
-
assert self._client is not None
|
179
185
|
async for batch in self._client.stub.ContainerFilesystemExecGetOutput.unary_stream(req):
|
180
186
|
if batch.eof:
|
181
187
|
yield None
|
@@ -186,17 +192,30 @@ class _FileIO(Generic[T]):
|
|
186
192
|
yield message
|
187
193
|
|
188
194
|
async def _consume_watch_output(self, exec_id: str) -> None:
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
195
|
+
completed = False
|
196
|
+
retries_remaining = 10
|
197
|
+
while not completed:
|
198
|
+
try:
|
199
|
+
iterator = self._consume_output(exec_id)
|
200
|
+
async for message in iterator:
|
201
|
+
self._watch_output_buffer.append(message)
|
202
|
+
if message is None:
|
203
|
+
completed = True
|
204
|
+
break
|
194
205
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
206
|
+
except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
|
207
|
+
if retries_remaining > 0:
|
208
|
+
retries_remaining -= 1
|
209
|
+
if isinstance(exc, GRPCError):
|
210
|
+
if exc.status in RETRYABLE_GRPC_STATUS_CODES:
|
211
|
+
await asyncio.sleep(1.0)
|
212
|
+
continue
|
213
|
+
elif isinstance(exc, StreamTerminatedError):
|
214
|
+
continue
|
215
|
+
elif isinstance(exc, ClientClosed):
|
216
|
+
# If the client was closed, the user has triggered a cleanup.
|
217
|
+
break
|
218
|
+
raise exc
|
200
219
|
|
201
220
|
async def _parse_watch_output(self, event: bytes) -> Optional[FileWatchEvent]:
|
202
221
|
try:
|
@@ -206,23 +225,6 @@ class _FileIO(Generic[T]):
|
|
206
225
|
# skip invalid events
|
207
226
|
return None
|
208
227
|
|
209
|
-
async def _stream_watch_output(self) -> AsyncIterator[FileWatchEvent]:
|
210
|
-
buffer = b""
|
211
|
-
while True:
|
212
|
-
if len(self._watch_output_buffer) > 0:
|
213
|
-
item = self._watch_output_buffer.pop(0)
|
214
|
-
if item is None:
|
215
|
-
break
|
216
|
-
buffer += item
|
217
|
-
# a single event may be split across multiple messages, the end of an event is marked by two newlines
|
218
|
-
if buffer.endswith(b"\n\n"):
|
219
|
-
event = await self._parse_watch_output(buffer.strip())
|
220
|
-
if event is not None:
|
221
|
-
yield event
|
222
|
-
buffer = b""
|
223
|
-
else:
|
224
|
-
await asyncio.sleep(0.1)
|
225
|
-
|
226
228
|
async def _wait(self, exec_id: str) -> bytes:
|
227
229
|
# The logic here is similar to how output is read from `exec`
|
228
230
|
output = b""
|
@@ -254,11 +256,12 @@ class _FileIO(Generic[T]):
|
|
254
256
|
raise TypeError("Expected str when in text mode")
|
255
257
|
|
256
258
|
async def _open_file(self, path: str, mode: str) -> None:
|
257
|
-
resp = await
|
259
|
+
resp = await retry_transient_errors(
|
260
|
+
self._client.stub.ContainerFilesystemExec,
|
258
261
|
api_pb2.ContainerFilesystemExecRequest(
|
259
262
|
file_open_request=api_pb2.ContainerFileOpenRequest(path=path, mode=mode),
|
260
263
|
task_id=self._task_id,
|
261
|
-
)
|
264
|
+
),
|
262
265
|
)
|
263
266
|
if not resp.HasField("file_descriptor"):
|
264
267
|
raise FilesystemExecutionError("Failed to open file")
|
@@ -270,26 +273,19 @@ class _FileIO(Generic[T]):
|
|
270
273
|
cls, path: str, mode: Union["_typeshed.OpenTextMode", "_typeshed.OpenBinaryMode"], client: _Client, task_id: str
|
271
274
|
) -> "_FileIO":
|
272
275
|
"""Create a new FileIO handle."""
|
273
|
-
self =
|
274
|
-
self._client = client
|
275
|
-
self._task_id = task_id
|
276
|
+
self = _FileIO(client, task_id)
|
276
277
|
self._validate_mode(mode)
|
277
278
|
await self._open_file(path, mode)
|
278
279
|
self._closed = False
|
279
280
|
return self
|
280
281
|
|
281
|
-
async def _make_request(
|
282
|
-
self, request: api_pb2.ContainerFilesystemExecRequest
|
283
|
-
) -> api_pb2.ContainerFilesystemExecResponse:
|
284
|
-
assert self._client is not None
|
285
|
-
return await retry_transient_errors(self._client.stub.ContainerFilesystemExec, request)
|
286
|
-
|
287
282
|
async def _make_read_request(self, n: Optional[int]) -> bytes:
|
288
|
-
resp = await
|
283
|
+
resp = await retry_transient_errors(
|
284
|
+
self._client.stub.ContainerFilesystemExec,
|
289
285
|
api_pb2.ContainerFilesystemExecRequest(
|
290
286
|
file_read_request=api_pb2.ContainerFileReadRequest(file_descriptor=self._file_descriptor, n=n),
|
291
287
|
task_id=self._task_id,
|
292
|
-
)
|
288
|
+
),
|
293
289
|
)
|
294
290
|
return await self._wait(resp.exec_id)
|
295
291
|
|
@@ -308,11 +304,12 @@ class _FileIO(Generic[T]):
|
|
308
304
|
"""Read a single line from the current position."""
|
309
305
|
self._check_closed()
|
310
306
|
self._check_readable()
|
311
|
-
resp = await
|
307
|
+
resp = await retry_transient_errors(
|
308
|
+
self._client.stub.ContainerFilesystemExec,
|
312
309
|
api_pb2.ContainerFilesystemExecRequest(
|
313
310
|
file_read_line_request=api_pb2.ContainerFileReadLineRequest(file_descriptor=self._file_descriptor),
|
314
311
|
task_id=self._task_id,
|
315
|
-
)
|
312
|
+
),
|
316
313
|
)
|
317
314
|
output = await self._wait(resp.exec_id)
|
318
315
|
if self._binary:
|
@@ -349,14 +346,15 @@ class _FileIO(Generic[T]):
|
|
349
346
|
raise ValueError("Write request payload exceeds 1 GiB limit")
|
350
347
|
for i in range(0, len(data), WRITE_CHUNK_SIZE):
|
351
348
|
chunk = data[i : i + WRITE_CHUNK_SIZE]
|
352
|
-
resp = await
|
349
|
+
resp = await retry_transient_errors(
|
350
|
+
self._client.stub.ContainerFilesystemExec,
|
353
351
|
api_pb2.ContainerFilesystemExecRequest(
|
354
352
|
file_write_request=api_pb2.ContainerFileWriteRequest(
|
355
353
|
file_descriptor=self._file_descriptor,
|
356
354
|
data=chunk,
|
357
355
|
),
|
358
356
|
task_id=self._task_id,
|
359
|
-
)
|
357
|
+
),
|
360
358
|
)
|
361
359
|
await self._wait(resp.exec_id)
|
362
360
|
|
@@ -364,11 +362,12 @@ class _FileIO(Generic[T]):
|
|
364
362
|
"""Flush the buffer to disk."""
|
365
363
|
self._check_closed()
|
366
364
|
self._check_writable()
|
367
|
-
resp = await
|
365
|
+
resp = await retry_transient_errors(
|
366
|
+
self._client.stub.ContainerFilesystemExec,
|
368
367
|
api_pb2.ContainerFilesystemExecRequest(
|
369
368
|
file_flush_request=api_pb2.ContainerFileFlushRequest(file_descriptor=self._file_descriptor),
|
370
369
|
task_id=self._task_id,
|
371
|
-
)
|
370
|
+
),
|
372
371
|
)
|
373
372
|
await self._wait(resp.exec_id)
|
374
373
|
|
@@ -389,7 +388,8 @@ class _FileIO(Generic[T]):
|
|
389
388
|
(relative to the current position) and 2 (relative to the file's end).
|
390
389
|
"""
|
391
390
|
self._check_closed()
|
392
|
-
resp = await
|
391
|
+
resp = await retry_transient_errors(
|
392
|
+
self._client.stub.ContainerFilesystemExec,
|
393
393
|
api_pb2.ContainerFilesystemExecRequest(
|
394
394
|
file_seek_request=api_pb2.ContainerFileSeekRequest(
|
395
395
|
file_descriptor=self._file_descriptor,
|
@@ -397,21 +397,20 @@ class _FileIO(Generic[T]):
|
|
397
397
|
whence=self._get_whence(whence),
|
398
398
|
),
|
399
399
|
task_id=self._task_id,
|
400
|
-
)
|
400
|
+
),
|
401
401
|
)
|
402
402
|
await self._wait(resp.exec_id)
|
403
403
|
|
404
404
|
@classmethod
|
405
405
|
async def ls(cls, path: str, client: _Client, task_id: str) -> list[str]:
|
406
406
|
"""List the contents of the provided directory."""
|
407
|
-
self =
|
408
|
-
|
409
|
-
|
410
|
-
resp = await self._make_request(
|
407
|
+
self = _FileIO(client, task_id)
|
408
|
+
resp = await retry_transient_errors(
|
409
|
+
self._client.stub.ContainerFilesystemExec,
|
411
410
|
api_pb2.ContainerFilesystemExecRequest(
|
412
411
|
file_ls_request=api_pb2.ContainerFileLsRequest(path=path),
|
413
412
|
task_id=task_id,
|
414
|
-
)
|
413
|
+
),
|
415
414
|
)
|
416
415
|
output = await self._wait(resp.exec_id)
|
417
416
|
try:
|
@@ -422,28 +421,26 @@ class _FileIO(Generic[T]):
|
|
422
421
|
@classmethod
|
423
422
|
async def mkdir(cls, path: str, client: _Client, task_id: str, parents: bool = False) -> None:
|
424
423
|
"""Create a new directory."""
|
425
|
-
self =
|
426
|
-
|
427
|
-
|
428
|
-
resp = await self._make_request(
|
424
|
+
self = _FileIO(client, task_id)
|
425
|
+
resp = await retry_transient_errors(
|
426
|
+
self._client.stub.ContainerFilesystemExec,
|
429
427
|
api_pb2.ContainerFilesystemExecRequest(
|
430
428
|
file_mkdir_request=api_pb2.ContainerFileMkdirRequest(path=path, make_parents=parents),
|
431
429
|
task_id=self._task_id,
|
432
|
-
)
|
430
|
+
),
|
433
431
|
)
|
434
432
|
await self._wait(resp.exec_id)
|
435
433
|
|
436
434
|
@classmethod
|
437
435
|
async def rm(cls, path: str, client: _Client, task_id: str, recursive: bool = False) -> None:
|
438
436
|
"""Remove a file or directory in the Sandbox."""
|
439
|
-
self =
|
440
|
-
|
441
|
-
|
442
|
-
resp = await self._make_request(
|
437
|
+
self = _FileIO(client, task_id)
|
438
|
+
resp = await retry_transient_errors(
|
439
|
+
self._client.stub.ContainerFilesystemExec,
|
443
440
|
api_pb2.ContainerFilesystemExecRequest(
|
444
441
|
file_rm_request=api_pb2.ContainerFileRmRequest(path=path, recursive=recursive),
|
445
442
|
task_id=self._task_id,
|
446
|
-
)
|
443
|
+
),
|
447
444
|
)
|
448
445
|
await self._wait(resp.exec_id)
|
449
446
|
|
@@ -457,10 +454,9 @@ class _FileIO(Generic[T]):
|
|
457
454
|
recursive: bool = False,
|
458
455
|
timeout: Optional[int] = None,
|
459
456
|
) -> AsyncIterator[FileWatchEvent]:
|
460
|
-
self =
|
461
|
-
|
462
|
-
|
463
|
-
resp = await self._make_request(
|
457
|
+
self = _FileIO(client, task_id)
|
458
|
+
resp = await retry_transient_errors(
|
459
|
+
self._client.stub.ContainerFilesystemExec,
|
464
460
|
api_pb2.ContainerFilesystemExecRequest(
|
465
461
|
file_watch_request=api_pb2.ContainerFileWatchRequest(
|
466
462
|
path=path,
|
@@ -468,22 +464,44 @@ class _FileIO(Generic[T]):
|
|
468
464
|
timeout_secs=timeout,
|
469
465
|
),
|
470
466
|
task_id=self._task_id,
|
471
|
-
)
|
467
|
+
),
|
472
468
|
)
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
469
|
+
async with TaskContext() as tc:
|
470
|
+
tc.create_task(self._consume_watch_output(resp.exec_id))
|
471
|
+
|
472
|
+
buffer = b""
|
473
|
+
while True:
|
474
|
+
if len(self._watch_output_buffer) > 0:
|
475
|
+
item = self._watch_output_buffer.pop(0)
|
476
|
+
if item is None:
|
477
|
+
break
|
478
|
+
buffer += item
|
479
|
+
# a single event may be split across multiple messages
|
480
|
+
# the end of an event is marked by two newlines
|
481
|
+
if buffer.endswith(b"\n\n"):
|
482
|
+
try:
|
483
|
+
event_json = json.loads(buffer.strip().decode())
|
484
|
+
event = FileWatchEvent(
|
485
|
+
type=FileWatchEventType(event_json["event_type"]),
|
486
|
+
paths=event_json["paths"],
|
487
|
+
)
|
488
|
+
if not filter or event.type in filter:
|
489
|
+
yield event
|
490
|
+
except (json.JSONDecodeError, KeyError, ValueError):
|
491
|
+
# skip invalid events
|
492
|
+
pass
|
493
|
+
buffer = b""
|
494
|
+
else:
|
495
|
+
await asyncio.sleep(0.1)
|
479
496
|
|
480
497
|
async def _close(self) -> None:
|
481
498
|
# Buffer is flushed by the runner on close
|
482
|
-
resp = await
|
499
|
+
resp = await retry_transient_errors(
|
500
|
+
self._client.stub.ContainerFilesystemExec,
|
483
501
|
api_pb2.ContainerFilesystemExecRequest(
|
484
502
|
file_close_request=api_pb2.ContainerFileCloseRequest(file_descriptor=self._file_descriptor),
|
485
503
|
task_id=self._task_id,
|
486
|
-
)
|
504
|
+
),
|
487
505
|
)
|
488
506
|
self._closed = True
|
489
507
|
await self._wait(resp.exec_id)
|
modal/file_io.pyi
CHANGED
@@ -32,15 +32,15 @@ class FileWatchEvent:
|
|
32
32
|
class _FileIO(typing.Generic[T]):
|
33
33
|
_task_id: str
|
34
34
|
_file_descriptor: str
|
35
|
-
_client:
|
35
|
+
_client: modal.client._Client
|
36
36
|
_watch_output_buffer: list[typing.Optional[bytes]]
|
37
37
|
|
38
|
+
def __init__(self, client: modal.client._Client, task_id: str) -> None: ...
|
38
39
|
def _validate_mode(self, mode: str) -> None: ...
|
39
40
|
def _handle_error(self, error: modal_proto.api_pb2.SystemErrorMessage) -> None: ...
|
40
41
|
def _consume_output(self, exec_id: str) -> typing.AsyncIterator[typing.Optional[bytes]]: ...
|
41
42
|
async def _consume_watch_output(self, exec_id: str) -> None: ...
|
42
43
|
async def _parse_watch_output(self, event: bytes) -> typing.Optional[FileWatchEvent]: ...
|
43
|
-
def _stream_watch_output(self) -> typing.AsyncIterator[FileWatchEvent]: ...
|
44
44
|
async def _wait(self, exec_id: str) -> bytes: ...
|
45
45
|
def _validate_type(self, data: typing.Union[bytes, str]) -> None: ...
|
46
46
|
async def _open_file(self, path: str, mode: str) -> None: ...
|
@@ -52,9 +52,6 @@ class _FileIO(typing.Generic[T]):
|
|
52
52
|
client: modal.client._Client,
|
53
53
|
task_id: str,
|
54
54
|
) -> _FileIO: ...
|
55
|
-
async def _make_request(
|
56
|
-
self, request: modal_proto.api_pb2.ContainerFilesystemExecRequest
|
57
|
-
) -> modal_proto.api_pb2.ContainerFilesystemExecResponse: ...
|
58
55
|
async def _make_read_request(self, n: typing.Optional[int]) -> bytes: ...
|
59
56
|
async def read(self, n: typing.Optional[int] = None) -> T: ...
|
60
57
|
async def readline(self) -> T: ...
|
@@ -108,10 +105,10 @@ T_INNER = typing.TypeVar("T_INNER", covariant=True)
|
|
108
105
|
class FileIO(typing.Generic[T]):
|
109
106
|
_task_id: str
|
110
107
|
_file_descriptor: str
|
111
|
-
_client:
|
108
|
+
_client: modal.client.Client
|
112
109
|
_watch_output_buffer: list[typing.Optional[bytes]]
|
113
110
|
|
114
|
-
def __init__(self,
|
111
|
+
def __init__(self, client: modal.client.Client, task_id: str) -> None: ...
|
115
112
|
def _validate_mode(self, mode: str) -> None: ...
|
116
113
|
def _handle_error(self, error: modal_proto.api_pb2.SystemErrorMessage) -> None: ...
|
117
114
|
|
@@ -133,12 +130,6 @@ class FileIO(typing.Generic[T]):
|
|
133
130
|
|
134
131
|
_parse_watch_output: ___parse_watch_output_spec
|
135
132
|
|
136
|
-
class ___stream_watch_output_spec(typing_extensions.Protocol):
|
137
|
-
def __call__(self) -> typing.Iterator[FileWatchEvent]: ...
|
138
|
-
def aio(self) -> typing.AsyncIterator[FileWatchEvent]: ...
|
139
|
-
|
140
|
-
_stream_watch_output: ___stream_watch_output_spec
|
141
|
-
|
142
133
|
class ___wait_spec(typing_extensions.Protocol):
|
143
134
|
def __call__(self, exec_id: str) -> bytes: ...
|
144
135
|
async def aio(self, exec_id: str) -> bytes: ...
|
@@ -162,16 +153,6 @@ class FileIO(typing.Generic[T]):
|
|
162
153
|
task_id: str,
|
163
154
|
) -> FileIO: ...
|
164
155
|
|
165
|
-
class ___make_request_spec(typing_extensions.Protocol):
|
166
|
-
def __call__(
|
167
|
-
self, request: modal_proto.api_pb2.ContainerFilesystemExecRequest
|
168
|
-
) -> modal_proto.api_pb2.ContainerFilesystemExecResponse: ...
|
169
|
-
async def aio(
|
170
|
-
self, request: modal_proto.api_pb2.ContainerFilesystemExecRequest
|
171
|
-
) -> modal_proto.api_pb2.ContainerFilesystemExecResponse: ...
|
172
|
-
|
173
|
-
_make_request: ___make_request_spec
|
174
|
-
|
175
156
|
class ___make_read_request_spec(typing_extensions.Protocol):
|
176
157
|
def __call__(self, n: typing.Optional[int]) -> bytes: ...
|
177
158
|
async def aio(self, n: typing.Optional[int]) -> bytes: ...
|
modal/functions.pyi
CHANGED
@@ -462,11 +462,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
462
462
|
|
463
463
|
_call_generator_nowait: ___call_generator_nowait_spec
|
464
464
|
|
465
|
-
class __remote_spec(typing_extensions.Protocol[
|
465
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
466
466
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
467
467
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
468
468
|
|
469
|
-
remote: __remote_spec[
|
469
|
+
remote: __remote_spec[ReturnType, P]
|
470
470
|
|
471
471
|
class __remote_gen_spec(typing_extensions.Protocol):
|
472
472
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -479,17 +479,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
479
479
|
def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
|
480
480
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
481
481
|
|
482
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
482
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
483
483
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
484
484
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
485
485
|
|
486
|
-
_experimental_spawn: ___experimental_spawn_spec[
|
486
|
+
_experimental_spawn: ___experimental_spawn_spec[ReturnType, P]
|
487
487
|
|
488
|
-
class __spawn_spec(typing_extensions.Protocol[
|
488
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER]):
|
489
489
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
490
490
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
491
491
|
|
492
|
-
spawn: __spawn_spec[
|
492
|
+
spawn: __spawn_spec[ReturnType, P]
|
493
493
|
|
494
494
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
495
495
|
|
modal/io_streams.py
CHANGED
@@ -14,8 +14,7 @@ from typing import (
|
|
14
14
|
from grpclib import Status
|
15
15
|
from grpclib.exceptions import GRPCError, StreamTerminatedError
|
16
16
|
|
17
|
-
from modal.exception import InvalidError
|
18
|
-
from modal.io_streams_helper import consume_stream_with_retries
|
17
|
+
from modal.exception import ClientClosed, InvalidError
|
19
18
|
from modal_proto import api_pb2
|
20
19
|
|
21
20
|
from ._utils.async_utils import synchronize_api
|
@@ -177,21 +176,34 @@ class _StreamReader(Generic[T]):
|
|
177
176
|
if self._stream_type == StreamType.DEVNULL:
|
178
177
|
return
|
179
178
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
self.
|
179
|
+
completed = False
|
180
|
+
retries_remaining = 10
|
181
|
+
while not completed:
|
182
|
+
try:
|
183
|
+
iterator = _container_process_logs_iterator(self._object_id, self._file_descriptor, self._client)
|
185
184
|
|
186
|
-
|
187
|
-
|
185
|
+
async for message in iterator:
|
186
|
+
if self._stream_type == StreamType.STDOUT and message:
|
187
|
+
print(message.decode("utf-8"), end="")
|
188
|
+
elif self._stream_type == StreamType.PIPE:
|
189
|
+
self._container_process_buffer.append(message)
|
190
|
+
if message is None:
|
191
|
+
completed = True
|
192
|
+
break
|
188
193
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
194
|
+
except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
|
195
|
+
if retries_remaining > 0:
|
196
|
+
retries_remaining -= 1
|
197
|
+
if isinstance(exc, GRPCError):
|
198
|
+
if exc.status in RETRYABLE_GRPC_STATUS_CODES:
|
199
|
+
await asyncio.sleep(1.0)
|
200
|
+
continue
|
201
|
+
elif isinstance(exc, StreamTerminatedError):
|
202
|
+
continue
|
203
|
+
elif isinstance(exc, ClientClosed):
|
204
|
+
# If the client was closed, the user has triggered a cleanup.
|
205
|
+
break
|
206
|
+
raise exc
|
195
207
|
|
196
208
|
async def _stream_container_process(self) -> AsyncGenerator[tuple[Optional[bytes], str], None]:
|
197
209
|
"""Streams the container process buffer to the reader."""
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=vEE0cK5QPF6_cdW5AJvcuWxz5KmeprHwBEtlDkVRHgE,45582
|
|
19
19
|
modal/app.pyi,sha256=Gx7gxjfQ70sxhbwfpx1VjvzEON-ZEMTJ_Vy8qt0oQvo,25302
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=JAnd4-GCN093BwkvOFAK5a6iy5ycxofjpUncMxlrIMw,15253
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=3aJYJhiUvXueGEy38n1ScXHG9s4C1UlDRT6yXNwUons,7280
|
23
23
|
modal/cloud_bucket_mount.py,sha256=G7T7jWLD0QkmrfKR75mSTwdUZ2xNfj7pkVqb4ipmxmI,5735
|
24
24
|
modal/cloud_bucket_mount.pyi,sha256=CEi7vrH3kDUF4LAy4qP6tfImy2UJuFRcRbsgRNM1wo8,1403
|
25
25
|
modal/cls.py,sha256=3hjb0JcoPjxKZNeK22f5rR43bZRBjoRI7_EMZXY7YrE,31172
|
@@ -33,17 +33,16 @@ modal/environments.py,sha256=wbv9ttFCbzATGfwcmvYiG608PfHovx0AQmawsg-jmic,6660
|
|
33
33
|
modal/environments.pyi,sha256=rF7oaaELoSNuoD6qImGnIbuGPtgWwR5SlcExyYJ61hQ,3515
|
34
34
|
modal/exception.py,sha256=4JyO-SACaLNDe2QC48EjsK8GMkZ8AgEurZ8j1YdRu8E,5263
|
35
35
|
modal/experimental.py,sha256=npfKbyMpI41uZZs9HW_QiB3E4ykWfDXZbACXXbw6qeA,2385
|
36
|
-
modal/file_io.py,sha256=
|
37
|
-
modal/file_io.pyi,sha256=
|
36
|
+
modal/file_io.py,sha256=lcMs_E9Xfm0YX1t9U2wNIBPnqHRxmImqjLW1GHqVmyg,20945
|
37
|
+
modal/file_io.pyi,sha256=NrIoB0YjIqZ8MDMe826xAnybT0ww_kxQM3iPLo82REU,8898
|
38
38
|
modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms,5571
|
39
39
|
modal/functions.py,sha256=3uJPbrEAWhpFfLfUnoRjGmvEUC-_wVh-8yNJBx8eVeM,68249
|
40
|
-
modal/functions.pyi,sha256=
|
40
|
+
modal/functions.pyi,sha256=3ESJ61f8oEDycDmrpnuNB2vjFKuLBG_aqyliXPTdY7M,25407
|
41
41
|
modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
|
42
42
|
modal/image.py,sha256=oKqqLhc3Ap2XMG5MKVlERKkMTwJPkNMNcSzxoZh4zuw,85259
|
43
43
|
modal/image.pyi,sha256=Pa1_LVr3FyNsnu_MhBO08fBgCeLazTEe25phYdu0bzE,25365
|
44
|
-
modal/io_streams.py,sha256=
|
44
|
+
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
45
45
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
46
|
-
modal/io_streams_helper.py,sha256=B5Ui56ph7LkRpZX0tAF80Q-gOMsvPPLx5bpIPX0kgDc,1772
|
47
46
|
modal/mount.py,sha256=wOr-2vmKImsE3lHBII8hL2gYy5ng46R58QwId4JultQ,29313
|
48
47
|
modal/mount.pyi,sha256=FiNV1wIKFvd0ZMZ0tm1mz6ZSA5Hjsge-kFSA5tPWfcI,10503
|
49
48
|
modal/network_file_system.py,sha256=INj1TfN_Fsmabmlte7anvey1epodjbMmjBW_TIJSST4,14406
|
@@ -149,13 +148,13 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
|
|
149
148
|
modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
|
150
149
|
modal_global_objects/mounts/python_standalone.py,sha256=SL_riIxpd8mP4i4CLDCWiFFNj0Ltknm9c_UIGfX5d60,1836
|
151
150
|
modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
152
|
-
modal_proto/api.proto,sha256=
|
153
|
-
modal_proto/api_grpc.py,sha256=
|
154
|
-
modal_proto/api_pb2.py,sha256=
|
155
|
-
modal_proto/api_pb2.pyi,sha256=
|
156
|
-
modal_proto/api_pb2_grpc.py,sha256=
|
157
|
-
modal_proto/api_pb2_grpc.pyi,sha256
|
158
|
-
modal_proto/modal_api_grpc.py,sha256=
|
151
|
+
modal_proto/api.proto,sha256=1hO6_dn7DwgFra9TQSAXBt1NV4ETiiURPHe09bodinc,80368
|
152
|
+
modal_proto/api_grpc.py,sha256=VakjV_Ge3fgZDRJN6EeG2yY_LMkZvn6yVXr5SnFKIDA,103542
|
153
|
+
modal_proto/api_pb2.py,sha256=77arFb-gAsuI-7Rbr65FxAB13i-mPoJEHYvs7z5Uo1s,295839
|
154
|
+
modal_proto/api_pb2.pyi,sha256=iGrfDlLXPg8oC9QdWOFlCrNoJqB0FompkpVa-EskYOg,394251
|
155
|
+
modal_proto/api_pb2_grpc.py,sha256=OAvrG7OCPKmF-6eawqc4X5iLN0WFBQ_AvufuWnHm3Es,224030
|
156
|
+
modal_proto/api_pb2_grpc.pyi,sha256=-bE8AO-IJsg0WMwSzEEe_jnmeirta3tqj5IwTVIrF6c,52169
|
157
|
+
modal_proto/modal_api_grpc.py,sha256=kOABHGzB81bAhxqKwj79DKqdK2HlcC4U0MkFETkQwfQ,13826
|
159
158
|
modal_proto/modal_options_grpc.py,sha256=qJ1cuwA54oRqrdTyPTbvfhFZYd9HhJKK5UCwt523r3Y,120
|
160
159
|
modal_proto/options.proto,sha256=a-siq4swVbZPfaFRXAipRZzGP2bq8OsdUvjlyzAeodQ,488
|
161
160
|
modal_proto/options_grpc.py,sha256=M18X3d-8F_cNYSVM3I25dUTO5rZ0rd-vCCfynfh13Nc,125
|
@@ -166,10 +165,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
166
165
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
167
166
|
modal_version/__init__.py,sha256=BEBWj9tcbFUwzEjUrqly601rauw5cYsHdcmJHs3iu0s,470
|
168
167
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
169
|
-
modal_version/_version_generated.py,sha256=
|
170
|
-
modal-0.71.
|
171
|
-
modal-0.71.
|
172
|
-
modal-0.71.
|
173
|
-
modal-0.71.
|
174
|
-
modal-0.71.
|
175
|
-
modal-0.71.
|
168
|
+
modal_version/_version_generated.py,sha256=UflrBs-PkGVliapv6qAxiCSr0k-pq7eJQFsV4Q_x40g,149
|
169
|
+
modal-0.71.10.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
170
|
+
modal-0.71.10.dist-info/METADATA,sha256=QOEXynkfqz3tQcZs7zhfsXbS3sgF5E3ExlEac6Q9yAI,2329
|
171
|
+
modal-0.71.10.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
172
|
+
modal-0.71.10.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
173
|
+
modal-0.71.10.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
174
|
+
modal-0.71.10.dist-info/RECORD,,
|
modal_proto/api.proto
CHANGED
@@ -2680,6 +2680,11 @@ message VolumeRemoveFileRequest {
|
|
2680
2680
|
bool recursive = 3;
|
2681
2681
|
}
|
2682
2682
|
|
2683
|
+
message VolumeRenameRequest {
|
2684
|
+
string volume_id = 1 [ (modal.options.audit_target_attr) = true ];
|
2685
|
+
string name = 2;
|
2686
|
+
}
|
2687
|
+
|
2683
2688
|
message Warning {
|
2684
2689
|
enum WarningType {
|
2685
2690
|
WARNING_TYPE_UNSPECIFIED = 0;
|
@@ -2887,6 +2892,7 @@ service ModalClient {
|
|
2887
2892
|
rpc VolumePutFiles(VolumePutFilesRequest) returns (google.protobuf.Empty);
|
2888
2893
|
rpc VolumeReload(VolumeReloadRequest) returns (google.protobuf.Empty);
|
2889
2894
|
rpc VolumeRemoveFile(VolumeRemoveFileRequest) returns (google.protobuf.Empty);
|
2895
|
+
rpc VolumeRename(VolumeRenameRequest) returns (google.protobuf.Empty);
|
2890
2896
|
|
2891
2897
|
// Workspaces
|
2892
2898
|
rpc WorkspaceNameLookup(google.protobuf.Empty) returns (WorkspaceNameLookupResponse);
|