modal 0.67.33__py3-none-any.whl → 0.67.42__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/_runtime/container_io_manager.py +2 -2
- modal/cli/_traceback.py +8 -3
- modal/cli/app.py +1 -1
- modal/cli/network_file_system.py +23 -2
- modal/client.py +1 -1
- modal/client.pyi +2 -2
- modal/functions.py +2 -2
- modal/functions.pyi +10 -10
- modal/io_streams.py +1 -1
- modal/network_file_system.py +6 -0
- modal/network_file_system.pyi +20 -0
- modal/queue.py +33 -27
- modal/sandbox.py +37 -12
- modal/sandbox.pyi +10 -3
- {modal-0.67.33.dist-info → modal-0.67.42.dist-info}/METADATA +1 -1
- {modal-0.67.33.dist-info → modal-0.67.42.dist-info}/RECORD +28 -28
- modal_proto/api.proto +26 -1
- modal_proto/api_grpc.py +16 -0
- modal_proto/api_pb2.py +743 -710
- modal_proto/api_pb2.pyi +88 -4
- modal_proto/api_pb2_grpc.py +34 -1
- modal_proto/api_pb2_grpc.pyi +13 -3
- modal_proto/modal_api_grpc.py +1 -0
- modal_version/_version_generated.py +1 -1
- {modal-0.67.33.dist-info → modal-0.67.42.dist-info}/LICENSE +0 -0
- {modal-0.67.33.dist-info → modal-0.67.42.dist-info}/WHEEL +0 -0
- {modal-0.67.33.dist-info → modal-0.67.42.dist-info}/entry_points.txt +0 -0
- {modal-0.67.33.dist-info → modal-0.67.42.dist-info}/top_level.txt +0 -0
@@ -908,7 +908,7 @@ class _ContainerIOManager:
|
|
908
908
|
if self.checkpoint_id:
|
909
909
|
logger.debug(f"Checkpoint ID: {self.checkpoint_id} (Memory Snapshot ID)")
|
910
910
|
else:
|
911
|
-
|
911
|
+
raise ValueError("No checkpoint ID provided for memory snapshot")
|
912
912
|
|
913
913
|
# Pause heartbeats since they keep the client connection open which causes the snapshotter to crash
|
914
914
|
async with self.heartbeat_condition:
|
@@ -918,7 +918,7 @@ class _ContainerIOManager:
|
|
918
918
|
self.heartbeat_condition.notify_all()
|
919
919
|
|
920
920
|
await self._client.stub.ContainerCheckpoint(
|
921
|
-
api_pb2.ContainerCheckpointRequest(checkpoint_id=self.checkpoint_id
|
921
|
+
api_pb2.ContainerCheckpointRequest(checkpoint_id=self.checkpoint_id)
|
922
922
|
)
|
923
923
|
|
924
924
|
await self._client._close(prep_for_restore=True)
|
modal/cli/_traceback.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Copyright Modal Labs 2024
|
2
2
|
"""Helper functions related to displaying tracebacks in the CLI."""
|
3
3
|
import functools
|
4
|
+
import re
|
4
5
|
import warnings
|
5
6
|
from typing import Optional
|
6
7
|
|
@@ -166,8 +167,12 @@ def highlight_modal_deprecation_warnings() -> None:
|
|
166
167
|
def showwarning(warning, category, filename, lineno, file=None, line=None):
|
167
168
|
if issubclass(category, (DeprecationError, PendingDeprecationError)):
|
168
169
|
content = str(warning)
|
169
|
-
|
170
|
-
|
170
|
+
if re.match(r"^\d{4}-\d{2}-\d{2}", content):
|
171
|
+
date = content[:10]
|
172
|
+
message = content[11:].strip()
|
173
|
+
else:
|
174
|
+
date = ""
|
175
|
+
message = content
|
171
176
|
try:
|
172
177
|
with open(filename, encoding="utf-8", errors="replace") as code_file:
|
173
178
|
source = code_file.readlines()[lineno - 1].strip()
|
@@ -178,7 +183,7 @@ def highlight_modal_deprecation_warnings() -> None:
|
|
178
183
|
panel = Panel(
|
179
184
|
message,
|
180
185
|
style="yellow",
|
181
|
-
title=f"Modal Deprecation Warning ({date})",
|
186
|
+
title=f"Modal Deprecation Warning ({date})" if date else "Modal Deprecation Warning",
|
182
187
|
title_align="left",
|
183
188
|
)
|
184
189
|
Console().print(panel)
|
modal/cli/app.py
CHANGED
modal/cli/network_file_system.py
CHANGED
@@ -10,7 +10,7 @@ from grpclib import GRPCError, Status
|
|
10
10
|
from rich.console import Console
|
11
11
|
from rich.syntax import Syntax
|
12
12
|
from rich.table import Table
|
13
|
-
from typer import Typer
|
13
|
+
from typer import Argument, Typer
|
14
14
|
|
15
15
|
import modal
|
16
16
|
from modal._location import display_location
|
@@ -18,7 +18,7 @@ from modal._output import OutputManager, ProgressHandler
|
|
18
18
|
from modal._utils.async_utils import synchronizer
|
19
19
|
from modal._utils.grpc_utils import retry_transient_errors
|
20
20
|
from modal.cli._download import _volume_download
|
21
|
-
from modal.cli.utils import ENV_OPTION, display_table, timestamp_to_local
|
21
|
+
from modal.cli.utils import ENV_OPTION, YES_OPTION, display_table, timestamp_to_local
|
22
22
|
from modal.client import _Client
|
23
23
|
from modal.environments import ensure_env
|
24
24
|
from modal.network_file_system import _NetworkFileSystem
|
@@ -217,3 +217,24 @@ async def rm(
|
|
217
217
|
if exc.status in (Status.NOT_FOUND, Status.INVALID_ARGUMENT):
|
218
218
|
raise UsageError(exc.message)
|
219
219
|
raise
|
220
|
+
|
221
|
+
|
222
|
+
@nfs_cli.command(
|
223
|
+
name="delete",
|
224
|
+
help="Delete a named, persistent modal.NetworkFileSystem.",
|
225
|
+
rich_help_panel="Management",
|
226
|
+
)
|
227
|
+
@synchronizer.create_blocking
|
228
|
+
async def delete(
|
229
|
+
nfs_name: str = Argument(help="Name of the modal.NetworkFileSystem to be deleted. Case sensitive"),
|
230
|
+
yes: bool = YES_OPTION,
|
231
|
+
env: Optional[str] = ENV_OPTION,
|
232
|
+
):
|
233
|
+
if not yes:
|
234
|
+
typer.confirm(
|
235
|
+
f"Are you sure you want to irrevocably delete the modal.NetworkFileSystem '{nfs_name}'?",
|
236
|
+
default=False,
|
237
|
+
abort=True,
|
238
|
+
)
|
239
|
+
|
240
|
+
await _NetworkFileSystem.delete(label=nfs_name, environment_name=env)
|
modal/client.py
CHANGED
@@ -147,7 +147,7 @@ class _Client:
|
|
147
147
|
)
|
148
148
|
if resp.warning:
|
149
149
|
ALARM_EMOJI = chr(0x1F6A8)
|
150
|
-
warnings.
|
150
|
+
warnings.warn_explicit(f"{ALARM_EMOJI} {resp.warning} {ALARM_EMOJI}", DeprecationError, "<unknown>", 0)
|
151
151
|
except GRPCError as exc:
|
152
152
|
if exc.status == Status.FAILED_PRECONDITION:
|
153
153
|
raise VersionError(
|
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.67.
|
29
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.42"
|
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.67.
|
84
|
+
self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.67.42"
|
85
85
|
): ...
|
86
86
|
def is_closed(self) -> bool: ...
|
87
87
|
@property
|
modal/functions.py
CHANGED
@@ -347,7 +347,7 @@ class _FunctionSpec:
|
|
347
347
|
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
|
348
348
|
gpus: Union[GPU_T, list[GPU_T]] # TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
|
349
349
|
cloud: Optional[str]
|
350
|
-
cpu: Optional[float]
|
350
|
+
cpu: Optional[Union[float, tuple[float, float]]]
|
351
351
|
memory: Optional[Union[int, tuple[int, int]]]
|
352
352
|
ephemeral_disk: Optional[int]
|
353
353
|
scheduler_placement: Optional[SchedulerPlacement]
|
@@ -448,7 +448,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
448
448
|
batch_max_size: Optional[int] = None,
|
449
449
|
batch_wait_ms: Optional[int] = None,
|
450
450
|
container_idle_timeout: Optional[int] = None,
|
451
|
-
cpu: Optional[float] = None,
|
451
|
+
cpu: Optional[Union[float, tuple[float, float]]] = None,
|
452
452
|
keep_warm: Optional[int] = None, # keep_warm=True is equivalent to keep_warm=1
|
453
453
|
cloud: Optional[str] = None,
|
454
454
|
scheduler_placement: Optional[SchedulerPlacement] = None,
|
modal/functions.pyi
CHANGED
@@ -96,7 +96,7 @@ class _FunctionSpec:
|
|
96
96
|
]
|
97
97
|
gpus: typing.Union[None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]]
|
98
98
|
cloud: typing.Optional[str]
|
99
|
-
cpu: typing.
|
99
|
+
cpu: typing.Union[float, tuple[float, float], None]
|
100
100
|
memory: typing.Union[int, tuple[int, int], None]
|
101
101
|
ephemeral_disk: typing.Optional[int]
|
102
102
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement]
|
@@ -117,7 +117,7 @@ class _FunctionSpec:
|
|
117
117
|
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
118
118
|
],
|
119
119
|
cloud: typing.Optional[str],
|
120
|
-
cpu: typing.
|
120
|
+
cpu: typing.Union[float, tuple[float, float], None],
|
121
121
|
memory: typing.Union[int, tuple[int, int], None],
|
122
122
|
ephemeral_disk: typing.Optional[int],
|
123
123
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement],
|
@@ -180,7 +180,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
180
180
|
batch_max_size: typing.Optional[int] = None,
|
181
181
|
batch_wait_ms: typing.Optional[int] = None,
|
182
182
|
container_idle_timeout: typing.Optional[int] = None,
|
183
|
-
cpu: typing.
|
183
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
184
184
|
keep_warm: typing.Optional[int] = None,
|
185
185
|
cloud: typing.Optional[str] = None,
|
186
186
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
@@ -348,7 +348,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
348
348
|
batch_max_size: typing.Optional[int] = None,
|
349
349
|
batch_wait_ms: typing.Optional[int] = None,
|
350
350
|
container_idle_timeout: typing.Optional[int] = None,
|
351
|
-
cpu: typing.
|
351
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
352
352
|
keep_warm: typing.Optional[int] = None,
|
353
353
|
cloud: typing.Optional[str] = None,
|
354
354
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement] = None,
|
@@ -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[ReturnType_INNER, P_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[ReturnType, P]
|
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[ReturnType_INNER, P_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[ReturnType, P]
|
479
479
|
|
480
|
-
class __spawn_spec(typing_extensions.Protocol[
|
480
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_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[ReturnType, P]
|
485
485
|
|
486
486
|
def get_raw_f(self) -> typing.Callable[..., typing.Any]: ...
|
487
487
|
|
modal/io_streams.py
CHANGED
@@ -184,7 +184,7 @@ class _StreamReader(Generic[T]):
|
|
184
184
|
|
185
185
|
async for message in iterator:
|
186
186
|
if self._stream_type == StreamType.STDOUT and message:
|
187
|
-
print(message, end="")
|
187
|
+
print(message.decode("utf-8"), end="")
|
188
188
|
elif self._stream_type == StreamType.PIPE:
|
189
189
|
self._container_process_buffer.append(message)
|
190
190
|
if message is None:
|
modal/network_file_system.py
CHANGED
@@ -221,6 +221,12 @@ class _NetworkFileSystem(_Object, type_prefix="sv"):
|
|
221
221
|
resp = await retry_transient_errors(client.stub.SharedVolumeGetOrCreate, request)
|
222
222
|
return resp.shared_volume_id
|
223
223
|
|
224
|
+
@staticmethod
|
225
|
+
async def delete(label: str, client: Optional[_Client] = None, environment_name: Optional[str] = None):
|
226
|
+
obj = await _NetworkFileSystem.lookup(label, client=client, environment_name=environment_name)
|
227
|
+
req = api_pb2.SharedVolumeDeleteRequest(shared_volume_id=obj.object_id)
|
228
|
+
await retry_transient_errors(obj._client.stub.SharedVolumeDelete, req)
|
229
|
+
|
224
230
|
@live_method
|
225
231
|
async def write_file(self, remote_path: str, fp: BinaryIO, progress_cb: Optional[Callable[..., Any]] = None) -> int:
|
226
232
|
"""Write from a file object to a path on the network file system, atomically.
|
modal/network_file_system.pyi
CHANGED
@@ -41,6 +41,10 @@ class _NetworkFileSystem(modal.object._Object):
|
|
41
41
|
client: typing.Optional[modal.client._Client] = None,
|
42
42
|
environment_name: typing.Optional[str] = None,
|
43
43
|
) -> str: ...
|
44
|
+
@staticmethod
|
45
|
+
async def delete(
|
46
|
+
label: str, client: typing.Optional[modal.client._Client] = None, environment_name: typing.Optional[str] = None
|
47
|
+
): ...
|
44
48
|
async def write_file(
|
45
49
|
self,
|
46
50
|
remote_path: str,
|
@@ -118,6 +122,22 @@ class NetworkFileSystem(modal.object.Object):
|
|
118
122
|
|
119
123
|
create_deployed: __create_deployed_spec
|
120
124
|
|
125
|
+
class __delete_spec(typing_extensions.Protocol):
|
126
|
+
def __call__(
|
127
|
+
self,
|
128
|
+
label: str,
|
129
|
+
client: typing.Optional[modal.client.Client] = None,
|
130
|
+
environment_name: typing.Optional[str] = None,
|
131
|
+
): ...
|
132
|
+
async def aio(
|
133
|
+
self,
|
134
|
+
label: str,
|
135
|
+
client: typing.Optional[modal.client.Client] = None,
|
136
|
+
environment_name: typing.Optional[str] = None,
|
137
|
+
): ...
|
138
|
+
|
139
|
+
delete: __delete_spec
|
140
|
+
|
121
141
|
class __write_file_spec(typing_extensions.Protocol):
|
122
142
|
def __call__(
|
123
143
|
self,
|
modal/queue.py
CHANGED
@@ -27,38 +27,12 @@ class _Queue(_Object, type_prefix="qu"):
|
|
27
27
|
|
28
28
|
By default, the `Queue` object acts as a single FIFO queue which supports puts and gets (blocking and non-blocking).
|
29
29
|
|
30
|
-
**Queue partitions (beta)**
|
31
|
-
|
32
|
-
Specifying partition keys gives access to other independent FIFO partitions within the same `Queue` object.
|
33
|
-
Across any two partitions, puts and gets are completely independent.
|
34
|
-
For example, a put in one partition does not affect a get in any other partition.
|
35
|
-
|
36
|
-
When no partition key is specified (by default), puts and gets will operate on a default partition.
|
37
|
-
This default partition is also isolated from all other partitions.
|
38
|
-
Please see the Usage section below for an example using partitions.
|
39
|
-
|
40
|
-
**Lifetime of a queue and its partitions**
|
41
|
-
|
42
|
-
By default, each partition is cleared 24 hours after the last `put` operation.
|
43
|
-
A lower TTL can be specified by the `partition_ttl` argument in the `put` or `put_many` methods.
|
44
|
-
Each partition's expiry is handled independently.
|
45
|
-
|
46
|
-
As such, `Queue`s are best used for communication between active functions and not relied on for persistent storage.
|
47
|
-
|
48
|
-
On app completion or after stopping an app any associated `Queue` objects are cleaned up.
|
49
|
-
All its partitions will be cleared.
|
50
|
-
|
51
|
-
**Limits**
|
52
|
-
|
53
|
-
A single `Queue` can contain up to 100,000 partitions, each with up to 5,000 items. Each item can be up to 256 KiB.
|
54
|
-
|
55
|
-
Partition keys must be non-empty and must not exceed 64 bytes.
|
56
|
-
|
57
30
|
**Usage**
|
58
31
|
|
59
32
|
```python
|
60
33
|
from modal import Queue
|
61
34
|
|
35
|
+
# Create an ephemeral queue which is anonymous and garbage collected
|
62
36
|
with Queue.ephemeral() as my_queue:
|
63
37
|
# Putting values
|
64
38
|
my_queue.put("some value")
|
@@ -82,9 +56,41 @@ class _Queue(_Object, type_prefix="qu"):
|
|
82
56
|
# (beta feature) Iterate through items in place (read immutably)
|
83
57
|
my_queue.put(1)
|
84
58
|
assert [v for v in my_queue.iterate()] == [0, 1]
|
59
|
+
|
60
|
+
# You can also create persistent queues that can be used across apps
|
61
|
+
queue = Queue.from_name("my-persisted-queue", create_if_missing=True)
|
62
|
+
queue.put(42)
|
63
|
+
assert queue.get() == 42
|
85
64
|
```
|
86
65
|
|
87
66
|
For more examples, see the [guide](/docs/guide/dicts-and-queues#modal-queues).
|
67
|
+
|
68
|
+
**Queue partitions (beta)**
|
69
|
+
|
70
|
+
Specifying partition keys gives access to other independent FIFO partitions within the same `Queue` object.
|
71
|
+
Across any two partitions, puts and gets are completely independent.
|
72
|
+
For example, a put in one partition does not affect a get in any other partition.
|
73
|
+
|
74
|
+
When no partition key is specified (by default), puts and gets will operate on a default partition.
|
75
|
+
This default partition is also isolated from all other partitions.
|
76
|
+
Please see the Usage section below for an example using partitions.
|
77
|
+
|
78
|
+
**Lifetime of a queue and its partitions**
|
79
|
+
|
80
|
+
By default, each partition is cleared 24 hours after the last `put` operation.
|
81
|
+
A lower TTL can be specified by the `partition_ttl` argument in the `put` or `put_many` methods.
|
82
|
+
Each partition's expiry is handled independently.
|
83
|
+
|
84
|
+
As such, `Queue`s are best used for communication between active functions and not relied on for persistent storage.
|
85
|
+
|
86
|
+
On app completion or after stopping an app any associated `Queue` objects are cleaned up.
|
87
|
+
All its partitions will be cleared.
|
88
|
+
|
89
|
+
**Limits**
|
90
|
+
|
91
|
+
A single `Queue` can contain up to 100,000 partitions, each with up to 5,000 items. Each item can be up to 256 KiB.
|
92
|
+
|
93
|
+
Partition keys must be non-empty and must not exceed 64 bytes.
|
88
94
|
"""
|
89
95
|
|
90
96
|
@staticmethod
|
modal/sandbox.py
CHANGED
@@ -21,7 +21,7 @@ from ._utils.mount_utils import validate_network_file_systems, validate_volumes
|
|
21
21
|
from .client import _Client
|
22
22
|
from .config import config
|
23
23
|
from .container_process import _ContainerProcess
|
24
|
-
from .exception import InvalidError, SandboxTerminatedError, SandboxTimeoutError, deprecation_warning
|
24
|
+
from .exception import ExecutionError, InvalidError, SandboxTerminatedError, SandboxTimeoutError, deprecation_warning
|
25
25
|
from .gpu import GPU_T
|
26
26
|
from .image import _Image
|
27
27
|
from .io_streams import StreamReader, StreamWriter, _StreamReader, _StreamWriter
|
@@ -196,7 +196,10 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
196
196
|
gpu: GPU_T = None,
|
197
197
|
cloud: Optional[str] = None,
|
198
198
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the sandbox on.
|
199
|
-
|
199
|
+
# Specify, in fractional CPU cores, how many CPU cores to request.
|
200
|
+
# Or, pass (request, limit) to additionally specify a hard limit in fractional CPU cores.
|
201
|
+
# CPU throttling will prevent a container from exceeding its specified limit.
|
202
|
+
cpu: Optional[Union[float, tuple[float, float]]] = None,
|
200
203
|
# Specify, in MiB, a memory request which is the minimum memory required.
|
201
204
|
# Or, pass (request, limit) to additionally specify a hard limit in MiB.
|
202
205
|
memory: Optional[Union[int, tuple[int, int]]] = None,
|
@@ -331,6 +334,29 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
331
334
|
except GRPCError as exc:
|
332
335
|
raise InvalidError(exc.message) if exc.status == Status.INVALID_ARGUMENT else exc
|
333
336
|
|
337
|
+
async def snapshot_filesystem(self, timeout: int = 55) -> _Image:
|
338
|
+
"""Snapshot the filesystem of the Sandbox.
|
339
|
+
|
340
|
+
Returns an [`Image`](https://modal.com/docs/reference/modal.Image) object which
|
341
|
+
can be used to spawn a new Sandbox with the same filesystem.
|
342
|
+
"""
|
343
|
+
req = api_pb2.SandboxSnapshotFsRequest(sandbox_id=self.object_id, timeout=timeout)
|
344
|
+
resp = await retry_transient_errors(self._client.stub.SandboxSnapshotFs, req)
|
345
|
+
|
346
|
+
if resp.result.status != api_pb2.GenericResult.GENERIC_STATUS_SUCCESS:
|
347
|
+
raise ExecutionError(resp.result.exception)
|
348
|
+
|
349
|
+
image_id = resp.image_id
|
350
|
+
metadata = resp.image_metadata
|
351
|
+
|
352
|
+
async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[str]):
|
353
|
+
self._hydrate(image_id, resolver.client, metadata)
|
354
|
+
|
355
|
+
rep = "Image()"
|
356
|
+
image = _Image._from_loader(_load, rep)
|
357
|
+
|
358
|
+
return image
|
359
|
+
|
334
360
|
# Live handle methods
|
335
361
|
|
336
362
|
async def wait(self, raise_on_termination: bool = True):
|
@@ -481,17 +507,16 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
481
507
|
await secret.resolve(client=self._client)
|
482
508
|
|
483
509
|
task_id = await self._get_task_id()
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
secret_ids=[secret.object_id for secret in secrets],
|
493
|
-
)
|
510
|
+
req = api_pb2.ContainerExecRequest(
|
511
|
+
task_id=task_id,
|
512
|
+
command=cmds,
|
513
|
+
pty_info=_pty_info or pty_info,
|
514
|
+
runtime_debug=config.get("function_runtime_debug"),
|
515
|
+
timeout_secs=timeout or 0,
|
516
|
+
workdir=workdir,
|
517
|
+
secret_ids=[secret.object_id for secret in secrets],
|
494
518
|
)
|
519
|
+
resp = await retry_transient_errors(self._client.stub.ContainerExec, req)
|
495
520
|
by_line = bufsize == 1
|
496
521
|
return _ContainerProcess(resp.exec_id, self._client, stdout=stdout, stderr=stderr, text=text, by_line=by_line)
|
497
522
|
|
modal/sandbox.pyi
CHANGED
@@ -69,7 +69,7 @@ class _Sandbox(modal.object._Object):
|
|
69
69
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
70
70
|
cloud: typing.Optional[str] = None,
|
71
71
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
72
|
-
cpu: typing.
|
72
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
73
73
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
74
74
|
block_network: bool = False,
|
75
75
|
cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
|
@@ -88,6 +88,7 @@ class _Sandbox(modal.object._Object):
|
|
88
88
|
@staticmethod
|
89
89
|
async def from_id(sandbox_id: str, client: typing.Optional[modal.client._Client] = None) -> _Sandbox: ...
|
90
90
|
async def set_tags(self, tags: dict[str, str], *, client: typing.Optional[modal.client._Client] = None): ...
|
91
|
+
async def snapshot_filesystem(self, timeout: int = 55) -> modal.image._Image: ...
|
91
92
|
async def wait(self, raise_on_termination: bool = True): ...
|
92
93
|
async def tunnels(self, timeout: int = 50) -> dict[int, modal._tunnel.Tunnel]: ...
|
93
94
|
async def terminate(self): ...
|
@@ -189,7 +190,7 @@ class Sandbox(modal.object.Object):
|
|
189
190
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
190
191
|
cloud: typing.Optional[str] = None,
|
191
192
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
192
|
-
cpu: typing.
|
193
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
193
194
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
194
195
|
block_network: bool = False,
|
195
196
|
cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
|
@@ -220,7 +221,7 @@ class Sandbox(modal.object.Object):
|
|
220
221
|
gpu: typing.Union[None, bool, str, modal.gpu._GPUConfig] = None,
|
221
222
|
cloud: typing.Optional[str] = None,
|
222
223
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
223
|
-
cpu: typing.
|
224
|
+
cpu: typing.Union[float, tuple[float, float], None] = None,
|
224
225
|
memory: typing.Union[int, tuple[int, int], None] = None,
|
225
226
|
block_network: bool = False,
|
226
227
|
cidr_allowlist: typing.Optional[collections.abc.Sequence[str]] = None,
|
@@ -252,6 +253,12 @@ class Sandbox(modal.object.Object):
|
|
252
253
|
|
253
254
|
set_tags: __set_tags_spec
|
254
255
|
|
256
|
+
class __snapshot_filesystem_spec(typing_extensions.Protocol):
|
257
|
+
def __call__(self, timeout: int = 55) -> modal.image.Image: ...
|
258
|
+
async def aio(self, timeout: int = 55) -> modal.image.Image: ...
|
259
|
+
|
260
|
+
snapshot_filesystem: __snapshot_filesystem_spec
|
261
|
+
|
255
262
|
class __wait_spec(typing_extensions.Protocol):
|
256
263
|
def __call__(self, raise_on_termination: bool = True): ...
|
257
264
|
async def aio(self, raise_on_termination: bool = True): ...
|
@@ -18,8 +18,8 @@ modal/_watcher.py,sha256=K6LYnlmSGQB4tWWI9JADv-tvSvQ1j522FwT71B51CX8,3584
|
|
18
18
|
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
|
-
modal/client.py,sha256=
|
22
|
-
modal/client.pyi,sha256
|
21
|
+
modal/client.py,sha256=cmylZhU35txmrx4nltNYuuqXRgeoMtm0ow1J9wJkEYQ,16400
|
22
|
+
modal/client.pyi,sha256=-fnThLyTYwsvmI5wZIayIBBHAkeTQPwnbRT-GNGQe8g,7354
|
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=OJqzj_V-n1g48BY_4Jg_BOTQdftEEl4kTWN0X4FOOdg,27378
|
@@ -33,17 +33,17 @@ modal/environments.py,sha256=5cgA-zbm6ngKLsRA19zSOgtgo9-BarJK3FJK0BiF2Lo,6505
|
|
33
33
|
modal/environments.pyi,sha256=XalNpiPkAtHWAvOU2Cotq0ozmtl-Jv0FDsR8h9mr27Q,3521
|
34
34
|
modal/exception.py,sha256=EBkdWVved2XEPsXaoPRu56xfxFFHL9iuqvUsdj42WDA,6392
|
35
35
|
modal/experimental.py,sha256=jFuNbwrNHos47viMB9q-cHJSvf2RDxDdoEcss9plaZE,2302
|
36
|
-
modal/functions.py,sha256=
|
37
|
-
modal/functions.pyi,sha256=
|
36
|
+
modal/functions.py,sha256=3GjjFbf40XciWAa4rTmh0erkZjPzRjKHqWxUu91mHOU,66685
|
37
|
+
modal/functions.pyi,sha256=4k5CaJ9iTuEyHQ2rC5OysNHBLv1CZrD7zBMU1zXIU4w,24988
|
38
38
|
modal/gpu.py,sha256=r4rL6uH3UJIQthzYvfWauXNyh01WqCPtKZCmmSX1fd4,6881
|
39
39
|
modal/image.py,sha256=cQ6WP1xHXZT_nY8z3aEFiGwKzrTV0yxi3Ab8JzF91eo,79653
|
40
40
|
modal/image.pyi,sha256=PIKH6JBA4L5TfdJrQu3pm2ykyIITmiP920TpP8cdyQA,24585
|
41
|
-
modal/io_streams.py,sha256=
|
41
|
+
modal/io_streams.py,sha256=QkQiizKRzd5bnbKQsap31LJgBYlAnj4-XkV_50xPYX0,15079
|
42
42
|
modal/io_streams.pyi,sha256=bCCVSxkMcosYd8O3PQDDwJw7TQ8JEcnYonLJ5t27TQs,4804
|
43
43
|
modal/mount.py,sha256=liaid5p42o0OKnzoocJJ_oCovDVderk3-JuCTa5pqtA,27656
|
44
44
|
modal/mount.pyi,sha256=3e4nkXUeeVmUmOyK8Tiyk_EQlHeWruN3yGJVnmDUVrI,9761
|
45
|
-
modal/network_file_system.py,sha256=
|
46
|
-
modal/network_file_system.pyi,sha256=
|
45
|
+
modal/network_file_system.py,sha256=NKZgh_p8MyJyyJgP92lhRgTmwA3kOPw7m8AbYlchhCE,14530
|
46
|
+
modal/network_file_system.pyi,sha256=8mHKXuRkxHPazF6ljIW7g4M5aVqLSl6eKUPLgDCug5c,7901
|
47
47
|
modal/object.py,sha256=KmtWRDd5ntHGSO9ASHe9MJcIgjNRqaDXGc3rWOXwrmA,9646
|
48
48
|
modal/object.pyi,sha256=MO78H9yFSE5i1gExPEwyyQzLdlshkcGHN1aQ0ylyvq0,8802
|
49
49
|
modal/output.py,sha256=N0xf4qeudEaYrslzdAl35VKV8rapstgIM2e9wO8_iy0,1967
|
@@ -54,14 +54,14 @@ modal/partial_function.pyi,sha256=EafGOzZdEq-yE5bYRoMfnMqw-o8Hk_So8MRPDSB99_0,89
|
|
54
54
|
modal/proxy.py,sha256=ZrOsuQP7dSZFq1OrIxalNnt0Zvsnp1h86Th679sSL40,1417
|
55
55
|
modal/proxy.pyi,sha256=UvygdOYneLTuoDY6hVaMNCyZ947Tmx93IdLjErUqkvM,368
|
56
56
|
modal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
|
-
modal/queue.py,sha256=
|
57
|
+
modal/queue.py,sha256=fJYFfpdrlVj_doc3QxgvJvv7c8BGHjjja5q_9HCtSqs,18658
|
58
58
|
modal/queue.pyi,sha256=di3ownBw4jc6d4X7ygXtbpjlUMOK69qyaD3lVsJbpoM,9900
|
59
59
|
modal/retries.py,sha256=HKR2Q9aNPWkMjQ5nwobqYTuZaSuw0a8lI2zrtY5IW98,5230
|
60
60
|
modal/runner.py,sha256=7obU-Gq1ocpBGCuR6pvn1T-D6ggg1T48qFo2TNUGWkU,24089
|
61
61
|
modal/runner.pyi,sha256=RAtCvx_lXWjyFjIaZ3t9-X1c7rqpgAQlhl4Hww53OY8,5038
|
62
62
|
modal/running_app.py,sha256=CshNvGDJtagOdKW54uYjY8HY73j2TpnsL9jkPFZAsfA,560
|
63
|
-
modal/sandbox.py,sha256=
|
64
|
-
modal/sandbox.pyi,sha256=
|
63
|
+
modal/sandbox.py,sha256=ua2z6aV_fBE_7hSCv9vcRp4U9j6lRE9uOrcen4LEJks,26316
|
64
|
+
modal/sandbox.pyi,sha256=fRl32Pt5F6TbK7aYewjlcL4WQxxmp7m6Ybktmkd2VOk,18108
|
65
65
|
modal/schedule.py,sha256=0ZFpKs1bOxeo5n3HZjoL7OE2ktsb-_oGtq-WJEPO4tY,2615
|
66
66
|
modal/scheduler_placement.py,sha256=BAREdOY5HzHpzSBqt6jDVR6YC_jYfHMVqOzkyqQfngU,1235
|
67
67
|
modal/secret.py,sha256=Y1WgybQIkfkxdzH9CQ1h-Wd1DJJpzipigMhyyvSxTww,10007
|
@@ -75,7 +75,7 @@ modal/volume.py,sha256=IISuMeXq9MoSkhXg8Q6JG0F-2n9NTkWk0xGuJB8l3d8,29159
|
|
75
75
|
modal/volume.pyi,sha256=St0mDiaojfep6Bs4sBbkRJmeacYHF6lh6FKOWGmheHA,11182
|
76
76
|
modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
77
77
|
modal/_runtime/asgi.py,sha256=GvuxZqWnIHMIR-Bx5f7toCQlkERaJO8CHjTPNM9IFIw,21537
|
78
|
-
modal/_runtime/container_io_manager.py,sha256=
|
78
|
+
modal/_runtime/container_io_manager.py,sha256=8NyX5uuwmHEJgxMwdoY9PpEO-IHA8LGdYdbdHLIafK8,44131
|
79
79
|
modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
|
80
80
|
modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
|
81
81
|
modal/_runtime/user_code_imports.py,sha256=q_3JOYqCPDcVFZWCHEjyEqj8yzdFsQ49HzeqYmFDLbk,14521
|
@@ -101,8 +101,8 @@ modal/_vendor/cloudpickle.py,sha256=Loq12qo7PBNbE4LFVEW6BdMMwY10MG94EOW1SCpcnQ0,
|
|
101
101
|
modal/_vendor/tblib.py,sha256=g1O7QUDd3sDoLd8YPFltkXkih7r_fyZOjgmGuligv3s,9722
|
102
102
|
modal/cli/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
|
103
103
|
modal/cli/_download.py,sha256=t6BXZwjTd9MgznDvbsV8rp0FZWggdzC-lUAGZU4xx1g,3984
|
104
|
-
modal/cli/_traceback.py,sha256=
|
105
|
-
modal/cli/app.py,sha256=
|
104
|
+
modal/cli/_traceback.py,sha256=Tm0g4V_fr4XAlmuh_4MNgZKtjJly9wsWtnKKvOJFM7Q,7130
|
105
|
+
modal/cli/app.py,sha256=KOU3tKdcw50612rmN2LmO-N8cT1M1-UgLs7tw68Kgds,7717
|
106
106
|
modal/cli/config.py,sha256=pXPLmX0bIoV57rQNqIPK7V-yllj-GPRY4jiBO_EklGg,1667
|
107
107
|
modal/cli/container.py,sha256=lRSrxnl7bTLLW9T6hzkqOxqFzzV9qHyXPOuOHY8zkc4,3194
|
108
108
|
modal/cli/dict.py,sha256=gwX4ZBsrr0dpWf_B5_5GN_ealcVzpcGyvY24dEY4y3Y,4455
|
@@ -110,7 +110,7 @@ modal/cli/entry_point.py,sha256=aaNxFAqZcmtSjwzkYIA_Ba9CkL4cL4_i2gy5VjoXxkM,4228
|
|
110
110
|
modal/cli/environment.py,sha256=Ayddkiq9jdj3XYDJ8ZmUqFpPPH8xajYlbexRkzGtUcg,4334
|
111
111
|
modal/cli/import_refs.py,sha256=wnqE5AMeyAN3IZmQvJCp54KRnJh8Nq_5fMqB6u6GEL8,9147
|
112
112
|
modal/cli/launch.py,sha256=uyI-ouGvYRjHLGxGQ2lYBZq32BiRT1i0L8ksz5iy7K8,2935
|
113
|
-
modal/cli/network_file_system.py,sha256=
|
113
|
+
modal/cli/network_file_system.py,sha256=3QbAxKEoRc6RCMsYE3OS-GcuiI4GMkz_wAKsIBbN1qg,8186
|
114
114
|
modal/cli/profile.py,sha256=rLXfjJObfPNjaZvNfHGIKqs7y9bGYyGe-K7V0w-Ni0M,3110
|
115
115
|
modal/cli/queues.py,sha256=MIh2OsliNE2QeL1erubfsRsNuG4fxqcqWA2vgIfQ4Mg,4494
|
116
116
|
modal/cli/run.py,sha256=IPA5Hx7HqCE01NilPZDh1fFaslO4QZa-RKEpMPqjLqA,17066
|
@@ -142,13 +142,13 @@ modal_global_objects/mounts/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0
|
|
142
142
|
modal_global_objects/mounts/modal_client_package.py,sha256=W0E_yShsRojPzWm6LtIQqNVolapdnrZkm2hVEQuZK_4,767
|
143
143
|
modal_global_objects/mounts/python_standalone.py,sha256=SL_riIxpd8mP4i4CLDCWiFFNj0Ltknm9c_UIGfX5d60,1836
|
144
144
|
modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
|
145
|
-
modal_proto/api.proto,sha256=
|
146
|
-
modal_proto/api_grpc.py,sha256=
|
147
|
-
modal_proto/api_pb2.py,sha256=
|
148
|
-
modal_proto/api_pb2.pyi,sha256=
|
149
|
-
modal_proto/api_pb2_grpc.py,sha256=
|
150
|
-
modal_proto/api_pb2_grpc.pyi,sha256=
|
151
|
-
modal_proto/modal_api_grpc.py,sha256
|
145
|
+
modal_proto/api.proto,sha256=zd8V-GFVMtUP8YwxnlVvaJIuocpWKchRCeUZSlMMk74,78818
|
146
|
+
modal_proto/api_grpc.py,sha256=DveC4ejFYEhCLiWbQShnmY31_FWGYU675Bmr7nHhsgs,101342
|
147
|
+
modal_proto/api_pb2.py,sha256=l88JT2IcEuSqCnk7WRTQrBoHtJyAQD4hHAoWmiCQr0A,287100
|
148
|
+
modal_proto/api_pb2.pyi,sha256=a303c1L3sRnSk9wZXa7DuQWwBZm8u6EShoJImMYxt98,384236
|
149
|
+
modal_proto/api_pb2_grpc.py,sha256=2PEP6JPOoTw2rDC5qYjLNuumP68ZwAouRhCoayisAhY,219162
|
150
|
+
modal_proto/api_pb2_grpc.pyi,sha256=uWtCxVEd0cFpOZ1oOGfZNO7Dv45OP4kp09jMnNyx9D4,51098
|
151
|
+
modal_proto/modal_api_grpc.py,sha256=-8mLby_om5MYo6yu1zA_hqhz0yLsQW7k2YWBVZW1iVs,13546
|
152
152
|
modal_proto/modal_options_grpc.py,sha256=qJ1cuwA54oRqrdTyPTbvfhFZYd9HhJKK5UCwt523r3Y,120
|
153
153
|
modal_proto/options.proto,sha256=a-siq4swVbZPfaFRXAipRZzGP2bq8OsdUvjlyzAeodQ,488
|
154
154
|
modal_proto/options_grpc.py,sha256=M18X3d-8F_cNYSVM3I25dUTO5rZ0rd-vCCfynfh13Nc,125
|
@@ -159,10 +159,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
|
|
159
159
|
modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
160
160
|
modal_version/__init__.py,sha256=3IY-AWLH55r35_mQXIaut0jrJvoPuf1NZJBQQfSbPuo,470
|
161
161
|
modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
|
162
|
-
modal_version/_version_generated.py,sha256=
|
163
|
-
modal-0.67.
|
164
|
-
modal-0.67.
|
165
|
-
modal-0.67.
|
166
|
-
modal-0.67.
|
167
|
-
modal-0.67.
|
168
|
-
modal-0.67.
|
162
|
+
modal_version/_version_generated.py,sha256=CPqvHkRI4G0suiPfCuDEKCeUgw_O5yILvh3KaNl3Np0,149
|
163
|
+
modal-0.67.42.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
164
|
+
modal-0.67.42.dist-info/METADATA,sha256=5HkkANTv2LGFzF5o8GlWJIgaUob1Vyk98nVmZienRxM,2329
|
165
|
+
modal-0.67.42.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
166
|
+
modal-0.67.42.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
167
|
+
modal-0.67.42.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
|
168
|
+
modal-0.67.42.dist-info/RECORD,,
|