modal 1.1.1.dev41__py3-none-any.whl → 1.1.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.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/__main__.py +1 -2
- modal/_container_entrypoint.py +18 -7
- modal/_functions.py +135 -13
- modal/_object.py +13 -2
- modal/_partial_function.py +8 -8
- modal/_runtime/asgi.py +3 -2
- modal/_runtime/container_io_manager.py +20 -14
- modal/_runtime/container_io_manager.pyi +38 -13
- modal/_runtime/execution_context.py +18 -2
- modal/_runtime/execution_context.pyi +4 -1
- modal/_runtime/gpu_memory_snapshot.py +158 -54
- modal/_utils/blob_utils.py +83 -24
- modal/_utils/function_utils.py +4 -3
- modal/_utils/time_utils.py +28 -4
- modal/app.py +8 -4
- modal/app.pyi +8 -8
- modal/cli/dict.py +14 -11
- modal/cli/entry_point.py +9 -3
- modal/cli/launch.py +102 -4
- modal/cli/profile.py +1 -0
- modal/cli/programs/launch_instance_ssh.py +94 -0
- modal/cli/programs/run_marimo.py +95 -0
- modal/cli/queues.py +49 -19
- modal/cli/secret.py +45 -18
- modal/cli/volume.py +14 -16
- modal/client.pyi +2 -10
- modal/cls.py +12 -2
- modal/cls.pyi +9 -1
- modal/config.py +7 -7
- modal/dict.py +206 -12
- modal/dict.pyi +358 -4
- modal/experimental/__init__.py +130 -0
- modal/file_io.py +1 -1
- modal/file_io.pyi +2 -2
- modal/file_pattern_matcher.py +25 -16
- modal/functions.pyi +111 -11
- modal/image.py +9 -3
- modal/image.pyi +7 -7
- modal/mount.py +20 -13
- modal/mount.pyi +16 -3
- modal/network_file_system.py +8 -2
- modal/object.pyi +3 -0
- modal/parallel_map.py +346 -101
- modal/parallel_map.pyi +108 -0
- modal/proxy.py +2 -1
- modal/queue.py +199 -9
- modal/queue.pyi +357 -3
- modal/sandbox.py +6 -5
- modal/sandbox.pyi +17 -14
- modal/secret.py +196 -3
- modal/secret.pyi +372 -0
- modal/volume.py +239 -23
- modal/volume.pyi +405 -10
- {modal-1.1.1.dev41.dist-info → modal-1.1.2.dist-info}/METADATA +2 -2
- {modal-1.1.1.dev41.dist-info → modal-1.1.2.dist-info}/RECORD +68 -66
- modal_docs/mdmd/mdmd.py +11 -1
- modal_proto/api.proto +37 -10
- modal_proto/api_grpc.py +32 -0
- modal_proto/api_pb2.py +627 -597
- modal_proto/api_pb2.pyi +107 -19
- modal_proto/api_pb2_grpc.py +67 -2
- modal_proto/api_pb2_grpc.pyi +24 -8
- modal_proto/modal_api_grpc.py +2 -0
- modal_version/__init__.py +1 -1
- {modal-1.1.1.dev41.dist-info → modal-1.1.2.dist-info}/WHEEL +0 -0
- {modal-1.1.1.dev41.dist-info → modal-1.1.2.dist-info}/entry_points.txt +0 -0
- {modal-1.1.1.dev41.dist-info → modal-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.1.dev41.dist-info → modal-1.1.2.dist-info}/top_level.txt +0 -0
modal/sandbox.pyi
CHANGED
|
@@ -59,7 +59,7 @@ class _Sandbox(modal._object._Object):
|
|
|
59
59
|
image: modal.image._Image,
|
|
60
60
|
secrets: collections.abc.Sequence[modal.secret._Secret],
|
|
61
61
|
name: typing.Optional[str] = None,
|
|
62
|
-
timeout:
|
|
62
|
+
timeout: int = 300,
|
|
63
63
|
workdir: typing.Optional[str] = None,
|
|
64
64
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
65
65
|
cloud: typing.Optional[str] = None,
|
|
@@ -95,7 +95,7 @@ class _Sandbox(modal._object._Object):
|
|
|
95
95
|
image: typing.Optional[modal.image._Image] = None,
|
|
96
96
|
secrets: collections.abc.Sequence[modal.secret._Secret] = (),
|
|
97
97
|
network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
|
|
98
|
-
timeout:
|
|
98
|
+
timeout: int = 300,
|
|
99
99
|
workdir: typing.Optional[str] = None,
|
|
100
100
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
101
101
|
cloud: typing.Optional[str] = None,
|
|
@@ -120,8 +120,9 @@ class _Sandbox(modal._object._Object):
|
|
|
120
120
|
client: typing.Optional[modal.client._Client] = None,
|
|
121
121
|
environment_name: typing.Optional[str] = None,
|
|
122
122
|
) -> _Sandbox:
|
|
123
|
-
"""Create a new Sandbox to run untrusted, arbitrary code.
|
|
124
|
-
|
|
123
|
+
"""Create a new Sandbox to run untrusted, arbitrary code.
|
|
124
|
+
|
|
125
|
+
The Sandbox's corresponding container will be created asynchronously.
|
|
125
126
|
|
|
126
127
|
**Usage**
|
|
127
128
|
|
|
@@ -143,7 +144,7 @@ class _Sandbox(modal._object._Object):
|
|
|
143
144
|
secrets: collections.abc.Sequence[modal.secret._Secret] = (),
|
|
144
145
|
mounts: collections.abc.Sequence[modal.mount._Mount] = (),
|
|
145
146
|
network_file_systems: dict[typing.Union[str, os.PathLike], modal.network_file_system._NetworkFileSystem] = {},
|
|
146
|
-
timeout:
|
|
147
|
+
timeout: int = 300,
|
|
147
148
|
workdir: typing.Optional[str] = None,
|
|
148
149
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
149
150
|
cloud: typing.Optional[str] = None,
|
|
@@ -368,7 +369,7 @@ class Sandbox(modal.object.Object):
|
|
|
368
369
|
image: modal.image.Image,
|
|
369
370
|
secrets: collections.abc.Sequence[modal.secret.Secret],
|
|
370
371
|
name: typing.Optional[str] = None,
|
|
371
|
-
timeout:
|
|
372
|
+
timeout: int = 300,
|
|
372
373
|
workdir: typing.Optional[str] = None,
|
|
373
374
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
374
375
|
cloud: typing.Optional[str] = None,
|
|
@@ -407,7 +408,7 @@ class Sandbox(modal.object.Object):
|
|
|
407
408
|
network_file_systems: dict[
|
|
408
409
|
typing.Union[str, os.PathLike], modal.network_file_system.NetworkFileSystem
|
|
409
410
|
] = {},
|
|
410
|
-
timeout:
|
|
411
|
+
timeout: int = 300,
|
|
411
412
|
workdir: typing.Optional[str] = None,
|
|
412
413
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
413
414
|
cloud: typing.Optional[str] = None,
|
|
@@ -432,8 +433,9 @@ class Sandbox(modal.object.Object):
|
|
|
432
433
|
client: typing.Optional[modal.client.Client] = None,
|
|
433
434
|
environment_name: typing.Optional[str] = None,
|
|
434
435
|
) -> Sandbox:
|
|
435
|
-
"""Create a new Sandbox to run untrusted, arbitrary code.
|
|
436
|
-
|
|
436
|
+
"""Create a new Sandbox to run untrusted, arbitrary code.
|
|
437
|
+
|
|
438
|
+
The Sandbox's corresponding container will be created asynchronously.
|
|
437
439
|
|
|
438
440
|
**Usage**
|
|
439
441
|
|
|
@@ -457,7 +459,7 @@ class Sandbox(modal.object.Object):
|
|
|
457
459
|
network_file_systems: dict[
|
|
458
460
|
typing.Union[str, os.PathLike], modal.network_file_system.NetworkFileSystem
|
|
459
461
|
] = {},
|
|
460
|
-
timeout:
|
|
462
|
+
timeout: int = 300,
|
|
461
463
|
workdir: typing.Optional[str] = None,
|
|
462
464
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
463
465
|
cloud: typing.Optional[str] = None,
|
|
@@ -482,8 +484,9 @@ class Sandbox(modal.object.Object):
|
|
|
482
484
|
client: typing.Optional[modal.client.Client] = None,
|
|
483
485
|
environment_name: typing.Optional[str] = None,
|
|
484
486
|
) -> Sandbox:
|
|
485
|
-
"""Create a new Sandbox to run untrusted, arbitrary code.
|
|
486
|
-
|
|
487
|
+
"""Create a new Sandbox to run untrusted, arbitrary code.
|
|
488
|
+
|
|
489
|
+
The Sandbox's corresponding container will be created asynchronously.
|
|
487
490
|
|
|
488
491
|
**Usage**
|
|
489
492
|
|
|
@@ -511,7 +514,7 @@ class Sandbox(modal.object.Object):
|
|
|
511
514
|
network_file_systems: dict[
|
|
512
515
|
typing.Union[str, os.PathLike], modal.network_file_system.NetworkFileSystem
|
|
513
516
|
] = {},
|
|
514
|
-
timeout:
|
|
517
|
+
timeout: int = 300,
|
|
515
518
|
workdir: typing.Optional[str] = None,
|
|
516
519
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
517
520
|
cloud: typing.Optional[str] = None,
|
|
@@ -547,7 +550,7 @@ class Sandbox(modal.object.Object):
|
|
|
547
550
|
network_file_systems: dict[
|
|
548
551
|
typing.Union[str, os.PathLike], modal.network_file_system.NetworkFileSystem
|
|
549
552
|
] = {},
|
|
550
|
-
timeout:
|
|
553
|
+
timeout: int = 300,
|
|
551
554
|
workdir: typing.Optional[str] = None,
|
|
552
555
|
gpu: typing.Union[None, str, modal.gpu._GPUConfig] = None,
|
|
553
556
|
cloud: typing.Optional[str] = None,
|
modal/secret.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Optional, Union
|
|
|
6
6
|
|
|
7
7
|
from google.protobuf.message import Message
|
|
8
8
|
from grpclib import GRPCError, Status
|
|
9
|
+
from synchronicity import classproperty
|
|
9
10
|
|
|
10
11
|
from modal_proto import api_pb2
|
|
11
12
|
|
|
@@ -16,9 +17,9 @@ from ._utils.async_utils import synchronize_api
|
|
|
16
17
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
17
18
|
from ._utils.grpc_utils import retry_transient_errors
|
|
18
19
|
from ._utils.name_utils import check_object_name
|
|
19
|
-
from ._utils.time_utils import timestamp_to_localized_dt
|
|
20
|
+
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
|
20
21
|
from .client import _Client
|
|
21
|
-
from .exception import InvalidError, NotFoundError
|
|
22
|
+
from .exception import AlreadyExistsError, InvalidError, NotFoundError
|
|
22
23
|
|
|
23
24
|
ENV_DICT_WRONG_TYPE_ERR = "the env_dict argument to Secret has to be a dict[str, Union[str, None]]"
|
|
24
25
|
|
|
@@ -35,6 +36,176 @@ class SecretInfo:
|
|
|
35
36
|
created_by: Optional[str]
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
class _SecretManager:
|
|
40
|
+
"""Namespace with methods for managing named Secret objects."""
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
async def create(
|
|
44
|
+
name: str, # Name to use for the new Secret
|
|
45
|
+
env_dict: dict[str, str], # Key-value pairs to set in the Secret
|
|
46
|
+
*,
|
|
47
|
+
allow_existing: bool = False, # If True, no-op when the Secret already exists
|
|
48
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
|
49
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Create a new Secret object.
|
|
52
|
+
|
|
53
|
+
**Examples:**
|
|
54
|
+
|
|
55
|
+
```python notest
|
|
56
|
+
contents = {"MY_KEY": "my-value", "MY_OTHER_KEY": "my-other-value"}
|
|
57
|
+
modal.Secret.objects.create("my-secret", contents)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Secrets will be created in the active environment, or another one can be specified:
|
|
61
|
+
|
|
62
|
+
```python notest
|
|
63
|
+
modal.Secret.objects.create("my-secret", contents, environment_name="dev")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
By default, an error will be raised if the Secret already exists, but passing
|
|
67
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
|
68
|
+
If the `env_dict` data differs from the existing Secret, it will be ignored.
|
|
69
|
+
|
|
70
|
+
```python notest
|
|
71
|
+
modal.Secret.objects.create("my-secret", contents, allow_existing=True)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Note that this method does not return a local instance of the Secret. You can use
|
|
75
|
+
`modal.Secret.from_name` to perform a lookup after creation.
|
|
76
|
+
|
|
77
|
+
Added in v1.1.2.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
check_object_name(name, "Secret")
|
|
81
|
+
client = await _Client.from_env() if client is None else client
|
|
82
|
+
object_creation_type = (
|
|
83
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
|
84
|
+
if allow_existing
|
|
85
|
+
else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
|
|
86
|
+
)
|
|
87
|
+
req = api_pb2.SecretGetOrCreateRequest(
|
|
88
|
+
deployment_name=name,
|
|
89
|
+
environment_name=_get_environment_name(environment_name),
|
|
90
|
+
object_creation_type=object_creation_type,
|
|
91
|
+
env_dict=env_dict,
|
|
92
|
+
)
|
|
93
|
+
try:
|
|
94
|
+
await retry_transient_errors(client.stub.SecretGetOrCreate, req)
|
|
95
|
+
except GRPCError as exc:
|
|
96
|
+
if exc.status == Status.ALREADY_EXISTS and not allow_existing:
|
|
97
|
+
raise AlreadyExistsError(exc.message)
|
|
98
|
+
else:
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
async def list(
|
|
103
|
+
*,
|
|
104
|
+
max_objects: Optional[int] = None, # Limit requests to this size
|
|
105
|
+
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
|
106
|
+
environment_name: str = "", # Uses active environment if not specified
|
|
107
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
108
|
+
) -> list["_Secret"]:
|
|
109
|
+
"""Return a list of hydrated Secret objects.
|
|
110
|
+
|
|
111
|
+
**Examples:**
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
secrets = modal.Secret.objects.list()
|
|
115
|
+
print([s.name for s in secrets])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Secrets will be retreived from the active environment, or another one can be specified:
|
|
119
|
+
|
|
120
|
+
```python notest
|
|
121
|
+
dev_secrets = modal.Secret.objects.list(environment_name="dev")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
|
|
125
|
+
number of results and to filter by creation date:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Added in v1.1.2.
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
client = await _Client.from_env() if client is None else client
|
|
135
|
+
if max_objects is not None and max_objects < 0:
|
|
136
|
+
raise InvalidError("max_objects cannot be negative")
|
|
137
|
+
|
|
138
|
+
items: list[api_pb2.SecretListItem] = []
|
|
139
|
+
|
|
140
|
+
async def retrieve_page(created_before: float) -> bool:
|
|
141
|
+
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
|
142
|
+
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
|
143
|
+
req = api_pb2.SecretListRequest(
|
|
144
|
+
environment_name=_get_environment_name(environment_name), pagination=pagination
|
|
145
|
+
)
|
|
146
|
+
resp = await retry_transient_errors(client.stub.SecretList, req)
|
|
147
|
+
items.extend(resp.items)
|
|
148
|
+
finished = (len(resp.items) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
|
149
|
+
return finished
|
|
150
|
+
|
|
151
|
+
finished = await retrieve_page(as_timestamp(created_before))
|
|
152
|
+
while True:
|
|
153
|
+
if finished:
|
|
154
|
+
break
|
|
155
|
+
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
|
156
|
+
|
|
157
|
+
secrets = [
|
|
158
|
+
_Secret._new_hydrated(
|
|
159
|
+
item.secret_id,
|
|
160
|
+
client,
|
|
161
|
+
item.metadata,
|
|
162
|
+
is_another_app=True,
|
|
163
|
+
rep=_Secret._repr(item.label, environment_name),
|
|
164
|
+
)
|
|
165
|
+
for item in items
|
|
166
|
+
]
|
|
167
|
+
return secrets[:max_objects] if max_objects is not None else secrets
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
async def delete(
|
|
171
|
+
name: str, # Name of the Secret to delete
|
|
172
|
+
*,
|
|
173
|
+
allow_missing: bool = False, # If True, don't raise an error if the Secret doesn't exist
|
|
174
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
|
175
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
176
|
+
):
|
|
177
|
+
"""Delete a named Secret.
|
|
178
|
+
|
|
179
|
+
Warning: Deletion is irreversible and will affect any Apps currently using the Secret.
|
|
180
|
+
|
|
181
|
+
**Examples:**
|
|
182
|
+
|
|
183
|
+
```python notest
|
|
184
|
+
await modal.Secret.objects.delete("my-secret")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Secrets will be deleted from the active environment, or another one can be specified:
|
|
188
|
+
|
|
189
|
+
```python notest
|
|
190
|
+
await modal.Secret.objects.delete("my-secret", environment_name="dev")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Added in v1.1.2.
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
obj = await _Secret.from_name(name, environment_name=environment_name).hydrate(client)
|
|
198
|
+
except NotFoundError:
|
|
199
|
+
if not allow_missing:
|
|
200
|
+
raise
|
|
201
|
+
else:
|
|
202
|
+
req = api_pb2.SecretDeleteRequest(secret_id=obj.object_id)
|
|
203
|
+
await retry_transient_errors(obj._client.stub.SecretDelete, req)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
SecretManager = synchronize_api(_SecretManager)
|
|
207
|
+
|
|
208
|
+
|
|
38
209
|
class _Secret(_Object, type_prefix="st"):
|
|
39
210
|
"""Secrets provide a dictionary of environment variables for images.
|
|
40
211
|
|
|
@@ -47,6 +218,10 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
47
218
|
|
|
48
219
|
_metadata: Optional[api_pb2.SecretMetadata] = None
|
|
49
220
|
|
|
221
|
+
@classproperty
|
|
222
|
+
def objects(cls) -> _SecretManager:
|
|
223
|
+
return _SecretManager
|
|
224
|
+
|
|
50
225
|
@property
|
|
51
226
|
def name(self) -> Optional[str]:
|
|
52
227
|
return self._name
|
|
@@ -234,7 +409,8 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
234
409
|
raise
|
|
235
410
|
self._hydrate(response.secret_id, resolver.client, response.metadata)
|
|
236
411
|
|
|
237
|
-
|
|
412
|
+
rep = _Secret._repr(name, environment_name)
|
|
413
|
+
return _Secret._from_loader(_load, rep, hydrate_lazily=True, name=name)
|
|
238
414
|
|
|
239
415
|
@staticmethod
|
|
240
416
|
async def lookup(
|
|
@@ -273,6 +449,23 @@ class _Secret(_Object, type_prefix="st"):
|
|
|
273
449
|
client: Optional[_Client] = None,
|
|
274
450
|
environment_name: Optional[str] = None,
|
|
275
451
|
overwrite: bool = False,
|
|
452
|
+
) -> str:
|
|
453
|
+
"""mdmd:hidden"""
|
|
454
|
+
deprecation_warning(
|
|
455
|
+
(2025, 8, 13),
|
|
456
|
+
"The undocumented `modal.Secret.create_deployed` method is deprecated and will be removed "
|
|
457
|
+
"in a future release. It can be replaced with `modal.Secret.objects.create`.",
|
|
458
|
+
)
|
|
459
|
+
return await _Secret._create_deployed(deployment_name, env_dict, namespace, client, environment_name, overwrite)
|
|
460
|
+
|
|
461
|
+
@staticmethod
|
|
462
|
+
async def _create_deployed(
|
|
463
|
+
deployment_name: str,
|
|
464
|
+
env_dict: dict[str, str],
|
|
465
|
+
namespace=None, # mdmd:line-hidden
|
|
466
|
+
client: Optional[_Client] = None,
|
|
467
|
+
environment_name: Optional[str] = None,
|
|
468
|
+
overwrite: bool = False,
|
|
276
469
|
) -> str:
|
|
277
470
|
"""mdmd:hidden"""
|
|
278
471
|
warn_if_passing_namespace(namespace, "modal.Secret.create_deployed")
|