modal 0.68.1__py3-none-any.whl → 0.68.2__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/exception.py +4 -0
- modal/file_io.py +380 -0
- modal/file_io.pyi +185 -0
- modal/functions.pyi +6 -6
- modal/sandbox.py +40 -0
- modal/sandbox.pyi +18 -0
- {modal-0.68.1.dist-info → modal-0.68.2.dist-info}/METADATA +2 -2
- {modal-0.68.1.dist-info → modal-0.68.2.dist-info}/RECORD +15 -13
- modal_docs/gen_reference_docs.py +1 -0
- modal_version/_version_generated.py +1 -1
- {modal-0.68.1.dist-info → modal-0.68.2.dist-info}/LICENSE +0 -0
- {modal-0.68.1.dist-info → modal-0.68.2.dist-info}/WHEEL +0 -0
- {modal-0.68.1.dist-info → modal-0.68.2.dist-info}/entry_points.txt +0 -0
- {modal-0.68.1.dist-info → modal-0.68.2.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.68.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.2"
|
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.68.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.68.2"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/exception.py
CHANGED
@@ -222,6 +222,10 @@ class ClientClosed(Error):
|
|
222
222
|
pass
|
223
223
|
|
224
224
|
|
225
|
+
class FilesystemExecutionError(Error):
|
226
|
+
"""Raised when an unknown error is thrown during a container filesystem operation."""
|
227
|
+
|
228
|
+
|
225
229
|
def print_server_warnings(server_warnings: Iterable[api_pb2.Warning]):
|
226
230
|
# TODO(erikbern): move this to modal._utils.deprecation
|
227
231
|
for warning in server_warnings:
|
modal/file_io.py
ADDED
@@ -0,0 +1,380 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import asyncio
|
3
|
+
import io
|
4
|
+
from typing import TYPE_CHECKING, AsyncIterator, Generic, Optional, Sequence, TypeVar, Union, cast
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
import _typeshed
|
8
|
+
|
9
|
+
from grpclib.exceptions import GRPCError, StreamTerminatedError
|
10
|
+
|
11
|
+
from modal._utils.grpc_utils import retry_transient_errors
|
12
|
+
from modal_proto import api_pb2
|
13
|
+
|
14
|
+
from ._utils.async_utils import synchronize_api
|
15
|
+
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
16
|
+
from .client import _Client
|
17
|
+
from .exception import FilesystemExecutionError, InvalidError
|
18
|
+
|
19
|
+
LARGE_FILE_SIZE_LIMIT = 16 * 1024 * 1024 # 16 MiB
|
20
|
+
READ_FILE_SIZE_LIMIT = 100 * 1024 * 1024 # 100 MiB
|
21
|
+
|
22
|
+
ERROR_MAPPING = {
|
23
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_UNSPECIFIED: FilesystemExecutionError,
|
24
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_PERM: PermissionError,
|
25
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_NOENT: FileNotFoundError,
|
26
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_IO: IOError,
|
27
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_NXIO: IOError,
|
28
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_NOMEM: MemoryError,
|
29
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_ACCES: PermissionError,
|
30
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_EXIST: FileExistsError,
|
31
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_NOTDIR: NotADirectoryError,
|
32
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_ISDIR: IsADirectoryError,
|
33
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_INVAL: OSError,
|
34
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_MFILE: OSError,
|
35
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_FBIG: OSError,
|
36
|
+
api_pb2.SystemErrorCode.SYSTEM_ERROR_CODE_NOSPC: OSError,
|
37
|
+
}
|
38
|
+
|
39
|
+
T = TypeVar("T", str, bytes)
|
40
|
+
|
41
|
+
|
42
|
+
async def _delete_bytes(file: "_FileIO", start: Optional[int] = None, end: Optional[int] = None) -> None:
|
43
|
+
"""Delete a range of bytes from the file.
|
44
|
+
|
45
|
+
`start` and `end` are byte offsets. `start` is inclusive, `end` is exclusive.
|
46
|
+
If either is None, the start or end of the file is used, respectively.
|
47
|
+
"""
|
48
|
+
assert file._file_descriptor is not None
|
49
|
+
file._check_closed()
|
50
|
+
if start is not None and end is not None:
|
51
|
+
if start >= end:
|
52
|
+
raise ValueError("start must be less than end")
|
53
|
+
resp = await file._make_request(
|
54
|
+
api_pb2.ContainerFilesystemExecRequest(
|
55
|
+
file_delete_bytes_request=api_pb2.ContainerFileDeleteBytesRequest(
|
56
|
+
file_descriptor=file._file_descriptor,
|
57
|
+
start_inclusive=start,
|
58
|
+
end_exclusive=end,
|
59
|
+
),
|
60
|
+
task_id=file._task_id,
|
61
|
+
)
|
62
|
+
)
|
63
|
+
await file._wait(resp.exec_id)
|
64
|
+
|
65
|
+
|
66
|
+
async def _replace_bytes(file: "_FileIO", data: bytes, start: Optional[int] = None, end: Optional[int] = None) -> None:
|
67
|
+
"""Replace a range of bytes in the file with new data. The length of the data does not
|
68
|
+
have to be the same as the length of the range being replaced.
|
69
|
+
|
70
|
+
`start` and `end` are byte offsets. `start` is inclusive, `end` is exclusive.
|
71
|
+
If either is None, the start or end of the file is used, respectively.
|
72
|
+
"""
|
73
|
+
assert file._file_descriptor is not None
|
74
|
+
file._check_closed()
|
75
|
+
if start is not None and end is not None:
|
76
|
+
if start >= end:
|
77
|
+
raise InvalidError("start must be less than end")
|
78
|
+
if len(data) > LARGE_FILE_SIZE_LIMIT:
|
79
|
+
raise InvalidError("Write request payload exceeds 16 MiB limit")
|
80
|
+
resp = await file._make_request(
|
81
|
+
api_pb2.ContainerFilesystemExecRequest(
|
82
|
+
file_write_replace_bytes_request=api_pb2.ContainerFileWriteReplaceBytesRequest(
|
83
|
+
file_descriptor=file._file_descriptor,
|
84
|
+
data=data,
|
85
|
+
start_inclusive=start,
|
86
|
+
end_exclusive=end,
|
87
|
+
),
|
88
|
+
task_id=file._task_id,
|
89
|
+
)
|
90
|
+
)
|
91
|
+
await file._wait(resp.exec_id)
|
92
|
+
|
93
|
+
|
94
|
+
# The FileIO class is designed to mimic Python's io.FileIO
|
95
|
+
# See https://github.com/python/cpython/blob/main/Lib/_pyio.py#L1459
|
96
|
+
class _FileIO(Generic[T]):
|
97
|
+
"""FileIO handle, used in the Sandbox filesystem API.
|
98
|
+
|
99
|
+
The API is designed to mimic Python's io.FileIO.
|
100
|
+
|
101
|
+
**Usage**
|
102
|
+
|
103
|
+
```python
|
104
|
+
import modal
|
105
|
+
|
106
|
+
app = modal.App.lookup("my-app", create_if_missing=True)
|
107
|
+
|
108
|
+
sb = modal.Sandbox.create(app=app)
|
109
|
+
f = sb.open("/tmp/foo.txt", "w")
|
110
|
+
f.write("hello")
|
111
|
+
f.close()
|
112
|
+
```
|
113
|
+
"""
|
114
|
+
|
115
|
+
_binary = False
|
116
|
+
_readable = False
|
117
|
+
_writable = False
|
118
|
+
_appended = False
|
119
|
+
_closed = True
|
120
|
+
|
121
|
+
_task_id: str = ""
|
122
|
+
_file_descriptor: str = ""
|
123
|
+
_client: Optional[_Client] = None
|
124
|
+
|
125
|
+
def _validate_mode(self, mode: str) -> None:
|
126
|
+
if not any(char in mode for char in "rwax"):
|
127
|
+
raise ValueError(f"Invalid file mode: {mode}")
|
128
|
+
|
129
|
+
self._readable = "r" in mode or "+" in mode
|
130
|
+
self._writable = "w" in mode or "a" in mode or "x" in mode or "+" in mode
|
131
|
+
self._appended = "a" in mode
|
132
|
+
self._binary = "b" in mode
|
133
|
+
|
134
|
+
valid_chars = set("rwaxb+")
|
135
|
+
if any(char not in valid_chars for char in mode):
|
136
|
+
raise ValueError(f"Invalid file mode: {mode}")
|
137
|
+
|
138
|
+
mode_count = sum(1 for c in mode if c in "rwax")
|
139
|
+
if mode_count > 1:
|
140
|
+
raise ValueError("must have exactly one of create/read/write/append mode")
|
141
|
+
|
142
|
+
seen_chars = set()
|
143
|
+
for char in mode:
|
144
|
+
if char in seen_chars:
|
145
|
+
raise ValueError(f"Invalid file mode: {mode}")
|
146
|
+
seen_chars.add(char)
|
147
|
+
|
148
|
+
def _handle_error(self, error: api_pb2.SystemErrorMessage) -> None:
|
149
|
+
error_class = ERROR_MAPPING.get(error.error_code, FilesystemExecutionError)
|
150
|
+
raise error_class(error.error_message)
|
151
|
+
|
152
|
+
async def _consume_output(self, exec_id: str) -> AsyncIterator[Optional[bytes]]:
|
153
|
+
req = api_pb2.ContainerFilesystemExecGetOutputRequest(
|
154
|
+
exec_id=exec_id,
|
155
|
+
timeout=55,
|
156
|
+
)
|
157
|
+
assert self._client is not None
|
158
|
+
async for batch in self._client.stub.ContainerFilesystemExecGetOutput.unary_stream(req):
|
159
|
+
if batch.eof:
|
160
|
+
yield None
|
161
|
+
break
|
162
|
+
if batch.HasField("error"):
|
163
|
+
self._handle_error(batch.error)
|
164
|
+
for message in batch.output:
|
165
|
+
yield message
|
166
|
+
|
167
|
+
async def _wait(self, exec_id: str) -> bytes:
|
168
|
+
# The logic here is similar to how output is read from `exec`
|
169
|
+
output = b""
|
170
|
+
completed = False
|
171
|
+
retries_remaining = 10
|
172
|
+
while not completed:
|
173
|
+
try:
|
174
|
+
async for data in self._consume_output(exec_id):
|
175
|
+
if data is None:
|
176
|
+
completed = True
|
177
|
+
break
|
178
|
+
output += data
|
179
|
+
except (GRPCError, StreamTerminatedError) as exc:
|
180
|
+
if retries_remaining > 0:
|
181
|
+
retries_remaining -= 1
|
182
|
+
if isinstance(exc, GRPCError):
|
183
|
+
if exc.status in RETRYABLE_GRPC_STATUS_CODES:
|
184
|
+
await asyncio.sleep(1.0)
|
185
|
+
continue
|
186
|
+
elif isinstance(exc, StreamTerminatedError):
|
187
|
+
continue
|
188
|
+
raise
|
189
|
+
return output
|
190
|
+
|
191
|
+
def _validate_type(self, data: Union[bytes, str]) -> None:
|
192
|
+
if self._binary and isinstance(data, str):
|
193
|
+
raise TypeError("Expected bytes when in binary mode")
|
194
|
+
if not self._binary and isinstance(data, bytes):
|
195
|
+
raise TypeError("Expected str when in text mode")
|
196
|
+
|
197
|
+
async def _open_file(self, path: str, mode: str) -> None:
|
198
|
+
resp = await self._make_request(
|
199
|
+
api_pb2.ContainerFilesystemExecRequest(
|
200
|
+
file_open_request=api_pb2.ContainerFileOpenRequest(path=path, mode=mode),
|
201
|
+
task_id=self._task_id,
|
202
|
+
)
|
203
|
+
)
|
204
|
+
if not resp.HasField("file_descriptor"):
|
205
|
+
raise FilesystemExecutionError("Failed to open file")
|
206
|
+
self._file_descriptor = resp.file_descriptor
|
207
|
+
await self._wait(resp.exec_id)
|
208
|
+
|
209
|
+
@classmethod
|
210
|
+
async def create(
|
211
|
+
cls, path: str, mode: Union["_typeshed.OpenTextMode", "_typeshed.OpenBinaryMode"], client: _Client, task_id: str
|
212
|
+
) -> "_FileIO":
|
213
|
+
"""Create a new FileIO handle."""
|
214
|
+
self = cls.__new__(cls)
|
215
|
+
self._client = client
|
216
|
+
self._task_id = task_id
|
217
|
+
self._validate_mode(mode)
|
218
|
+
await self._open_file(path, mode)
|
219
|
+
self._closed = False
|
220
|
+
return self
|
221
|
+
|
222
|
+
async def _make_request(
|
223
|
+
self, request: api_pb2.ContainerFilesystemExecRequest
|
224
|
+
) -> api_pb2.ContainerFilesystemExecResponse:
|
225
|
+
assert self._client is not None
|
226
|
+
return await retry_transient_errors(self._client.stub.ContainerFilesystemExec, request)
|
227
|
+
|
228
|
+
async def _make_read_request(self, n: Optional[int]) -> bytes:
|
229
|
+
resp = await self._make_request(
|
230
|
+
api_pb2.ContainerFilesystemExecRequest(
|
231
|
+
file_read_request=api_pb2.ContainerFileReadRequest(file_descriptor=self._file_descriptor, n=n),
|
232
|
+
task_id=self._task_id,
|
233
|
+
)
|
234
|
+
)
|
235
|
+
return await self._wait(resp.exec_id)
|
236
|
+
|
237
|
+
async def read(self, n: Optional[int] = None) -> T:
|
238
|
+
"""Read n bytes from the current position, or the entire remaining file if n is None."""
|
239
|
+
self._check_closed()
|
240
|
+
self._check_readable()
|
241
|
+
if n is not None and n > READ_FILE_SIZE_LIMIT:
|
242
|
+
raise ValueError("Read request payload exceeds 100 MiB limit")
|
243
|
+
output = await self._make_read_request(n)
|
244
|
+
if self._binary:
|
245
|
+
return cast(T, output)
|
246
|
+
return cast(T, output.decode("utf-8"))
|
247
|
+
|
248
|
+
async def readline(self) -> T:
|
249
|
+
"""Read a single line from the current position."""
|
250
|
+
self._check_closed()
|
251
|
+
self._check_readable()
|
252
|
+
resp = await self._make_request(
|
253
|
+
api_pb2.ContainerFilesystemExecRequest(
|
254
|
+
file_read_line_request=api_pb2.ContainerFileReadLineRequest(file_descriptor=self._file_descriptor),
|
255
|
+
task_id=self._task_id,
|
256
|
+
)
|
257
|
+
)
|
258
|
+
output = await self._wait(resp.exec_id)
|
259
|
+
if self._binary:
|
260
|
+
return cast(T, output)
|
261
|
+
return cast(T, output.decode("utf-8"))
|
262
|
+
|
263
|
+
async def readlines(self) -> Sequence[T]:
|
264
|
+
"""Read all lines from the current position."""
|
265
|
+
self._check_closed()
|
266
|
+
self._check_readable()
|
267
|
+
output = await self._make_read_request(None)
|
268
|
+
if self._binary:
|
269
|
+
lines_bytes = output.split(b"\n")
|
270
|
+
output = [line + b"\n" for line in lines_bytes[:-1]] + ([lines_bytes[-1]] if lines_bytes[-1] else [])
|
271
|
+
return cast(Sequence[T], output)
|
272
|
+
else:
|
273
|
+
lines = output.decode("utf-8").split("\n")
|
274
|
+
output = [line + "\n" for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
|
275
|
+
return cast(Sequence[T], output)
|
276
|
+
|
277
|
+
async def write(self, data: Union[bytes, str]) -> None:
|
278
|
+
"""Write data to the current position.
|
279
|
+
|
280
|
+
Writes may not appear until the entire buffer is flushed, which
|
281
|
+
can be done manually with `flush()` or automatically when the file is
|
282
|
+
closed.
|
283
|
+
"""
|
284
|
+
self._check_closed()
|
285
|
+
self._check_writable()
|
286
|
+
self._validate_type(data)
|
287
|
+
if isinstance(data, str):
|
288
|
+
data = data.encode("utf-8")
|
289
|
+
if len(data) > LARGE_FILE_SIZE_LIMIT:
|
290
|
+
raise ValueError("Write request payload exceeds 16 MiB limit")
|
291
|
+
resp = await self._make_request(
|
292
|
+
api_pb2.ContainerFilesystemExecRequest(
|
293
|
+
file_write_request=api_pb2.ContainerFileWriteRequest(file_descriptor=self._file_descriptor, data=data),
|
294
|
+
task_id=self._task_id,
|
295
|
+
)
|
296
|
+
)
|
297
|
+
await self._wait(resp.exec_id)
|
298
|
+
|
299
|
+
async def flush(self) -> None:
|
300
|
+
"""Flush the buffer to disk."""
|
301
|
+
self._check_closed()
|
302
|
+
self._check_writable()
|
303
|
+
resp = await self._make_request(
|
304
|
+
api_pb2.ContainerFilesystemExecRequest(
|
305
|
+
file_flush_request=api_pb2.ContainerFileFlushRequest(file_descriptor=self._file_descriptor),
|
306
|
+
task_id=self._task_id,
|
307
|
+
)
|
308
|
+
)
|
309
|
+
await self._wait(resp.exec_id)
|
310
|
+
|
311
|
+
def _get_whence(self, whence: int):
|
312
|
+
if whence == 0:
|
313
|
+
return api_pb2.SeekWhence.SEEK_SET
|
314
|
+
elif whence == 1:
|
315
|
+
return api_pb2.SeekWhence.SEEK_CUR
|
316
|
+
elif whence == 2:
|
317
|
+
return api_pb2.SeekWhence.SEEK_END
|
318
|
+
else:
|
319
|
+
raise ValueError(f"Invalid whence value: {whence}")
|
320
|
+
|
321
|
+
async def seek(self, offset: int, whence: int = 0) -> None:
|
322
|
+
"""Move to a new position in the file.
|
323
|
+
|
324
|
+
`whence` defaults to 0 (absolute file positioning); other values are 1
|
325
|
+
(relative to the current position) and 2 (relative to the file's end).
|
326
|
+
"""
|
327
|
+
self._check_closed()
|
328
|
+
resp = await self._make_request(
|
329
|
+
api_pb2.ContainerFilesystemExecRequest(
|
330
|
+
file_seek_request=api_pb2.ContainerFileSeekRequest(
|
331
|
+
file_descriptor=self._file_descriptor,
|
332
|
+
offset=offset,
|
333
|
+
whence=self._get_whence(whence),
|
334
|
+
),
|
335
|
+
task_id=self._task_id,
|
336
|
+
)
|
337
|
+
)
|
338
|
+
await self._wait(resp.exec_id)
|
339
|
+
|
340
|
+
async def _close(self) -> None:
|
341
|
+
# Buffer is flushed by the runner on close
|
342
|
+
resp = await self._make_request(
|
343
|
+
api_pb2.ContainerFilesystemExecRequest(
|
344
|
+
file_close_request=api_pb2.ContainerFileCloseRequest(file_descriptor=self._file_descriptor),
|
345
|
+
task_id=self._task_id,
|
346
|
+
)
|
347
|
+
)
|
348
|
+
self._closed = True
|
349
|
+
await self._wait(resp.exec_id)
|
350
|
+
|
351
|
+
async def close(self) -> None:
|
352
|
+
"""Flush the buffer and close the file."""
|
353
|
+
await self._close()
|
354
|
+
|
355
|
+
# also validated in the runner, but checked in the client to catch errors early
|
356
|
+
def _check_writable(self) -> None:
|
357
|
+
if not self._writable:
|
358
|
+
raise io.UnsupportedOperation("not writeable")
|
359
|
+
|
360
|
+
# also validated in the runner, but checked in the client to catch errors early
|
361
|
+
def _check_readable(self) -> None:
|
362
|
+
if not self._readable:
|
363
|
+
raise io.UnsupportedOperation("not readable")
|
364
|
+
|
365
|
+
# also validated in the runner, but checked in the client to catch errors early
|
366
|
+
def _check_closed(self) -> None:
|
367
|
+
if self._closed:
|
368
|
+
raise ValueError("I/O operation on closed file")
|
369
|
+
|
370
|
+
def __enter__(self) -> "_FileIO":
|
371
|
+
self._check_closed()
|
372
|
+
return self
|
373
|
+
|
374
|
+
async def __exit__(self, exc_type, exc_value, traceback) -> None:
|
375
|
+
await self._close()
|
376
|
+
|
377
|
+
|
378
|
+
delete_bytes = synchronize_api(_delete_bytes)
|
379
|
+
replace_bytes = synchronize_api(_replace_bytes)
|
380
|
+
FileIO = synchronize_api(_FileIO)
|
modal/file_io.pyi
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
import _typeshed
|
2
|
+
import modal.client
|
3
|
+
import modal_proto.api_pb2
|
4
|
+
import typing
|
5
|
+
import typing_extensions
|
6
|
+
|
7
|
+
T = typing.TypeVar("T")
|
8
|
+
|
9
|
+
async def _delete_bytes(
|
10
|
+
file: _FileIO, start: typing.Optional[int] = None, end: typing.Optional[int] = None
|
11
|
+
) -> None: ...
|
12
|
+
async def _replace_bytes(
|
13
|
+
file: _FileIO, data: bytes, start: typing.Optional[int] = None, end: typing.Optional[int] = None
|
14
|
+
) -> None: ...
|
15
|
+
|
16
|
+
class _FileIO(typing.Generic[T]):
|
17
|
+
_task_id: str
|
18
|
+
_file_descriptor: str
|
19
|
+
_client: typing.Optional[modal.client._Client]
|
20
|
+
|
21
|
+
def _validate_mode(self, mode: str) -> None: ...
|
22
|
+
def _handle_error(self, error: modal_proto.api_pb2.SystemErrorMessage) -> None: ...
|
23
|
+
def _consume_output(self, exec_id: str) -> typing.AsyncIterator[typing.Optional[bytes]]: ...
|
24
|
+
async def _wait(self, exec_id: str) -> bytes: ...
|
25
|
+
def _validate_type(self, data: typing.Union[bytes, str]) -> None: ...
|
26
|
+
async def _open_file(self, path: str, mode: str) -> None: ...
|
27
|
+
@classmethod
|
28
|
+
async def create(
|
29
|
+
cls,
|
30
|
+
path: str,
|
31
|
+
mode: typing.Union[_typeshed.OpenTextMode, _typeshed.OpenBinaryMode],
|
32
|
+
client: modal.client._Client,
|
33
|
+
task_id: str,
|
34
|
+
) -> _FileIO: ...
|
35
|
+
async def _make_request(
|
36
|
+
self, request: modal_proto.api_pb2.ContainerFilesystemExecRequest
|
37
|
+
) -> modal_proto.api_pb2.ContainerFilesystemExecResponse: ...
|
38
|
+
async def _make_read_request(self, n: typing.Optional[int]) -> bytes: ...
|
39
|
+
async def read(self, n: typing.Optional[int] = None) -> T: ...
|
40
|
+
async def readline(self) -> T: ...
|
41
|
+
async def readlines(self) -> typing.Sequence[T]: ...
|
42
|
+
async def write(self, data: typing.Union[bytes, str]) -> None: ...
|
43
|
+
async def flush(self) -> None: ...
|
44
|
+
def _get_whence(self, whence: int): ...
|
45
|
+
async def seek(self, offset: int, whence: int = 0) -> None: ...
|
46
|
+
async def _close(self) -> None: ...
|
47
|
+
async def close(self) -> None: ...
|
48
|
+
def _check_writable(self) -> None: ...
|
49
|
+
def _check_readable(self) -> None: ...
|
50
|
+
def _check_closed(self) -> None: ...
|
51
|
+
def __enter__(self) -> _FileIO: ...
|
52
|
+
async def __exit__(self, exc_type, exc_value, traceback) -> None: ...
|
53
|
+
|
54
|
+
class __delete_bytes_spec(typing_extensions.Protocol):
|
55
|
+
def __call__(self, file: FileIO, start: typing.Optional[int] = None, end: typing.Optional[int] = None) -> None: ...
|
56
|
+
async def aio(self, file: FileIO, start: typing.Optional[int] = None, end: typing.Optional[int] = None) -> None: ...
|
57
|
+
|
58
|
+
delete_bytes: __delete_bytes_spec
|
59
|
+
|
60
|
+
class __replace_bytes_spec(typing_extensions.Protocol):
|
61
|
+
def __call__(
|
62
|
+
self, file: FileIO, data: bytes, start: typing.Optional[int] = None, end: typing.Optional[int] = None
|
63
|
+
) -> None: ...
|
64
|
+
async def aio(
|
65
|
+
self, file: FileIO, data: bytes, start: typing.Optional[int] = None, end: typing.Optional[int] = None
|
66
|
+
) -> None: ...
|
67
|
+
|
68
|
+
replace_bytes: __replace_bytes_spec
|
69
|
+
|
70
|
+
T_INNER = typing.TypeVar("T_INNER", covariant=True)
|
71
|
+
|
72
|
+
class FileIO(typing.Generic[T]):
|
73
|
+
_task_id: str
|
74
|
+
_file_descriptor: str
|
75
|
+
_client: typing.Optional[modal.client.Client]
|
76
|
+
|
77
|
+
def __init__(self, /, *args, **kwargs): ...
|
78
|
+
def _validate_mode(self, mode: str) -> None: ...
|
79
|
+
def _handle_error(self, error: modal_proto.api_pb2.SystemErrorMessage) -> None: ...
|
80
|
+
|
81
|
+
class ___consume_output_spec(typing_extensions.Protocol):
|
82
|
+
def __call__(self, exec_id: str) -> typing.Iterator[typing.Optional[bytes]]: ...
|
83
|
+
def aio(self, exec_id: str) -> typing.AsyncIterator[typing.Optional[bytes]]: ...
|
84
|
+
|
85
|
+
_consume_output: ___consume_output_spec
|
86
|
+
|
87
|
+
class ___wait_spec(typing_extensions.Protocol):
|
88
|
+
def __call__(self, exec_id: str) -> bytes: ...
|
89
|
+
async def aio(self, exec_id: str) -> bytes: ...
|
90
|
+
|
91
|
+
_wait: ___wait_spec
|
92
|
+
|
93
|
+
def _validate_type(self, data: typing.Union[bytes, str]) -> None: ...
|
94
|
+
|
95
|
+
class ___open_file_spec(typing_extensions.Protocol):
|
96
|
+
def __call__(self, path: str, mode: str) -> None: ...
|
97
|
+
async def aio(self, path: str, mode: str) -> None: ...
|
98
|
+
|
99
|
+
_open_file: ___open_file_spec
|
100
|
+
|
101
|
+
@classmethod
|
102
|
+
def create(
|
103
|
+
cls,
|
104
|
+
path: str,
|
105
|
+
mode: typing.Union[_typeshed.OpenTextMode, _typeshed.OpenBinaryMode],
|
106
|
+
client: modal.client.Client,
|
107
|
+
task_id: str,
|
108
|
+
) -> FileIO: ...
|
109
|
+
|
110
|
+
class ___make_request_spec(typing_extensions.Protocol):
|
111
|
+
def __call__(
|
112
|
+
self, request: modal_proto.api_pb2.ContainerFilesystemExecRequest
|
113
|
+
) -> modal_proto.api_pb2.ContainerFilesystemExecResponse: ...
|
114
|
+
async def aio(
|
115
|
+
self, request: modal_proto.api_pb2.ContainerFilesystemExecRequest
|
116
|
+
) -> modal_proto.api_pb2.ContainerFilesystemExecResponse: ...
|
117
|
+
|
118
|
+
_make_request: ___make_request_spec
|
119
|
+
|
120
|
+
class ___make_read_request_spec(typing_extensions.Protocol):
|
121
|
+
def __call__(self, n: typing.Optional[int]) -> bytes: ...
|
122
|
+
async def aio(self, n: typing.Optional[int]) -> bytes: ...
|
123
|
+
|
124
|
+
_make_read_request: ___make_read_request_spec
|
125
|
+
|
126
|
+
class __read_spec(typing_extensions.Protocol[T_INNER]):
|
127
|
+
def __call__(self, n: typing.Optional[int] = None) -> T_INNER: ...
|
128
|
+
async def aio(self, n: typing.Optional[int] = None) -> T_INNER: ...
|
129
|
+
|
130
|
+
read: __read_spec[T]
|
131
|
+
|
132
|
+
class __readline_spec(typing_extensions.Protocol[T_INNER]):
|
133
|
+
def __call__(self) -> T_INNER: ...
|
134
|
+
async def aio(self) -> T_INNER: ...
|
135
|
+
|
136
|
+
readline: __readline_spec[T]
|
137
|
+
|
138
|
+
class __readlines_spec(typing_extensions.Protocol[T_INNER]):
|
139
|
+
def __call__(self) -> typing.Sequence[T_INNER]: ...
|
140
|
+
async def aio(self) -> typing.Sequence[T_INNER]: ...
|
141
|
+
|
142
|
+
readlines: __readlines_spec[T]
|
143
|
+
|
144
|
+
class __write_spec(typing_extensions.Protocol):
|
145
|
+
def __call__(self, data: typing.Union[bytes, str]) -> None: ...
|
146
|
+
async def aio(self, data: typing.Union[bytes, str]) -> None: ...
|
147
|
+
|
148
|
+
write: __write_spec
|
149
|
+
|
150
|
+
class __flush_spec(typing_extensions.Protocol):
|
151
|
+
def __call__(self) -> None: ...
|
152
|
+
async def aio(self) -> None: ...
|
153
|
+
|
154
|
+
flush: __flush_spec
|
155
|
+
|
156
|
+
def _get_whence(self, whence: int): ...
|
157
|
+
|
158
|
+
class __seek_spec(typing_extensions.Protocol):
|
159
|
+
def __call__(self, offset: int, whence: int = 0) -> None: ...
|
160
|
+
async def aio(self, offset: int, whence: int = 0) -> None: ...
|
161
|
+
|
162
|
+
seek: __seek_spec
|
163
|
+
|
164
|
+
class ___close_spec(typing_extensions.Protocol):
|
165
|
+
def __call__(self) -> None: ...
|
166
|
+
async def aio(self) -> None: ...
|
167
|
+
|
168
|
+
_close: ___close_spec
|
169
|
+
|
170
|
+
class __close_spec(typing_extensions.Protocol):
|
171
|
+
def __call__(self) -> None: ...
|
172
|
+
async def aio(self) -> None: ...
|
173
|
+
|
174
|
+
close: __close_spec
|
175
|
+
|
176
|
+
def _check_writable(self) -> None: ...
|
177
|
+
def _check_readable(self) -> None: ...
|
178
|
+
def _check_closed(self) -> None: ...
|
179
|
+
def __enter__(self) -> FileIO: ...
|
180
|
+
|
181
|
+
class ____exit___spec(typing_extensions.Protocol):
|
182
|
+
def __call__(self, exc_type, exc_value, traceback) -> None: ...
|
183
|
+
async def aio(self, exc_type, exc_value, traceback) -> None: ...
|
184
|
+
|
185
|
+
__exit__: ____exit___spec
|
modal/functions.pyi
CHANGED
@@ -455,11 +455,11 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
455
455
|
|
456
456
|
_call_generator_nowait: ___call_generator_nowait_spec
|
457
457
|
|
458
|
-
class __remote_spec(typing_extensions.Protocol[
|
458
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
459
459
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
460
460
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
461
461
|
|
462
|
-
remote: __remote_spec[
|
462
|
+
remote: __remote_spec[P, ReturnType]
|
463
463
|
|
464
464
|
class __remote_gen_spec(typing_extensions.Protocol):
|
465
465
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
@@ -471,17 +471,17 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
471
471
|
def _get_obj(self) -> typing.Optional[modal.cls.Obj]: ...
|
472
472
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
473
473
|
|
474
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
474
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
475
475
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
476
476
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
477
477
|
|
478
|
-
_experimental_spawn: ___experimental_spawn_spec[
|
478
|
+
_experimental_spawn: ___experimental_spawn_spec[P, ReturnType]
|
479
479
|
|
480
|
-
class __spawn_spec(typing_extensions.Protocol[
|
480
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
481
481
|
def __call__(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
482
482
|
async def aio(self, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
483
483
|
|
484
|
-
spawn: __spawn_spec[
|
484
|
+
spawn: __spawn_spec[P, ReturnType]
|
485
485
|
|
486
486
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
487
487
|
|
modal/sandbox.py
CHANGED
@@ -4,6 +4,9 @@ import os
|
|
4
4
|
from collections.abc import AsyncGenerator, Sequence
|
5
5
|
from typing import TYPE_CHECKING, Literal, Optional, Union, overload
|
6
6
|
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
import _typeshed
|
9
|
+
|
7
10
|
from google.protobuf.message import Message
|
8
11
|
from grpclib import GRPCError, Status
|
9
12
|
|
@@ -29,6 +32,7 @@ from .exception import (
|
|
29
32
|
deprecation_error,
|
30
33
|
deprecation_warning,
|
31
34
|
)
|
35
|
+
from .file_io import _FileIO
|
32
36
|
from .gpu import GPU_T
|
33
37
|
from .image import _Image
|
34
38
|
from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
|
@@ -527,6 +531,42 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
527
531
|
by_line = bufsize == 1
|
528
532
|
return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line)
|
529
533
|
|
534
|
+
@overload
|
535
|
+
async def open(
|
536
|
+
self,
|
537
|
+
path: str,
|
538
|
+
mode: "_typeshed.OpenTextMode",
|
539
|
+
) -> _FileIO[str]:
|
540
|
+
...
|
541
|
+
|
542
|
+
@overload
|
543
|
+
async def open(
|
544
|
+
self,
|
545
|
+
path: str,
|
546
|
+
mode: "_typeshed.OpenBinaryMode",
|
547
|
+
) -> _FileIO[bytes]:
|
548
|
+
...
|
549
|
+
|
550
|
+
async def open(
|
551
|
+
self,
|
552
|
+
path: str,
|
553
|
+
mode: Union["_typeshed.OpenTextMode", "_typeshed.OpenBinaryMode"] = "r",
|
554
|
+
):
|
555
|
+
"""Open a file in the Sandbox and return
|
556
|
+
a [`FileIO`](/docs/reference/modal.FileIO#modalfile_io) handle.
|
557
|
+
|
558
|
+
**Usage**
|
559
|
+
|
560
|
+
```python notest
|
561
|
+
sb = modal.Sandbox.create(app=sb_app)
|
562
|
+
f = sb.open("/test.txt", "w")
|
563
|
+
f.write("hello")
|
564
|
+
f.close()
|
565
|
+
```
|
566
|
+
"""
|
567
|
+
task_id = await self._get_task_id()
|
568
|
+
return await _FileIO.create(path, mode, self._client, task_id)
|
569
|
+
|
530
570
|
@property
|
531
571
|
def stdout(self) -> _StreamReader[str]:
|
532
572
|
"""
|
modal/sandbox.pyi
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import _typeshed
|
1
2
|
import collections.abc
|
2
3
|
import google.protobuf.message
|
3
4
|
import modal._tunnel
|
@@ -5,6 +6,7 @@ import modal.app
|
|
5
6
|
import modal.client
|
6
7
|
import modal.cloud_bucket_mount
|
7
8
|
import modal.container_process
|
9
|
+
import modal.file_io
|
8
10
|
import modal.gpu
|
9
11
|
import modal.image
|
10
12
|
import modal.io_streams
|
@@ -122,6 +124,10 @@ class _Sandbox(modal.object._Object):
|
|
122
124
|
bufsize: typing.Literal[-1, 1] = -1,
|
123
125
|
_pty_info: typing.Optional[modal_proto.api_pb2.PTYInfo] = None,
|
124
126
|
) -> modal.container_process._ContainerProcess[bytes]: ...
|
127
|
+
@typing.overload
|
128
|
+
async def open(self, path: str, mode: _typeshed.OpenTextMode) -> modal.file_io._FileIO[str]: ...
|
129
|
+
@typing.overload
|
130
|
+
async def open(self, path: str, mode: _typeshed.OpenBinaryMode) -> modal.file_io._FileIO[bytes]: ...
|
125
131
|
@property
|
126
132
|
def stdout(self) -> modal.io_streams._StreamReader[str]: ...
|
127
133
|
@property
|
@@ -349,6 +355,18 @@ class Sandbox(modal.object.Object):
|
|
349
355
|
|
350
356
|
exec: __exec_spec
|
351
357
|
|
358
|
+
class __open_spec(typing_extensions.Protocol):
|
359
|
+
@typing.overload
|
360
|
+
def __call__(self, path: str, mode: _typeshed.OpenTextMode) -> modal.file_io.FileIO[str]: ...
|
361
|
+
@typing.overload
|
362
|
+
def __call__(self, path: str, mode: _typeshed.OpenBinaryMode) -> modal.file_io.FileIO[bytes]: ...
|
363
|
+
@typing.overload
|
364
|
+
async def aio(self, path: str, mode: _typeshed.OpenTextMode) -> modal.file_io.FileIO[str]: ...
|
365
|
+
@typing.overload
|
366
|
+
async def aio(self, path: str, mode: _typeshed.OpenBinaryMode) -> modal.file_io.FileIO[bytes]: ...
|
367
|
+
|
368
|
+
open: __open_spec
|
369
|
+
|
352
370
|
@property
|
353
371
|
def stdout(self) -> modal.io_streams.StreamReader[str]: ...
|
354
372
|
@property
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: modal
|
3
|
-
Version: 0.68.
|
3
|
+
Version: 0.68.2
|
4
4
|
Summary: Python client library for Modal
|
5
5
|
Author: Modal Labs
|
6
6
|
Author-email: support@modal.com
|
@@ -21,7 +21,7 @@ Requires-Dist: fastapi
|
|
21
21
|
Requires-Dist: grpclib (==0.4.7)
|
22
22
|
Requires-Dist: protobuf (!=4.24.0,<6.0,>=3.19)
|
23
23
|
Requires-Dist: rich (>=12.0.0)
|
24
|
-
Requires-Dist: synchronicity (~=0.9.
|
24
|
+
Requires-Dist: synchronicity (~=0.9.6)
|
25
25
|
Requires-Dist: toml
|
26
26
|
Requires-Dist: typer (>=0.9)
|
27
27
|
Requires-Dist: types-certifi
|
@@ -19,7 +19,7 @@ modal/app.py,sha256=EJ7FUN6rWnSwLJoYJh8nmKg_t-8hdN8_rt0OrkP7JvQ,46084
|
|
19
19
|
modal/app.pyi,sha256=BE5SlR5tRECuc6-e2lUuOknDdov3zxgZ4N0AsLb5ZVQ,25270
|
20
20
|
modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
|
21
21
|
modal/client.py,sha256=uy1Lw3hipt9ILv19VdmBBGYIW7VHmr4rzXi7pO7Kg7s,15791
|
22
|
-
modal/client.pyi,sha256=
|
22
|
+
modal/client.pyi,sha256=HARg2kCIdOYmjEulbyyZJAN3F01iJPF8IiBZbqlcEFk,7352
|
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=pMirIOmb59YzaIK2rn7Vd756E1QKDDweYT90GYIHiMk,27472
|
@@ -31,10 +31,12 @@ modal/dict.py,sha256=RmJlEwFJOdSfAYcVa50hbbFccV8e7BvC5tc5g1HXF-c,12622
|
|
31
31
|
modal/dict.pyi,sha256=2cYgOqBxYZih4BYgMV0c3rNPuxYR6-cB1GBXzFkHA5c,7265
|
32
32
|
modal/environments.py,sha256=5cgA-zbm6ngKLsRA19zSOgtgo9-BarJK3FJK0BiF2Lo,6505
|
33
33
|
modal/environments.pyi,sha256=XalNpiPkAtHWAvOU2Cotq0ozmtl-Jv0FDsR8h9mr27Q,3521
|
34
|
-
modal/exception.py,sha256=
|
34
|
+
modal/exception.py,sha256=dRK789TD1HaB63kHhu1yZuvS2vP_Vua3iLMBtA6dgqk,7128
|
35
35
|
modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
|
36
|
+
modal/file_io.py,sha256=q8s872qf6Ntdw7dPogDlpYbixxGkwCA0BlQn2UUoVhY,14637
|
37
|
+
modal/file_io.pyi,sha256=pfkmJiaBpMCZReE6-KCjYOzB1dVtyYDYokJoYX8ARK4,6932
|
36
38
|
modal/functions.py,sha256=Z2g1CuDTz_491ReOJKs66qGlodPDpKAiKHVwARL2zYA,66792
|
37
|
-
modal/functions.pyi,sha256=
|
39
|
+
modal/functions.pyi,sha256=IyuM9TV79JfrtfTaJ4yq3EcWp3yHuxLavpxTOwSWEDw,24988
|
38
40
|
modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
|
39
41
|
modal/image.py,sha256=cQ6WP1xHXZT_nY8z3aEFiGwKzrTV0yxi3Ab8JzF91eo,79653
|
40
42
|
modal/image.pyi,sha256=PIKH6JBA4L5TfdJrQu3pm2ykyIITmiP920TpP8cdyQA,24585
|
@@ -60,8 +62,8 @@ modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
|
|
60
62
|
modal/runner.py,sha256=Q02VdfLCO7YKpnOSqqh58XL3hR2XHaDeiJVYW3MKz_8,24580
|
61
63
|
modal/runner.pyi,sha256=BvMS1ZVzWSn8B8q0KnIZOJKPkN5L-i5b-USbV6SWWHQ,5177
|
62
64
|
modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
|
63
|
-
modal/sandbox.py,sha256=
|
64
|
-
modal/sandbox.pyi,sha256=
|
65
|
+
modal/sandbox.py,sha256=abmprnPtA-WVsHKu4J1deoltpAUvXtIHQHX-ZpYjYPE,27293
|
66
|
+
modal/sandbox.pyi,sha256=QPNuiTLNoKwYf8JK_fmfUBXpdGYlukyaksFV1DpCd2g,18987
|
65
67
|
modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
|
66
68
|
modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
|
67
69
|
modal/secret.py,sha256=Y1WgybQIkfkxdzH9CQ1h-Wd1DJJpzipigMhyyvSxTww,10007
|
@@ -131,7 +133,7 @@ modal/requirements/README.md,sha256=9tK76KP0Uph7O0M5oUgsSwEZDj5y-dcUPsnpR0Sc-Ik,
|
|
131
133
|
modal/requirements/base-images.json,sha256=kLNo5Sqmnhp9H6Hr9IcaGJFrRaRg1yfuepUWkm-y8iQ,571
|
132
134
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
133
135
|
modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
|
134
|
-
modal_docs/gen_reference_docs.py,sha256=
|
136
|
+
modal_docs/gen_reference_docs.py,sha256=aDcUSSDtAAZ4eeFWyroeIg2TOzyRoYcic-d9Zh9TdLY,6656
|
135
137
|
modal_docs/mdmd/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
136
138
|
modal_docs/mdmd/mdmd.py,sha256=F6EXKkjwrTbOiG6I7wKtNGVVmmeWLAJ5pnE7DUkDpvM,6231
|
137
139
|
modal_docs/mdmd/signatures.py,sha256=XJaZrK7Mdepk5fdX51A8uENiLFNil85Ud0d4MH8H5f0,3218
|
@@ -159,10 +161,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
159
161
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
160
162
|
modal_version/__init__.py,sha256=RT6zPoOdFO99u5Wcxxaoir4ZCuPTbQ22cvzFAXl3vUY,470
|
161
163
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
162
|
-
modal_version/_version_generated.py,sha256=
|
163
|
-
modal-0.68.
|
164
|
-
modal-0.68.
|
165
|
-
modal-0.68.
|
166
|
-
modal-0.68.
|
167
|
-
modal-0.68.
|
168
|
-
modal-0.68.
|
164
|
+
modal_version/_version_generated.py,sha256=6Pvpcc5MvVX_YgJS7zOEdJe-DbGK8uv3kqiGt-OVVXE,148
|
165
|
+
modal-0.68.2.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
166
|
+
modal-0.68.2.dist-info/METADATA,sha256=5nS06eizxvISwpNNTho3610CO0mCCjpisIfo5STyfYY,2328
|
167
|
+
modal-0.68.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
168
|
+
modal-0.68.2.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
169
|
+
modal-0.68.2.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
170
|
+
modal-0.68.2.dist-info/RECORD,,
|
modal_docs/gen_reference_docs.py
CHANGED
@@ -86,6 +86,7 @@ def run(output_dir: str = None):
|
|
86
86
|
("modal.Sandbox", "modal.sandbox"),
|
87
87
|
("modal.ContainerProcess", "modal.container_process"),
|
88
88
|
("modal.io_streams", "modal.io_streams"),
|
89
|
+
("modal.FileIO", "modal.file_io"),
|
89
90
|
]
|
90
91
|
# These aren't defined in `modal`, but should still be documented as top-level entries.
|
91
92
|
forced_members = {"web_endpoint", "asgi_app", "method", "wsgi_app", "forward"}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|