modal 1.0.6.dev58__py3-none-any.whl → 1.2.3.dev7__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 +3 -4
- modal/_billing.py +80 -0
- modal/_clustered_functions.py +7 -3
- modal/_clustered_functions.pyi +4 -2
- modal/_container_entrypoint.py +41 -49
- modal/_functions.py +424 -195
- modal/_grpc_client.py +171 -0
- modal/_load_context.py +105 -0
- modal/_object.py +68 -20
- modal/_output.py +58 -45
- modal/_partial_function.py +36 -11
- modal/_pty.py +7 -3
- modal/_resolver.py +21 -35
- modal/_runtime/asgi.py +4 -3
- modal/_runtime/container_io_manager.py +301 -186
- modal/_runtime/container_io_manager.pyi +70 -61
- modal/_runtime/execution_context.py +18 -2
- modal/_runtime/execution_context.pyi +4 -1
- modal/_runtime/gpu_memory_snapshot.py +170 -63
- modal/_runtime/user_code_imports.py +28 -58
- modal/_serialization.py +57 -1
- modal/_utils/async_utils.py +33 -12
- modal/_utils/auth_token_manager.py +2 -5
- modal/_utils/blob_utils.py +110 -53
- modal/_utils/function_utils.py +49 -42
- modal/_utils/grpc_utils.py +80 -50
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +17 -3
- modal/_utils/task_command_router_client.py +536 -0
- modal/_utils/time_utils.py +34 -6
- modal/app.py +219 -83
- modal/app.pyi +229 -56
- modal/billing.py +5 -0
- modal/{requirements → builder}/2025.06.txt +1 -0
- modal/{requirements → builder}/PREVIEW.txt +1 -0
- modal/cli/_download.py +19 -3
- modal/cli/_traceback.py +3 -2
- modal/cli/app.py +4 -4
- modal/cli/cluster.py +15 -7
- modal/cli/config.py +5 -3
- modal/cli/container.py +7 -6
- modal/cli/dict.py +22 -16
- modal/cli/entry_point.py +12 -5
- modal/cli/environment.py +5 -4
- modal/cli/import_refs.py +3 -3
- modal/cli/launch.py +102 -5
- modal/cli/network_file_system.py +9 -13
- modal/cli/profile.py +3 -2
- modal/cli/programs/launch_instance_ssh.py +94 -0
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/run_marimo.py +95 -0
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +57 -26
- modal/cli/run.py +58 -16
- modal/cli/secret.py +48 -22
- modal/cli/utils.py +3 -4
- modal/cli/volume.py +28 -25
- modal/client.py +13 -116
- modal/client.pyi +9 -91
- modal/cloud_bucket_mount.py +5 -3
- modal/cloud_bucket_mount.pyi +5 -1
- modal/cls.py +130 -102
- modal/cls.pyi +45 -85
- modal/config.py +29 -10
- modal/container_process.py +291 -13
- modal/container_process.pyi +95 -32
- modal/dict.py +282 -63
- modal/dict.pyi +423 -73
- modal/environments.py +15 -27
- modal/environments.pyi +5 -15
- modal/exception.py +8 -0
- modal/experimental/__init__.py +143 -38
- modal/experimental/flash.py +247 -78
- modal/experimental/flash.pyi +137 -9
- modal/file_io.py +14 -28
- modal/file_io.pyi +2 -2
- modal/file_pattern_matcher.py +25 -16
- modal/functions.pyi +134 -61
- modal/image.py +255 -86
- modal/image.pyi +300 -62
- modal/io_streams.py +436 -126
- modal/io_streams.pyi +236 -171
- modal/mount.py +62 -157
- modal/mount.pyi +45 -172
- modal/network_file_system.py +30 -53
- modal/network_file_system.pyi +16 -76
- modal/object.pyi +42 -8
- modal/parallel_map.py +821 -113
- modal/parallel_map.pyi +134 -0
- modal/partial_function.pyi +4 -1
- modal/proxy.py +16 -7
- modal/proxy.pyi +10 -2
- modal/queue.py +263 -61
- modal/queue.pyi +409 -66
- modal/runner.py +112 -92
- modal/runner.pyi +45 -27
- modal/sandbox.py +451 -124
- modal/sandbox.pyi +513 -67
- modal/secret.py +291 -67
- modal/secret.pyi +425 -19
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +11 -8
- modal/token_flow.py +4 -4
- modal/volume.py +344 -98
- modal/volume.pyi +464 -68
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/METADATA +9 -8
- modal-1.2.3.dev7.dist-info/RECORD +195 -0
- modal_docs/mdmd/mdmd.py +11 -1
- modal_proto/api.proto +399 -67
- modal_proto/api_grpc.py +241 -1
- modal_proto/api_pb2.py +1395 -1000
- modal_proto/api_pb2.pyi +1239 -79
- modal_proto/api_pb2_grpc.py +499 -4
- modal_proto/api_pb2_grpc.pyi +162 -14
- modal_proto/modal_api_grpc.py +175 -160
- modal_proto/sandbox_router.proto +145 -0
- modal_proto/sandbox_router_grpc.py +105 -0
- modal_proto/sandbox_router_pb2.py +149 -0
- modal_proto/sandbox_router_pb2.pyi +333 -0
- modal_proto/sandbox_router_pb2_grpc.py +203 -0
- modal_proto/sandbox_router_pb2_grpc.pyi +75 -0
- modal_proto/task_command_router.proto +144 -0
- modal_proto/task_command_router_grpc.py +105 -0
- modal_proto/task_command_router_pb2.py +149 -0
- modal_proto/task_command_router_pb2.pyi +333 -0
- modal_proto/task_command_router_pb2_grpc.py +203 -0
- modal_proto/task_command_router_pb2_grpc.pyi +75 -0
- modal_version/__init__.py +1 -1
- modal-1.0.6.dev58.dist-info/RECORD +0 -183
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- /modal/{requirements → builder}/2023.12.312.txt +0 -0
- /modal/{requirements → builder}/2023.12.txt +0 -0
- /modal/{requirements → builder}/2024.04.txt +0 -0
- /modal/{requirements → builder}/2024.10.txt +0 -0
- /modal/{requirements → builder}/README.md +0 -0
- /modal/{requirements → builder}/base-images.json +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/WHEEL +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/entry_points.txt +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/licenses/LICENSE +0 -0
- {modal-1.0.6.dev58.dist-info → modal-1.2.3.dev7.dist-info}/top_level.txt +0 -0
modal/dict.py
CHANGED
|
@@ -1,28 +1,227 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
2
|
from collections.abc import AsyncIterator, Mapping
|
|
3
|
-
from
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Optional, Union
|
|
4
6
|
|
|
5
|
-
from
|
|
7
|
+
from google.protobuf.message import Message
|
|
8
|
+
from grpclib import GRPCError, Status
|
|
9
|
+
from synchronicity import classproperty
|
|
6
10
|
from synchronicity.async_wrap import asynccontextmanager
|
|
7
11
|
|
|
12
|
+
from modal._utils.grpc_utils import Retry
|
|
8
13
|
from modal_proto import api_pb2
|
|
9
14
|
|
|
10
|
-
from .
|
|
15
|
+
from ._load_context import LoadContext
|
|
16
|
+
from ._object import (
|
|
17
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
|
18
|
+
_get_environment_name,
|
|
19
|
+
_Object,
|
|
20
|
+
live_method,
|
|
21
|
+
live_method_gen,
|
|
22
|
+
)
|
|
11
23
|
from ._resolver import Resolver
|
|
12
24
|
from ._serialization import deserialize, serialize
|
|
13
25
|
from ._utils.async_utils import TaskContext, synchronize_api
|
|
14
26
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
15
|
-
from ._utils.grpc_utils import retry_transient_errors
|
|
16
27
|
from ._utils.name_utils import check_object_name
|
|
28
|
+
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
|
17
29
|
from .client import _Client
|
|
18
30
|
from .config import logger
|
|
19
|
-
from .exception import RequestSizeError
|
|
31
|
+
from .exception import AlreadyExistsError, InvalidError, NotFoundError, RequestSizeError
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _NoDefaultSentinel:
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
return "..."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_NO_DEFAULT = _NoDefaultSentinel()
|
|
20
40
|
|
|
21
41
|
|
|
22
42
|
def _serialize_dict(data):
|
|
23
43
|
return [api_pb2.DictEntry(key=serialize(k), value=serialize(v)) for k, v in data.items()]
|
|
24
44
|
|
|
25
45
|
|
|
46
|
+
@dataclass
|
|
47
|
+
class DictInfo:
|
|
48
|
+
"""Information about a Dict object."""
|
|
49
|
+
|
|
50
|
+
# This dataclass should be limited to information that is unchanging over the lifetime of the Dict,
|
|
51
|
+
# since it is transmitted from the server when the object is hydrated and could be stale when accessed.
|
|
52
|
+
|
|
53
|
+
name: Optional[str]
|
|
54
|
+
created_at: datetime
|
|
55
|
+
created_by: Optional[str]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _DictManager:
|
|
59
|
+
"""Namespace with methods for managing named Dict objects."""
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
async def create(
|
|
63
|
+
name: str, # Name to use for the new Dict
|
|
64
|
+
*,
|
|
65
|
+
allow_existing: bool = False, # If True, no-op when the Dict already exists
|
|
66
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
|
67
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Create a new Dict object.
|
|
70
|
+
|
|
71
|
+
**Examples:**
|
|
72
|
+
|
|
73
|
+
```python notest
|
|
74
|
+
modal.Dict.objects.create("my-dict")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Dicts will be created in the active environment, or another one can be specified:
|
|
78
|
+
|
|
79
|
+
```python notest
|
|
80
|
+
modal.Dict.objects.create("my-dict", environment_name="dev")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
By default, an error will be raised if the Dict already exists, but passing
|
|
84
|
+
`allow_existing=True` will make the creation attempt a no-op in this case.
|
|
85
|
+
|
|
86
|
+
```python notest
|
|
87
|
+
modal.Dict.objects.create("my-dict", allow_existing=True)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Note that this method does not return a local instance of the Dict. You can use
|
|
91
|
+
`modal.Dict.from_name` to perform a lookup after creation.
|
|
92
|
+
|
|
93
|
+
Added in v1.1.2.
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
check_object_name(name, "Dict")
|
|
97
|
+
client = await _Client.from_env() if client is None else client
|
|
98
|
+
object_creation_type = (
|
|
99
|
+
api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING
|
|
100
|
+
if allow_existing
|
|
101
|
+
else api_pb2.OBJECT_CREATION_TYPE_CREATE_FAIL_IF_EXISTS
|
|
102
|
+
)
|
|
103
|
+
req = api_pb2.DictGetOrCreateRequest(
|
|
104
|
+
deployment_name=name,
|
|
105
|
+
environment_name=_get_environment_name(environment_name),
|
|
106
|
+
object_creation_type=object_creation_type,
|
|
107
|
+
)
|
|
108
|
+
try:
|
|
109
|
+
await client.stub.DictGetOrCreate(req)
|
|
110
|
+
except GRPCError as exc:
|
|
111
|
+
if exc.status == Status.ALREADY_EXISTS and not allow_existing:
|
|
112
|
+
raise AlreadyExistsError(exc.message)
|
|
113
|
+
else:
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
async def list(
|
|
118
|
+
*,
|
|
119
|
+
max_objects: Optional[int] = None, # Limit results to this size
|
|
120
|
+
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
|
121
|
+
environment_name: str = "", # Uses active environment if not specified
|
|
122
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
123
|
+
) -> list["_Dict"]:
|
|
124
|
+
"""Return a list of hydrated Dict objects.
|
|
125
|
+
|
|
126
|
+
**Examples:**
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
dicts = modal.Dict.objects.list()
|
|
130
|
+
print([d.name for d in dicts])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Dicts will be retreived from the active environment, or another one can be specified:
|
|
134
|
+
|
|
135
|
+
```python notest
|
|
136
|
+
dev_dicts = modal.Dict.objects.list(environment_name="dev")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
By default, all named Dict are returned, newest to oldest. It's also possible to limit the
|
|
140
|
+
number of results and to filter by creation date:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Added in v1.1.2.
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
client = await _Client.from_env() if client is None else client
|
|
150
|
+
if max_objects is not None and max_objects < 0:
|
|
151
|
+
raise InvalidError("max_objects cannot be negative")
|
|
152
|
+
|
|
153
|
+
items: list[api_pb2.DictListResponse.DictInfo] = []
|
|
154
|
+
|
|
155
|
+
async def retrieve_page(created_before: float) -> bool:
|
|
156
|
+
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
|
157
|
+
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
|
158
|
+
req = api_pb2.DictListRequest(
|
|
159
|
+
environment_name=_get_environment_name(environment_name), pagination=pagination
|
|
160
|
+
)
|
|
161
|
+
resp = await client.stub.DictList(req)
|
|
162
|
+
items.extend(resp.dicts)
|
|
163
|
+
finished = (len(resp.dicts) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
|
164
|
+
return finished
|
|
165
|
+
|
|
166
|
+
finished = await retrieve_page(as_timestamp(created_before))
|
|
167
|
+
while True:
|
|
168
|
+
if finished:
|
|
169
|
+
break
|
|
170
|
+
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
|
171
|
+
|
|
172
|
+
dicts = [
|
|
173
|
+
_Dict._new_hydrated(
|
|
174
|
+
item.dict_id,
|
|
175
|
+
client,
|
|
176
|
+
item.metadata,
|
|
177
|
+
is_another_app=True,
|
|
178
|
+
rep=_Dict._repr(item.name, environment_name),
|
|
179
|
+
)
|
|
180
|
+
for item in items
|
|
181
|
+
]
|
|
182
|
+
return dicts[:max_objects] if max_objects is not None else dicts
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
async def delete(
|
|
186
|
+
name: str, # Name of the Dict to delete
|
|
187
|
+
*,
|
|
188
|
+
allow_missing: bool = False, # If True, don't raise an error if the Dict doesn't exist
|
|
189
|
+
environment_name: Optional[str] = None, # Uses active environment if not specified
|
|
190
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
191
|
+
):
|
|
192
|
+
"""Delete a named Dict.
|
|
193
|
+
|
|
194
|
+
Warning: This deletes an *entire Dict*, not just a specific key.
|
|
195
|
+
Deletion is irreversible and will affect any Apps currently using the Dict.
|
|
196
|
+
|
|
197
|
+
**Examples:**
|
|
198
|
+
|
|
199
|
+
```python notest
|
|
200
|
+
await modal.Dict.objects.delete("my-dict")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Dicts will be deleted from the active environment, or another one can be specified:
|
|
204
|
+
|
|
205
|
+
```python notest
|
|
206
|
+
await modal.Dict.objects.delete("my-dict", environment_name="dev")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Added in v1.1.2.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
obj = await _Dict.from_name(name, environment_name=environment_name).hydrate(client)
|
|
214
|
+
except NotFoundError:
|
|
215
|
+
if not allow_missing:
|
|
216
|
+
raise
|
|
217
|
+
else:
|
|
218
|
+
req = api_pb2.DictDeleteRequest(dict_id=obj.object_id)
|
|
219
|
+
await obj._client.stub.DictDelete(req)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
DictManager = synchronize_api(_DictManager)
|
|
223
|
+
|
|
224
|
+
|
|
26
225
|
class _Dict(_Object, type_prefix="di"):
|
|
27
226
|
"""Distributed dictionary for storage in Modal apps.
|
|
28
227
|
|
|
@@ -65,12 +264,33 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
65
264
|
For more examples, see the [guide](https://modal.com/docs/guide/dicts-and-queues#modal-dicts).
|
|
66
265
|
"""
|
|
67
266
|
|
|
267
|
+
_name: Optional[str] = None
|
|
268
|
+
_metadata: Optional[api_pb2.DictMetadata] = None
|
|
269
|
+
|
|
68
270
|
def __init__(self, data={}):
|
|
69
271
|
"""mdmd:hidden"""
|
|
70
272
|
raise RuntimeError(
|
|
71
273
|
"`Dict(...)` constructor is not allowed. Please use `Dict.from_name` or `Dict.ephemeral` instead"
|
|
72
274
|
)
|
|
73
275
|
|
|
276
|
+
@classproperty
|
|
277
|
+
def objects(cls) -> _DictManager:
|
|
278
|
+
return _DictManager
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def name(self) -> Optional[str]:
|
|
282
|
+
return self._name
|
|
283
|
+
|
|
284
|
+
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
285
|
+
if metadata:
|
|
286
|
+
assert isinstance(metadata, api_pb2.DictMetadata)
|
|
287
|
+
self._metadata = metadata
|
|
288
|
+
self._name = metadata.name
|
|
289
|
+
|
|
290
|
+
def _get_metadata(self) -> api_pb2.DictMetadata:
|
|
291
|
+
assert self._metadata
|
|
292
|
+
return self._metadata
|
|
293
|
+
|
|
74
294
|
@classmethod
|
|
75
295
|
@asynccontextmanager
|
|
76
296
|
async def ephemeral(
|
|
@@ -78,7 +298,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
78
298
|
data: Optional[dict] = None, # DEPRECATED
|
|
79
299
|
client: Optional[_Client] = None,
|
|
80
300
|
environment_name: Optional[str] = None,
|
|
81
|
-
_heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
|
301
|
+
_heartbeat_sleep: float = EPHEMERAL_OBJECT_HEARTBEAT_SLEEP, # mdmd:line-hidden
|
|
82
302
|
) -> AsyncIterator["_Dict"]:
|
|
83
303
|
"""Creates a new ephemeral Dict within a context manager:
|
|
84
304
|
|
|
@@ -108,11 +328,17 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
108
328
|
environment_name=_get_environment_name(environment_name),
|
|
109
329
|
data=serialized,
|
|
110
330
|
)
|
|
111
|
-
response = await
|
|
331
|
+
response = await client.stub.DictGetOrCreate(request, retry=Retry(total_timeout=10.0))
|
|
112
332
|
async with TaskContext() as tc:
|
|
113
333
|
request = api_pb2.DictHeartbeatRequest(dict_id=response.dict_id)
|
|
114
334
|
tc.infinite_loop(lambda: client.stub.DictHeartbeat(request), sleep=_heartbeat_sleep)
|
|
115
|
-
yield cls._new_hydrated(
|
|
335
|
+
yield cls._new_hydrated(
|
|
336
|
+
response.dict_id,
|
|
337
|
+
client,
|
|
338
|
+
response.metadata,
|
|
339
|
+
is_another_app=True,
|
|
340
|
+
rep="modal.Dict.ephemeral()",
|
|
341
|
+
)
|
|
116
342
|
|
|
117
343
|
@staticmethod
|
|
118
344
|
def from_name(
|
|
@@ -122,6 +348,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
122
348
|
namespace=None, # mdmd:line-hidden
|
|
123
349
|
environment_name: Optional[str] = None,
|
|
124
350
|
create_if_missing: bool = False,
|
|
351
|
+
client: Optional[_Client] = None,
|
|
125
352
|
) -> "_Dict":
|
|
126
353
|
"""Reference a named Dict, creating if necessary.
|
|
127
354
|
|
|
@@ -143,77 +370,64 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
143
370
|
"Passing data to `modal.Dict.from_name` is deprecated and will stop working in a future release.",
|
|
144
371
|
)
|
|
145
372
|
|
|
146
|
-
async def _load(self: _Dict, resolver: Resolver, existing_object_id: Optional[str]):
|
|
373
|
+
async def _load(self: _Dict, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
|
|
147
374
|
serialized = _serialize_dict(data if data is not None else {})
|
|
148
375
|
req = api_pb2.DictGetOrCreateRequest(
|
|
149
376
|
deployment_name=name,
|
|
150
|
-
environment_name=
|
|
377
|
+
environment_name=load_context.environment_name,
|
|
151
378
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
|
152
379
|
data=serialized,
|
|
153
380
|
)
|
|
154
|
-
response = await
|
|
381
|
+
response = await load_context.client.stub.DictGetOrCreate(req)
|
|
155
382
|
logger.debug(f"Created dict with id {response.dict_id}")
|
|
156
|
-
self._hydrate(response.dict_id,
|
|
157
|
-
|
|
158
|
-
|
|
383
|
+
self._hydrate(response.dict_id, load_context.client, response.metadata)
|
|
384
|
+
|
|
385
|
+
rep = _Dict._repr(name, environment_name)
|
|
386
|
+
return _Dict._from_loader(
|
|
387
|
+
_load,
|
|
388
|
+
rep,
|
|
389
|
+
is_another_app=True,
|
|
390
|
+
hydrate_lazily=True,
|
|
391
|
+
name=name,
|
|
392
|
+
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
393
|
+
)
|
|
159
394
|
|
|
160
395
|
@staticmethod
|
|
161
|
-
async def
|
|
396
|
+
async def delete(
|
|
162
397
|
name: str,
|
|
163
|
-
|
|
164
|
-
namespace=None, # mdmd:line-hidden
|
|
398
|
+
*,
|
|
165
399
|
client: Optional[_Client] = None,
|
|
166
400
|
environment_name: Optional[str] = None,
|
|
167
|
-
|
|
168
|
-
) -> "_Dict":
|
|
401
|
+
):
|
|
169
402
|
"""mdmd:hidden
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
DEPRECATED: This method is deprecated in favor of `modal.Dict.from_name`.
|
|
403
|
+
Delete a named Dict object.
|
|
173
404
|
|
|
174
|
-
|
|
175
|
-
|
|
405
|
+
Warning: This deletes an *entire Dict*, not just a specific key.
|
|
406
|
+
Deletion is irreversible and will affect any Apps currently using the Dict.
|
|
176
407
|
|
|
177
|
-
|
|
178
|
-
d = modal.Dict.from_name("my-dict")
|
|
179
|
-
d["xyz"] = 123
|
|
180
|
-
```
|
|
408
|
+
DEPRECATED: This method is deprecated; we recommend using `modal.Dict.objects.delete` instead.
|
|
181
409
|
"""
|
|
182
410
|
deprecation_warning(
|
|
183
|
-
(2025,
|
|
184
|
-
"`modal.Dict.lookup` is deprecated and will be removed in a future release."
|
|
185
|
-
" It can be replaced with `modal.Dict.from_name`."
|
|
186
|
-
"\n\nSee https://modal.com/docs/guide/modal-1-0-migration for more information.",
|
|
187
|
-
)
|
|
188
|
-
warn_if_passing_namespace(namespace, "modal.Dict.lookup")
|
|
189
|
-
obj = _Dict.from_name(
|
|
190
|
-
name,
|
|
191
|
-
data=data,
|
|
192
|
-
environment_name=environment_name,
|
|
193
|
-
create_if_missing=create_if_missing,
|
|
411
|
+
(2025, 8, 6), "`modal.Dict.delete` is deprecated; we recommend using `modal.Dict.objects.delete` instead."
|
|
194
412
|
)
|
|
195
|
-
|
|
196
|
-
client = await _Client.from_env()
|
|
197
|
-
resolver = Resolver(client=client)
|
|
198
|
-
await resolver.load(obj)
|
|
199
|
-
return obj
|
|
413
|
+
await _Dict.objects.delete(name, environment_name=environment_name, client=client)
|
|
200
414
|
|
|
201
|
-
@
|
|
202
|
-
async def
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
415
|
+
@live_method
|
|
416
|
+
async def info(self) -> DictInfo:
|
|
417
|
+
"""Return information about the Dict object."""
|
|
418
|
+
metadata = self._get_metadata()
|
|
419
|
+
creation_info = metadata.creation_info
|
|
420
|
+
return DictInfo(
|
|
421
|
+
name=metadata.name or None,
|
|
422
|
+
created_at=timestamp_to_localized_dt(creation_info.created_at),
|
|
423
|
+
created_by=creation_info.created_by or None,
|
|
424
|
+
)
|
|
211
425
|
|
|
212
426
|
@live_method
|
|
213
427
|
async def clear(self) -> None:
|
|
214
428
|
"""Remove all items from the Dict."""
|
|
215
429
|
req = api_pb2.DictClearRequest(dict_id=self.object_id)
|
|
216
|
-
await
|
|
430
|
+
await self._client.stub.DictClear(req)
|
|
217
431
|
|
|
218
432
|
@live_method
|
|
219
433
|
async def get(self, key: Any, default: Optional[Any] = None) -> Any:
|
|
@@ -222,7 +436,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
222
436
|
Returns `default` if key does not exist.
|
|
223
437
|
"""
|
|
224
438
|
req = api_pb2.DictGetRequest(dict_id=self.object_id, key=serialize(key))
|
|
225
|
-
resp = await
|
|
439
|
+
resp = await self._client.stub.DictGet(req)
|
|
226
440
|
if not resp.found:
|
|
227
441
|
return default
|
|
228
442
|
return deserialize(resp.value, self._client)
|
|
@@ -231,7 +445,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
231
445
|
async def contains(self, key: Any) -> bool:
|
|
232
446
|
"""Return if a key is present."""
|
|
233
447
|
req = api_pb2.DictContainsRequest(dict_id=self.object_id, key=serialize(key))
|
|
234
|
-
resp = await
|
|
448
|
+
resp = await self._client.stub.DictContains(req)
|
|
235
449
|
return resp.found
|
|
236
450
|
|
|
237
451
|
@live_method
|
|
@@ -241,7 +455,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
241
455
|
Note: This is an expensive operation and will return at most 100,000.
|
|
242
456
|
"""
|
|
243
457
|
req = api_pb2.DictLenRequest(dict_id=self.object_id)
|
|
244
|
-
resp = await
|
|
458
|
+
resp = await self._client.stub.DictLen(req)
|
|
245
459
|
return resp.len
|
|
246
460
|
|
|
247
461
|
@live_method
|
|
@@ -270,7 +484,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
270
484
|
serialized = _serialize_dict(contents)
|
|
271
485
|
req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized)
|
|
272
486
|
try:
|
|
273
|
-
await
|
|
487
|
+
await self._client.stub.DictUpdate(req)
|
|
274
488
|
except GRPCError as exc:
|
|
275
489
|
if "status = '413'" in exc.message:
|
|
276
490
|
raise RequestSizeError("Dict.update request is too large") from exc
|
|
@@ -288,7 +502,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
288
502
|
serialized = _serialize_dict(updates)
|
|
289
503
|
req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized, if_not_exists=skip_if_exists)
|
|
290
504
|
try:
|
|
291
|
-
resp = await
|
|
505
|
+
resp = await self._client.stub.DictUpdate(req)
|
|
292
506
|
return resp.created
|
|
293
507
|
except GRPCError as exc:
|
|
294
508
|
if "status = '413'" in exc.message:
|
|
@@ -305,11 +519,16 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
305
519
|
return await self.put(key, value)
|
|
306
520
|
|
|
307
521
|
@live_method
|
|
308
|
-
async def pop(self, key: Any) -> Any:
|
|
309
|
-
"""Remove a key from the Dict, returning the value if it exists.
|
|
522
|
+
async def pop(self, key: Any, default: Any = _NO_DEFAULT) -> Any:
|
|
523
|
+
"""Remove a key from the Dict, returning the value if it exists.
|
|
524
|
+
|
|
525
|
+
If key is not found, return default if provided, otherwise raise KeyError.
|
|
526
|
+
"""
|
|
310
527
|
req = api_pb2.DictPopRequest(dict_id=self.object_id, key=serialize(key))
|
|
311
|
-
resp = await
|
|
528
|
+
resp = await self._client.stub.DictPop(req)
|
|
312
529
|
if not resp.found:
|
|
530
|
+
if default is not _NO_DEFAULT:
|
|
531
|
+
return default
|
|
313
532
|
raise KeyError(f"{key} not in dict {self.object_id}")
|
|
314
533
|
return deserialize(resp.value, self._client)
|
|
315
534
|
|