modal 0.71.0__py3-none-any.whl → 0.71.1__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 +86 -0
- modal/file_io.pyi +59 -0
- modal/io_streams.py +15 -27
- modal/io_streams_helper.py +53 -0
- modal/sandbox.py +13 -2
- modal/sandbox.pyi +25 -0
- {modal-0.71.0.dist-info → modal-0.71.1.dist-info}/METADATA +1 -1
- {modal-0.71.0.dist-info → modal-0.71.1.dist-info}/RECORD +14 -13
- modal_version/_version_generated.py +1 -1
- {modal-0.71.0.dist-info → modal-0.71.1.dist-info}/LICENSE +0 -0
- {modal-0.71.0.dist-info → modal-0.71.1.dist-info}/WHEEL +0 -0
- {modal-0.71.0.dist-info → modal-0.71.1.dist-info}/entry_points.txt +0 -0
- {modal-0.71.0.dist-info → modal-0.71.1.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.1"
|
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.1"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/file_io.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2024
|
2
2
|
import asyncio
|
3
|
+
import enum
|
3
4
|
import io
|
5
|
+
from dataclasses import dataclass
|
4
6
|
from typing import TYPE_CHECKING, AsyncIterator, Generic, Optional, Sequence, TypeVar, Union, cast
|
5
7
|
|
6
8
|
if TYPE_CHECKING:
|
@@ -11,6 +13,7 @@ import json
|
|
11
13
|
from grpclib.exceptions import GRPCError, StreamTerminatedError
|
12
14
|
|
13
15
|
from modal._utils.grpc_utils import retry_transient_errors
|
16
|
+
from modal.io_streams_helper import consume_stream_with_retries
|
14
17
|
from modal_proto import api_pb2
|
15
18
|
|
16
19
|
from ._utils.async_utils import synchronize_api
|
@@ -94,6 +97,20 @@ async def _replace_bytes(file: "_FileIO", data: bytes, start: Optional[int] = No
|
|
94
97
|
await file._wait(resp.exec_id)
|
95
98
|
|
96
99
|
|
100
|
+
class FileWatchEventType(enum.Enum):
|
101
|
+
Unknown = "Unknown"
|
102
|
+
Access = "Access"
|
103
|
+
Create = "Create"
|
104
|
+
Modify = "Modify"
|
105
|
+
Remove = "Remove"
|
106
|
+
|
107
|
+
|
108
|
+
@dataclass
|
109
|
+
class FileWatchEvent:
|
110
|
+
paths: list[str]
|
111
|
+
type: FileWatchEventType
|
112
|
+
|
113
|
+
|
97
114
|
# The FileIO class is designed to mimic Python's io.FileIO
|
98
115
|
# See https://github.com/python/cpython/blob/main/Lib/_pyio.py#L1459
|
99
116
|
class _FileIO(Generic[T]):
|
@@ -124,6 +141,7 @@ class _FileIO(Generic[T]):
|
|
124
141
|
_task_id: str = ""
|
125
142
|
_file_descriptor: str = ""
|
126
143
|
_client: Optional[_Client] = None
|
144
|
+
_watch_output_buffer: list[Optional[bytes]] = []
|
127
145
|
|
128
146
|
def _validate_mode(self, mode: str) -> None:
|
129
147
|
if not any(char in mode for char in "rwax"):
|
@@ -167,6 +185,44 @@ class _FileIO(Generic[T]):
|
|
167
185
|
for message in batch.output:
|
168
186
|
yield message
|
169
187
|
|
188
|
+
async def _consume_watch_output(self, exec_id: str) -> None:
|
189
|
+
def item_handler(item: Optional[bytes]):
|
190
|
+
self._watch_output_buffer.append(item)
|
191
|
+
|
192
|
+
def completion_check(item: Optional[bytes]):
|
193
|
+
return item is None
|
194
|
+
|
195
|
+
await consume_stream_with_retries(
|
196
|
+
self._consume_output(exec_id),
|
197
|
+
item_handler,
|
198
|
+
completion_check,
|
199
|
+
)
|
200
|
+
|
201
|
+
async def _parse_watch_output(self, event: bytes) -> Optional[FileWatchEvent]:
|
202
|
+
try:
|
203
|
+
event_json = json.loads(event.decode())
|
204
|
+
return FileWatchEvent(type=FileWatchEventType(event_json["event_type"]), paths=event_json["paths"])
|
205
|
+
except (json.JSONDecodeError, KeyError, ValueError):
|
206
|
+
# skip invalid events
|
207
|
+
return None
|
208
|
+
|
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
|
+
|
170
226
|
async def _wait(self, exec_id: str) -> bytes:
|
171
227
|
# The logic here is similar to how output is read from `exec`
|
172
228
|
output = b""
|
@@ -391,6 +447,36 @@ class _FileIO(Generic[T]):
|
|
391
447
|
)
|
392
448
|
await self._wait(resp.exec_id)
|
393
449
|
|
450
|
+
@classmethod
|
451
|
+
async def watch(
|
452
|
+
cls,
|
453
|
+
path: str,
|
454
|
+
client: _Client,
|
455
|
+
task_id: str,
|
456
|
+
filter: Optional[list[FileWatchEventType]] = None,
|
457
|
+
recursive: bool = False,
|
458
|
+
timeout: Optional[int] = None,
|
459
|
+
) -> AsyncIterator[FileWatchEvent]:
|
460
|
+
self = cls.__new__(cls)
|
461
|
+
self._client = client
|
462
|
+
self._task_id = task_id
|
463
|
+
resp = await self._make_request(
|
464
|
+
api_pb2.ContainerFilesystemExecRequest(
|
465
|
+
file_watch_request=api_pb2.ContainerFileWatchRequest(
|
466
|
+
path=path,
|
467
|
+
recursive=recursive,
|
468
|
+
timeout_secs=timeout,
|
469
|
+
),
|
470
|
+
task_id=self._task_id,
|
471
|
+
)
|
472
|
+
)
|
473
|
+
task = asyncio.create_task(self._consume_watch_output(resp.exec_id))
|
474
|
+
async for event in self._stream_watch_output():
|
475
|
+
if filter and event.type not in filter:
|
476
|
+
continue
|
477
|
+
yield event
|
478
|
+
task.cancel()
|
479
|
+
|
394
480
|
async def _close(self) -> None:
|
395
481
|
# Buffer is flushed by the runner on close
|
396
482
|
resp = await self._make_request(
|
modal/file_io.pyi
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import _typeshed
|
2
|
+
import enum
|
2
3
|
import modal.client
|
3
4
|
import modal_proto.api_pb2
|
4
5
|
import typing
|
@@ -13,14 +14,33 @@ async def _replace_bytes(
|
|
13
14
|
file: _FileIO, data: bytes, start: typing.Optional[int] = None, end: typing.Optional[int] = None
|
14
15
|
) -> None: ...
|
15
16
|
|
17
|
+
class FileWatchEventType(enum.Enum):
|
18
|
+
Unknown = "Unknown"
|
19
|
+
Access = "Access"
|
20
|
+
Create = "Create"
|
21
|
+
Modify = "Modify"
|
22
|
+
Remove = "Remove"
|
23
|
+
|
24
|
+
class FileWatchEvent:
|
25
|
+
paths: list[str]
|
26
|
+
type: FileWatchEventType
|
27
|
+
|
28
|
+
def __init__(self, paths: list[str], type: FileWatchEventType) -> None: ...
|
29
|
+
def __repr__(self): ...
|
30
|
+
def __eq__(self, other): ...
|
31
|
+
|
16
32
|
class _FileIO(typing.Generic[T]):
|
17
33
|
_task_id: str
|
18
34
|
_file_descriptor: str
|
19
35
|
_client: typing.Optional[modal.client._Client]
|
36
|
+
_watch_output_buffer: list[typing.Optional[bytes]]
|
20
37
|
|
21
38
|
def _validate_mode(self, mode: str) -> None: ...
|
22
39
|
def _handle_error(self, error: modal_proto.api_pb2.SystemErrorMessage) -> None: ...
|
23
40
|
def _consume_output(self, exec_id: str) -> typing.AsyncIterator[typing.Optional[bytes]]: ...
|
41
|
+
async def _consume_watch_output(self, exec_id: str) -> None: ...
|
42
|
+
async def _parse_watch_output(self, event: bytes) -> typing.Optional[FileWatchEvent]: ...
|
43
|
+
def _stream_watch_output(self) -> typing.AsyncIterator[FileWatchEvent]: ...
|
24
44
|
async def _wait(self, exec_id: str) -> bytes: ...
|
25
45
|
def _validate_type(self, data: typing.Union[bytes, str]) -> None: ...
|
26
46
|
async def _open_file(self, path: str, mode: str) -> None: ...
|
@@ -49,6 +69,16 @@ class _FileIO(typing.Generic[T]):
|
|
49
69
|
async def mkdir(cls, path: str, client: modal.client._Client, task_id: str, parents: bool = False) -> None: ...
|
50
70
|
@classmethod
|
51
71
|
async def rm(cls, path: str, client: modal.client._Client, task_id: str, recursive: bool = False) -> None: ...
|
72
|
+
@classmethod
|
73
|
+
def watch(
|
74
|
+
cls,
|
75
|
+
path: str,
|
76
|
+
client: modal.client._Client,
|
77
|
+
task_id: str,
|
78
|
+
filter: typing.Optional[list[FileWatchEventType]] = None,
|
79
|
+
recursive: bool = False,
|
80
|
+
timeout: typing.Optional[int] = None,
|
81
|
+
) -> typing.AsyncIterator[FileWatchEvent]: ...
|
52
82
|
async def _close(self) -> None: ...
|
53
83
|
async def close(self) -> None: ...
|
54
84
|
def _check_writable(self) -> None: ...
|
@@ -79,6 +109,7 @@ class FileIO(typing.Generic[T]):
|
|
79
109
|
_task_id: str
|
80
110
|
_file_descriptor: str
|
81
111
|
_client: typing.Optional[modal.client.Client]
|
112
|
+
_watch_output_buffer: list[typing.Optional[bytes]]
|
82
113
|
|
83
114
|
def __init__(self, /, *args, **kwargs): ...
|
84
115
|
def _validate_mode(self, mode: str) -> None: ...
|
@@ -90,6 +121,24 @@ class FileIO(typing.Generic[T]):
|
|
90
121
|
|
91
122
|
_consume_output: ___consume_output_spec
|
92
123
|
|
124
|
+
class ___consume_watch_output_spec(typing_extensions.Protocol):
|
125
|
+
def __call__(self, exec_id: str) -> None: ...
|
126
|
+
async def aio(self, exec_id: str) -> None: ...
|
127
|
+
|
128
|
+
_consume_watch_output: ___consume_watch_output_spec
|
129
|
+
|
130
|
+
class ___parse_watch_output_spec(typing_extensions.Protocol):
|
131
|
+
def __call__(self, event: bytes) -> typing.Optional[FileWatchEvent]: ...
|
132
|
+
async def aio(self, event: bytes) -> typing.Optional[FileWatchEvent]: ...
|
133
|
+
|
134
|
+
_parse_watch_output: ___parse_watch_output_spec
|
135
|
+
|
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
|
+
|
93
142
|
class ___wait_spec(typing_extensions.Protocol):
|
94
143
|
def __call__(self, exec_id: str) -> bytes: ...
|
95
144
|
async def aio(self, exec_id: str) -> bytes: ...
|
@@ -173,6 +222,16 @@ class FileIO(typing.Generic[T]):
|
|
173
222
|
def mkdir(cls, path: str, client: modal.client.Client, task_id: str, parents: bool = False) -> None: ...
|
174
223
|
@classmethod
|
175
224
|
def rm(cls, path: str, client: modal.client.Client, task_id: str, recursive: bool = False) -> None: ...
|
225
|
+
@classmethod
|
226
|
+
def watch(
|
227
|
+
cls,
|
228
|
+
path: str,
|
229
|
+
client: modal.client.Client,
|
230
|
+
task_id: str,
|
231
|
+
filter: typing.Optional[list[FileWatchEventType]] = None,
|
232
|
+
recursive: bool = False,
|
233
|
+
timeout: typing.Optional[int] = None,
|
234
|
+
) -> typing.Iterator[FileWatchEvent]: ...
|
176
235
|
|
177
236
|
class ___close_spec(typing_extensions.Protocol):
|
178
237
|
def __call__(self) -> None: ...
|
modal/io_streams.py
CHANGED
@@ -14,7 +14,8 @@ from typing import (
|
|
14
14
|
from grpclib import Status
|
15
15
|
from grpclib.exceptions import GRPCError, StreamTerminatedError
|
16
16
|
|
17
|
-
from modal.exception import
|
17
|
+
from modal.exception import InvalidError
|
18
|
+
from modal.io_streams_helper import consume_stream_with_retries
|
18
19
|
from modal_proto import api_pb2
|
19
20
|
|
20
21
|
from ._utils.async_utils import synchronize_api
|
@@ -176,34 +177,21 @@ class _StreamReader(Generic[T]):
|
|
176
177
|
if self._stream_type == StreamType.DEVNULL:
|
177
178
|
return
|
178
179
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
180
|
+
def item_handler(item: Optional[bytes]):
|
181
|
+
if self._stream_type == StreamType.STDOUT and item is not None:
|
182
|
+
print(item.decode("utf-8"), end="")
|
183
|
+
elif self._stream_type == StreamType.PIPE:
|
184
|
+
self._container_process_buffer.append(item)
|
184
185
|
|
185
|
-
|
186
|
-
|
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
|
186
|
+
def completion_check(item: Optional[bytes]):
|
187
|
+
return item is None
|
193
188
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
189
|
+
iterator = _container_process_logs_iterator(self._object_id, self._file_descriptor, self._client)
|
190
|
+
await consume_stream_with_retries(
|
191
|
+
iterator,
|
192
|
+
item_handler,
|
193
|
+
completion_check,
|
194
|
+
)
|
207
195
|
|
208
196
|
async def _stream_container_process(self) -> AsyncGenerator[tuple[Optional[bytes], str], None]:
|
209
197
|
"""Streams the container process buffer to the reader."""
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright Modal Labs 2024
|
2
|
+
import asyncio
|
3
|
+
from typing import AsyncIterator, Callable, TypeVar
|
4
|
+
|
5
|
+
from grpclib.exceptions import GRPCError, StreamTerminatedError
|
6
|
+
|
7
|
+
from modal.exception import ClientClosed
|
8
|
+
|
9
|
+
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES
|
10
|
+
|
11
|
+
T = TypeVar("T")
|
12
|
+
|
13
|
+
|
14
|
+
async def consume_stream_with_retries(
|
15
|
+
stream: AsyncIterator[T],
|
16
|
+
item_handler: Callable[[T], None],
|
17
|
+
completion_check: Callable[[T], bool],
|
18
|
+
max_retries: int = 10,
|
19
|
+
retry_delay: float = 1.0,
|
20
|
+
) -> None:
|
21
|
+
"""mdmd:hidden
|
22
|
+
Helper function to consume a stream with retry logic for transient errors.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
stream_generator: Function that returns an AsyncIterator to consume
|
26
|
+
item_handler: Callback function to handle each item from the stream
|
27
|
+
completion_check: Callback function to check if the stream is complete
|
28
|
+
max_retries: Maximum number of retry attempts
|
29
|
+
retry_delay: Delay in seconds between retries
|
30
|
+
"""
|
31
|
+
completed = False
|
32
|
+
retries_remaining = max_retries
|
33
|
+
|
34
|
+
while not completed:
|
35
|
+
try:
|
36
|
+
async for item in stream:
|
37
|
+
item_handler(item)
|
38
|
+
if completion_check(item):
|
39
|
+
completed = True
|
40
|
+
break
|
41
|
+
|
42
|
+
except (GRPCError, StreamTerminatedError, ClientClosed) as exc:
|
43
|
+
if retries_remaining > 0:
|
44
|
+
retries_remaining -= 1
|
45
|
+
if isinstance(exc, GRPCError):
|
46
|
+
if exc.status in RETRYABLE_GRPC_STATUS_CODES:
|
47
|
+
await asyncio.sleep(retry_delay)
|
48
|
+
continue
|
49
|
+
elif isinstance(exc, StreamTerminatedError):
|
50
|
+
continue
|
51
|
+
elif isinstance(exc, ClientClosed):
|
52
|
+
break
|
53
|
+
raise
|
modal/sandbox.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
import asyncio
|
3
3
|
import os
|
4
4
|
from collections.abc import AsyncGenerator, Sequence
|
5
|
-
from typing import TYPE_CHECKING, Literal, Optional, Union, overload
|
5
|
+
from typing import TYPE_CHECKING, AsyncIterator, Literal, Optional, Union, overload
|
6
6
|
|
7
7
|
if TYPE_CHECKING:
|
8
8
|
import _typeshed
|
@@ -26,7 +26,7 @@ from .client import _Client
|
|
26
26
|
from .config import config
|
27
27
|
from .container_process import _ContainerProcess
|
28
28
|
from .exception import ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError
|
29
|
-
from .file_io import _FileIO
|
29
|
+
from .file_io import FileWatchEvent, FileWatchEventType, _FileIO
|
30
30
|
from .gpu import GPU_T
|
31
31
|
from .image import _Image
|
32
32
|
from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
|
@@ -579,6 +579,17 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
579
579
|
task_id = await self._get_task_id()
|
580
580
|
return await _FileIO.rm(path, self._client, task_id, recursive)
|
581
581
|
|
582
|
+
async def watch(
|
583
|
+
self,
|
584
|
+
path: str,
|
585
|
+
filter: Optional[list[FileWatchEventType]] = None,
|
586
|
+
recursive: Optional[bool] = None,
|
587
|
+
timeout: Optional[int] = None,
|
588
|
+
) -> AsyncIterator[FileWatchEvent]:
|
589
|
+
task_id = await self._get_task_id()
|
590
|
+
async for event in _FileIO.watch(path, self._client, task_id, filter, recursive, timeout):
|
591
|
+
yield event
|
592
|
+
|
582
593
|
@property
|
583
594
|
def stdout(self) -> _StreamReader[str]:
|
584
595
|
"""
|
modal/sandbox.pyi
CHANGED
@@ -131,6 +131,13 @@ class _Sandbox(modal.object._Object):
|
|
131
131
|
async def ls(self, path: str) -> list[str]: ...
|
132
132
|
async def mkdir(self, path: str, parents: bool = False) -> None: ...
|
133
133
|
async def rm(self, path: str, recursive: bool = False) -> None: ...
|
134
|
+
def watch(
|
135
|
+
self,
|
136
|
+
path: str,
|
137
|
+
filter: typing.Optional[list[modal.file_io.FileWatchEventType]] = None,
|
138
|
+
recursive: typing.Optional[bool] = None,
|
139
|
+
timeout: typing.Optional[int] = None,
|
140
|
+
) -> typing.AsyncIterator[modal.file_io.FileWatchEvent]: ...
|
134
141
|
@property
|
135
142
|
def stdout(self) -> modal.io_streams._StreamReader[str]: ...
|
136
143
|
@property
|
@@ -388,6 +395,24 @@ class Sandbox(modal.object.Object):
|
|
388
395
|
|
389
396
|
rm: __rm_spec
|
390
397
|
|
398
|
+
class __watch_spec(typing_extensions.Protocol):
|
399
|
+
def __call__(
|
400
|
+
self,
|
401
|
+
path: str,
|
402
|
+
filter: typing.Optional[list[modal.file_io.FileWatchEventType]] = None,
|
403
|
+
recursive: typing.Optional[bool] = None,
|
404
|
+
timeout: typing.Optional[int] = None,
|
405
|
+
) -> typing.Iterator[modal.file_io.FileWatchEvent]: ...
|
406
|
+
def aio(
|
407
|
+
self,
|
408
|
+
path: str,
|
409
|
+
filter: typing.Optional[list[modal.file_io.FileWatchEventType]] = None,
|
410
|
+
recursive: typing.Optional[bool] = None,
|
411
|
+
timeout: typing.Optional[int] = None,
|
412
|
+
) -> typing.AsyncIterator[modal.file_io.FileWatchEvent]: ...
|
413
|
+
|
414
|
+
watch: __watch_spec
|
415
|
+
|
391
416
|
@property
|
392
417
|
def stdout(self) -> modal.io_streams.StreamReader[str]: ...
|
393
418
|
@property
|
@@ -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=uq7W4l6HMPzZo73ALVZrAkQ436KKcQIR861Uoh144uI,7278
|
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,16 +33,17 @@ modal/environments.py,sha256=wbv9ttFCbzATGfwcmvYiG608PfHovx0AQmawsg-jmic,6660
|
|
33
33
|
modal/environments.pyi,sha256=rF7oaaELoSNuoD6qImGnIbuGPtgWwR5SlcExyYJ61hQ,3515
|
34
34
|
modal/exception.py,sha256=GEV6xMnVnkle0gsFZVLB4B7cUMyw8HzVDvAvPr34ZV4,5185
|
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=ZR8VBCDsDt5uB9TNN9XbEh7sniJzM_5YL47m8WP0m5c,19617
|
37
|
+
modal/file_io.pyi,sha256=79Fg75BjmMEeCX0Lx-Z8C4XSNPCotwNdK6ZLIDFm2f4,9770
|
38
38
|
modal/file_pattern_matcher.py,sha256=LaI7Paxg0xR9D-D7Tgc60xR0w1KZee22LjGbFie1Vms,5571
|
39
39
|
modal/functions.py,sha256=aXXXr3rk7BCeh5OWMvxGksGm8FQoYCyrBDGV74FPoPE,67827
|
40
40
|
modal/functions.pyi,sha256=oMmcExtQxHwPej06jQ3uBe1tUlSR3VbAx7u3Vm-Ohhg,25317
|
41
41
|
modal/gpu.py,sha256=MTxj6ql8EpgfBg8YmZ5a1cLznyuZFssX1qXbEX4LKVM,7503
|
42
42
|
modal/image.py,sha256=Krvcsclomp9YsqSNwFj2FoAyg10OvU47RDnsNCwjGbQ,84550
|
43
43
|
modal/image.pyi,sha256=1fgGvsL5Rb0Sa-_2OCgIyJ_QgHcL0_9MWD_oY7cyFFM,24937
|
44
|
-
modal/io_streams.py,sha256=
|
44
|
+
modal/io_streams.py,sha256=Xxc5grJiO94nBA48FFWH3S3g6SPR0xFVgZ_DZ1oFmvI,14428
|
45
45
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
46
|
+
modal/io_streams_helper.py,sha256=B5Ui56ph7LkRpZX0tAF80Q-gOMsvPPLx5bpIPX0kgDc,1772
|
46
47
|
modal/mount.py,sha256=wOr-2vmKImsE3lHBII8hL2gYy5ng46R58QwId4JultQ,29313
|
47
48
|
modal/mount.pyi,sha256=FiNV1wIKFvd0ZMZ0tm1mz6ZSA5Hjsge-kFSA5tPWfcI,10503
|
48
49
|
modal/network_file_system.py,sha256=INj1TfN_Fsmabmlte7anvey1epodjbMmjBW_TIJSST4,14406
|
@@ -63,8 +64,8 @@ modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
|
|
63
64
|
modal/runner.py,sha256=mhqgRdjD5cUDpBQIokiX7OCfVblpGV6aWmZ-WvWJgGg,24114
|
64
65
|
modal/runner.pyi,sha256=YmP4EOCNjjkwSIPi2Gl6hF_ji_ytkxz9dw3iB9KXaOI,5275
|
65
66
|
modal/running_app.py,sha256=v61mapYNV1-O-Uaho5EfJlryMLvIT9We0amUOSvSGx8,1188
|
66
|
-
modal/sandbox.py,sha256=
|
67
|
-
modal/sandbox.pyi,sha256=
|
67
|
+
modal/sandbox.py,sha256=eSxTEjeHQRVARIJEsnQLngN1lkpROXWZT0FCWBernyI,28573
|
68
|
+
modal/sandbox.pyi,sha256=lceWDeXqzdeRc1iIuM5YmpoZlBJcVBpQO1Jc3AT1AQI,20809
|
68
69
|
modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
|
69
70
|
modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
|
70
71
|
modal/secret.py,sha256=lebVTZi4fC9PXQpLVmsvgQLzy-2Kzxv1WBD0Jr2wsxQ,10117
|
@@ -165,10 +166,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
165
166
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
166
167
|
modal_version/__init__.py,sha256=BEBWj9tcbFUwzEjUrqly601rauw5cYsHdcmJHs3iu0s,470
|
167
168
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
168
|
-
modal_version/_version_generated.py,sha256
|
169
|
-
modal-0.71.
|
170
|
-
modal-0.71.
|
171
|
-
modal-0.71.
|
172
|
-
modal-0.71.
|
173
|
-
modal-0.71.
|
174
|
-
modal-0.71.
|
169
|
+
modal_version/_version_generated.py,sha256=-6j4dF5udT1J3FkOZ3fPAMw-HT0ESmNAwXdT64NOp4A,148
|
170
|
+
modal-0.71.1.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
171
|
+
modal-0.71.1.dist-info/METADATA,sha256=k-Q6SXYTk8SGVYiU8blPMqGZsGxgQbjOHj6kuKSef0M,2328
|
172
|
+
modal-0.71.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
173
|
+
modal-0.71.1.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
174
|
+
modal-0.71.1.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
175
|
+
modal-0.71.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|