modal 1.1.5.dev66__py3-none-any.whl → 1.3.1.dev8__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/__init__.py +4 -4
- modal/__main__.py +4 -29
- modal/_billing.py +84 -0
- modal/_clustered_functions.py +1 -3
- modal/_container_entrypoint.py +33 -208
- modal/_functions.py +171 -138
- modal/_grpc_client.py +191 -0
- modal/_ipython.py +16 -6
- modal/_load_context.py +106 -0
- modal/_object.py +72 -21
- modal/_output.py +12 -14
- modal/_partial_function.py +31 -4
- modal/_resolver.py +44 -57
- modal/_runtime/container_io_manager.py +30 -28
- modal/_runtime/container_io_manager.pyi +42 -44
- modal/_runtime/gpu_memory_snapshot.py +9 -7
- modal/_runtime/user_code_event_loop.py +80 -0
- modal/_runtime/user_code_imports.py +236 -10
- modal/_serialization.py +2 -1
- modal/_traceback.py +4 -13
- modal/_tunnel.py +16 -11
- modal/_tunnel.pyi +25 -3
- modal/_utils/async_utils.py +337 -10
- modal/_utils/auth_token_manager.py +1 -4
- modal/_utils/blob_utils.py +29 -22
- modal/_utils/function_utils.py +20 -21
- modal/_utils/grpc_testing.py +6 -3
- modal/_utils/grpc_utils.py +223 -64
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/name_utils.py +2 -3
- modal/_utils/package_utils.py +0 -1
- modal/_utils/rand_pb_testing.py +8 -1
- modal/_utils/task_command_router_client.py +524 -0
- modal/_vendor/cloudpickle.py +144 -48
- modal/app.py +285 -105
- modal/app.pyi +216 -53
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +6 -3
- modal/builder/PREVIEW.txt +2 -1
- modal/builder/base-images.json +4 -2
- modal/cli/_download.py +19 -3
- modal/cli/cluster.py +4 -2
- modal/cli/config.py +3 -1
- modal/cli/container.py +5 -4
- modal/cli/dict.py +5 -2
- modal/cli/entry_point.py +26 -2
- modal/cli/environment.py +2 -16
- modal/cli/launch.py +1 -76
- modal/cli/network_file_system.py +5 -20
- modal/cli/programs/run_jupyter.py +1 -1
- modal/cli/programs/vscode.py +1 -1
- modal/cli/queues.py +5 -4
- modal/cli/run.py +24 -204
- modal/cli/secret.py +1 -2
- modal/cli/shell.py +375 -0
- modal/cli/utils.py +1 -13
- modal/cli/volume.py +11 -17
- modal/client.py +16 -125
- modal/client.pyi +94 -144
- modal/cloud_bucket_mount.py +3 -1
- modal/cloud_bucket_mount.pyi +4 -0
- modal/cls.py +101 -64
- modal/cls.pyi +9 -8
- modal/config.py +21 -1
- modal/container_process.py +288 -12
- modal/container_process.pyi +99 -38
- modal/dict.py +72 -33
- modal/dict.pyi +88 -57
- modal/environments.py +16 -8
- modal/environments.pyi +6 -2
- modal/exception.py +154 -16
- modal/experimental/__init__.py +24 -53
- modal/experimental/flash.py +161 -74
- modal/experimental/flash.pyi +97 -49
- modal/file_io.py +50 -92
- modal/file_io.pyi +117 -89
- modal/functions.pyi +70 -87
- modal/image.py +82 -47
- modal/image.pyi +51 -30
- modal/io_streams.py +500 -149
- modal/io_streams.pyi +279 -189
- modal/mount.py +60 -46
- modal/mount.pyi +41 -17
- modal/network_file_system.py +19 -11
- modal/network_file_system.pyi +72 -39
- modal/object.pyi +114 -22
- modal/parallel_map.py +42 -44
- modal/parallel_map.pyi +9 -17
- modal/partial_function.pyi +4 -2
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +45 -38
- modal/queue.pyi +88 -52
- modal/runner.py +96 -96
- modal/runner.pyi +44 -27
- modal/sandbox.py +225 -107
- modal/sandbox.pyi +226 -60
- modal/secret.py +58 -56
- modal/secret.pyi +28 -13
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +29 -15
- modal/snapshot.pyi +18 -10
- modal/token_flow.py +1 -1
- modal/token_flow.pyi +4 -6
- modal/volume.py +102 -55
- modal/volume.pyi +125 -66
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
- modal-1.3.1.dev8.dist-info/RECORD +189 -0
- modal_proto/api.proto +141 -70
- modal_proto/api_grpc.py +42 -26
- modal_proto/api_pb2.py +1123 -1103
- modal_proto/api_pb2.pyi +331 -83
- modal_proto/api_pb2_grpc.py +80 -48
- modal_proto/api_pb2_grpc.pyi +26 -18
- modal_proto/modal_api_grpc.py +175 -174
- modal_proto/task_command_router.proto +164 -0
- modal_proto/task_command_router_grpc.py +138 -0
- modal_proto/task_command_router_pb2.py +180 -0
- modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +148 -57
- modal_proto/task_command_router_pb2_grpc.py +272 -0
- modal_proto/task_command_router_pb2_grpc.pyi +100 -0
- modal_version/__init__.py +1 -1
- modal_version/__main__.py +1 -1
- modal/cli/programs/launch_instance_ssh.py +0 -94
- modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.5.dev66.dist-info/RECORD +0 -191
- 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_proto/sandbox_router.proto +0 -125
- modal_proto/sandbox_router_grpc.py +0 -89
- modal_proto/sandbox_router_pb2.py +0 -128
- modal_proto/sandbox_router_pb2_grpc.py +0 -169
- modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
modal/dict.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
+
import builtins
|
|
2
3
|
from collections.abc import AsyncIterator, Mapping
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from typing import Any, Optional, Union
|
|
6
7
|
|
|
7
8
|
from google.protobuf.message import Message
|
|
8
|
-
from grpclib import GRPCError, Status
|
|
9
9
|
from synchronicity import classproperty
|
|
10
10
|
from synchronicity.async_wrap import asynccontextmanager
|
|
11
11
|
|
|
12
|
+
from modal._utils.grpc_utils import Retry
|
|
12
13
|
from modal_proto import api_pb2
|
|
13
14
|
|
|
15
|
+
from ._load_context import LoadContext
|
|
14
16
|
from ._object import (
|
|
15
17
|
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
|
16
18
|
_get_environment_name,
|
|
@@ -22,12 +24,18 @@ from ._resolver import Resolver
|
|
|
22
24
|
from ._serialization import deserialize, serialize
|
|
23
25
|
from ._utils.async_utils import TaskContext, synchronize_api
|
|
24
26
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
25
|
-
from ._utils.grpc_utils import retry_transient_errors
|
|
26
27
|
from ._utils.name_utils import check_object_name
|
|
27
28
|
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
|
28
29
|
from .client import _Client
|
|
29
30
|
from .config import logger
|
|
30
|
-
from .exception import
|
|
31
|
+
from .exception import (
|
|
32
|
+
AlreadyExistsError,
|
|
33
|
+
DeserializationError,
|
|
34
|
+
Error,
|
|
35
|
+
InvalidError,
|
|
36
|
+
NotFoundError,
|
|
37
|
+
RequestSizeError,
|
|
38
|
+
)
|
|
31
39
|
|
|
32
40
|
|
|
33
41
|
class _NoDefaultSentinel:
|
|
@@ -42,6 +50,25 @@ def _serialize_dict(data):
|
|
|
42
50
|
return [api_pb2.DictEntry(key=serialize(k), value=serialize(v)) for k, v in data.items()]
|
|
43
51
|
|
|
44
52
|
|
|
53
|
+
def _deserialize_dict_key(dict: "_Dict", data: bytes) -> Any:
|
|
54
|
+
try:
|
|
55
|
+
return deserialize(data, dict._client)
|
|
56
|
+
except DeserializationError as exc:
|
|
57
|
+
dict_identifier = f"Dict '{dict.name}'" if dict.name else f"ephemeral Dict {dict.object_id}"
|
|
58
|
+
raise DeserializationError(f"Failed to deserialize a key from {dict_identifier}: {exc}") from exc
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _deserialize_dict_value(dict: "_Dict", data: bytes, key: Any = _NO_DEFAULT) -> Any:
|
|
62
|
+
try:
|
|
63
|
+
return deserialize(data, dict._client)
|
|
64
|
+
except DeserializationError as exc:
|
|
65
|
+
key_identifier = "" if key is _NO_DEFAULT else f" for key {key!r}"
|
|
66
|
+
dict_identifier = f"Dict '{dict.name}'" if dict.name else f"ephemeral Dict {dict.object_id}"
|
|
67
|
+
raise DeserializationError(
|
|
68
|
+
f"Failed to deserialize value{key_identifier} from {dict_identifier}: {exc}"
|
|
69
|
+
) from exc
|
|
70
|
+
|
|
71
|
+
|
|
45
72
|
@dataclass
|
|
46
73
|
class DictInfo:
|
|
47
74
|
"""Information about a Dict object."""
|
|
@@ -105,11 +132,9 @@ class _DictManager:
|
|
|
105
132
|
object_creation_type=object_creation_type,
|
|
106
133
|
)
|
|
107
134
|
try:
|
|
108
|
-
await
|
|
109
|
-
except
|
|
110
|
-
if
|
|
111
|
-
raise AlreadyExistsError(exc.message)
|
|
112
|
-
else:
|
|
135
|
+
await client.stub.DictGetOrCreate(req)
|
|
136
|
+
except AlreadyExistsError:
|
|
137
|
+
if not allow_existing:
|
|
113
138
|
raise
|
|
114
139
|
|
|
115
140
|
@staticmethod
|
|
@@ -119,7 +144,7 @@ class _DictManager:
|
|
|
119
144
|
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
|
120
145
|
environment_name: str = "", # Uses active environment if not specified
|
|
121
146
|
client: Optional[_Client] = None, # Optional client with Modal credentials
|
|
122
|
-
) -> list["_Dict"]:
|
|
147
|
+
) -> builtins.list["_Dict"]:
|
|
123
148
|
"""Return a list of hydrated Dict objects.
|
|
124
149
|
|
|
125
150
|
**Examples:**
|
|
@@ -157,7 +182,7 @@ class _DictManager:
|
|
|
157
182
|
req = api_pb2.DictListRequest(
|
|
158
183
|
environment_name=_get_environment_name(environment_name), pagination=pagination
|
|
159
184
|
)
|
|
160
|
-
resp = await
|
|
185
|
+
resp = await client.stub.DictList(req)
|
|
161
186
|
items.extend(resp.dicts)
|
|
162
187
|
finished = (len(resp.dicts) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
|
163
188
|
return finished
|
|
@@ -215,7 +240,7 @@ class _DictManager:
|
|
|
215
240
|
raise
|
|
216
241
|
else:
|
|
217
242
|
req = api_pb2.DictDeleteRequest(dict_id=obj.object_id)
|
|
218
|
-
await
|
|
243
|
+
await obj._client.stub.DictDelete(req)
|
|
219
244
|
|
|
220
245
|
|
|
221
246
|
DictManager = synchronize_api(_DictManager)
|
|
@@ -327,7 +352,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
327
352
|
environment_name=_get_environment_name(environment_name),
|
|
328
353
|
data=serialized,
|
|
329
354
|
)
|
|
330
|
-
response = await
|
|
355
|
+
response = await client.stub.DictGetOrCreate(request, retry=Retry(total_timeout=10.0))
|
|
331
356
|
async with TaskContext() as tc:
|
|
332
357
|
request = api_pb2.DictHeartbeatRequest(dict_id=response.dict_id)
|
|
333
358
|
tc.infinite_loop(lambda: client.stub.DictHeartbeat(request), sleep=_heartbeat_sleep)
|
|
@@ -347,6 +372,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
347
372
|
namespace=None, # mdmd:line-hidden
|
|
348
373
|
environment_name: Optional[str] = None,
|
|
349
374
|
create_if_missing: bool = False,
|
|
375
|
+
client: Optional[_Client] = None,
|
|
350
376
|
) -> "_Dict":
|
|
351
377
|
"""Reference a named Dict, creating if necessary.
|
|
352
378
|
|
|
@@ -368,20 +394,27 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
368
394
|
"Passing data to `modal.Dict.from_name` is deprecated and will stop working in a future release.",
|
|
369
395
|
)
|
|
370
396
|
|
|
371
|
-
async def _load(self: _Dict, resolver: Resolver, existing_object_id: Optional[str]):
|
|
397
|
+
async def _load(self: _Dict, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]):
|
|
372
398
|
serialized = _serialize_dict(data if data is not None else {})
|
|
373
399
|
req = api_pb2.DictGetOrCreateRequest(
|
|
374
400
|
deployment_name=name,
|
|
375
|
-
environment_name=
|
|
401
|
+
environment_name=load_context.environment_name,
|
|
376
402
|
object_creation_type=(api_pb2.OBJECT_CREATION_TYPE_CREATE_IF_MISSING if create_if_missing else None),
|
|
377
403
|
data=serialized,
|
|
378
404
|
)
|
|
379
|
-
response = await
|
|
405
|
+
response = await load_context.client.stub.DictGetOrCreate(req)
|
|
380
406
|
logger.debug(f"Created dict with id {response.dict_id}")
|
|
381
|
-
self._hydrate(response.dict_id,
|
|
407
|
+
self._hydrate(response.dict_id, load_context.client, response.metadata)
|
|
382
408
|
|
|
383
409
|
rep = _Dict._repr(name, environment_name)
|
|
384
|
-
return _Dict._from_loader(
|
|
410
|
+
return _Dict._from_loader(
|
|
411
|
+
_load,
|
|
412
|
+
rep,
|
|
413
|
+
is_another_app=True,
|
|
414
|
+
hydrate_lazily=True,
|
|
415
|
+
name=name,
|
|
416
|
+
load_context_overrides=LoadContext(environment_name=environment_name, client=client),
|
|
417
|
+
)
|
|
385
418
|
|
|
386
419
|
@staticmethod
|
|
387
420
|
async def delete(
|
|
@@ -418,7 +451,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
418
451
|
async def clear(self) -> None:
|
|
419
452
|
"""Remove all items from the Dict."""
|
|
420
453
|
req = api_pb2.DictClearRequest(dict_id=self.object_id)
|
|
421
|
-
await
|
|
454
|
+
await self._client.stub.DictClear(req)
|
|
422
455
|
|
|
423
456
|
@live_method
|
|
424
457
|
async def get(self, key: Any, default: Optional[Any] = None) -> Any:
|
|
@@ -427,16 +460,16 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
427
460
|
Returns `default` if key does not exist.
|
|
428
461
|
"""
|
|
429
462
|
req = api_pb2.DictGetRequest(dict_id=self.object_id, key=serialize(key))
|
|
430
|
-
resp = await
|
|
463
|
+
resp = await self._client.stub.DictGet(req)
|
|
431
464
|
if not resp.found:
|
|
432
465
|
return default
|
|
433
|
-
return
|
|
466
|
+
return _deserialize_dict_value(self, resp.value, key)
|
|
434
467
|
|
|
435
468
|
@live_method
|
|
436
469
|
async def contains(self, key: Any) -> bool:
|
|
437
470
|
"""Return if a key is present."""
|
|
438
471
|
req = api_pb2.DictContainsRequest(dict_id=self.object_id, key=serialize(key))
|
|
439
|
-
resp = await
|
|
472
|
+
resp = await self._client.stub.DictContains(req)
|
|
440
473
|
return resp.found
|
|
441
474
|
|
|
442
475
|
@live_method
|
|
@@ -446,7 +479,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
446
479
|
Note: This is an expensive operation and will return at most 100,000.
|
|
447
480
|
"""
|
|
448
481
|
req = api_pb2.DictLenRequest(dict_id=self.object_id)
|
|
449
|
-
resp = await
|
|
482
|
+
resp = await self._client.stub.DictLen(req)
|
|
450
483
|
return resp.len
|
|
451
484
|
|
|
452
485
|
@live_method
|
|
@@ -475,9 +508,9 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
475
508
|
serialized = _serialize_dict(contents)
|
|
476
509
|
req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized)
|
|
477
510
|
try:
|
|
478
|
-
await
|
|
479
|
-
except
|
|
480
|
-
if "status = '413'" in exc
|
|
511
|
+
await self._client.stub.DictUpdate(req)
|
|
512
|
+
except Error as exc:
|
|
513
|
+
if "status = '413'" in str(exc):
|
|
481
514
|
raise RequestSizeError("Dict.update request is too large") from exc
|
|
482
515
|
else:
|
|
483
516
|
raise exc
|
|
@@ -493,10 +526,10 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
493
526
|
serialized = _serialize_dict(updates)
|
|
494
527
|
req = api_pb2.DictUpdateRequest(dict_id=self.object_id, updates=serialized, if_not_exists=skip_if_exists)
|
|
495
528
|
try:
|
|
496
|
-
resp = await
|
|
529
|
+
resp = await self._client.stub.DictUpdate(req)
|
|
497
530
|
return resp.created
|
|
498
|
-
except
|
|
499
|
-
if "status = '413'" in exc
|
|
531
|
+
except Error as exc:
|
|
532
|
+
if "status = '413'" in str(exc):
|
|
500
533
|
raise RequestSizeError("Dict.put request is too large") from exc
|
|
501
534
|
else:
|
|
502
535
|
raise exc
|
|
@@ -516,12 +549,12 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
516
549
|
If key is not found, return default if provided, otherwise raise KeyError.
|
|
517
550
|
"""
|
|
518
551
|
req = api_pb2.DictPopRequest(dict_id=self.object_id, key=serialize(key))
|
|
519
|
-
resp = await
|
|
552
|
+
resp = await self._client.stub.DictPop(req)
|
|
520
553
|
if not resp.found:
|
|
521
554
|
if default is not _NO_DEFAULT:
|
|
522
555
|
return default
|
|
523
556
|
raise KeyError(f"{key} not in dict {self.object_id}")
|
|
524
|
-
return
|
|
557
|
+
return _deserialize_dict_value(self, resp.value, key)
|
|
525
558
|
|
|
526
559
|
@live_method
|
|
527
560
|
async def __delitem__(self, key: Any) -> Any:
|
|
@@ -548,7 +581,7 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
548
581
|
"""
|
|
549
582
|
req = api_pb2.DictContentsRequest(dict_id=self.object_id, keys=True)
|
|
550
583
|
async for resp in self._client.stub.DictContents.unary_stream(req):
|
|
551
|
-
yield
|
|
584
|
+
yield _deserialize_dict_key(self, resp.key)
|
|
552
585
|
|
|
553
586
|
@live_method_gen
|
|
554
587
|
async def values(self) -> AsyncIterator[Any]:
|
|
@@ -559,7 +592,11 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
559
592
|
"""
|
|
560
593
|
req = api_pb2.DictContentsRequest(dict_id=self.object_id, values=True)
|
|
561
594
|
async for resp in self._client.stub.DictContents.unary_stream(req):
|
|
562
|
-
|
|
595
|
+
try:
|
|
596
|
+
key_deser = _deserialize_dict_key(self, resp.key)
|
|
597
|
+
except DeserializationError:
|
|
598
|
+
key_deser = _NO_DEFAULT
|
|
599
|
+
yield _deserialize_dict_value(self, resp.value, key_deser)
|
|
563
600
|
|
|
564
601
|
@live_method_gen
|
|
565
602
|
async def items(self) -> AsyncIterator[tuple[Any, Any]]:
|
|
@@ -570,7 +607,9 @@ class _Dict(_Object, type_prefix="di"):
|
|
|
570
607
|
"""
|
|
571
608
|
req = api_pb2.DictContentsRequest(dict_id=self.object_id, keys=True, values=True)
|
|
572
609
|
async for resp in self._client.stub.DictContents.unary_stream(req):
|
|
573
|
-
|
|
610
|
+
key_deser = _deserialize_dict_key(self, resp.key)
|
|
611
|
+
value_deser = _deserialize_dict_value(self, resp.value, key_deser)
|
|
612
|
+
yield (key_deser, value_deser)
|
|
574
613
|
|
|
575
614
|
|
|
576
615
|
Dict = synchronize_api(_Dict)
|
modal/dict.pyi
CHANGED
|
@@ -18,6 +18,8 @@ class _NoDefaultSentinel:
|
|
|
18
18
|
_NO_DEFAULT: _NoDefaultSentinel
|
|
19
19
|
|
|
20
20
|
def _serialize_dict(data): ...
|
|
21
|
+
def _deserialize_dict_key(dict: _Dict, data: bytes) -> typing.Any: ...
|
|
22
|
+
def _deserialize_dict_value(dict: _Dict, data: bytes, key: typing.Any = ...) -> typing.Any: ...
|
|
21
23
|
|
|
22
24
|
class DictInfo:
|
|
23
25
|
"""Information about a Dict object."""
|
|
@@ -222,7 +224,7 @@ class DictManager:
|
|
|
222
224
|
"""
|
|
223
225
|
...
|
|
224
226
|
|
|
225
|
-
create: __create_spec
|
|
227
|
+
create: typing.ClassVar[__create_spec]
|
|
226
228
|
|
|
227
229
|
class __list_spec(typing_extensions.Protocol):
|
|
228
230
|
def __call__(
|
|
@@ -295,7 +297,7 @@ class DictManager:
|
|
|
295
297
|
"""
|
|
296
298
|
...
|
|
297
299
|
|
|
298
|
-
list: __list_spec
|
|
300
|
+
list: typing.ClassVar[__list_spec]
|
|
299
301
|
|
|
300
302
|
class __delete_spec(typing_extensions.Protocol):
|
|
301
303
|
def __call__(
|
|
@@ -358,7 +360,7 @@ class DictManager:
|
|
|
358
360
|
"""
|
|
359
361
|
...
|
|
360
362
|
|
|
361
|
-
delete: __delete_spec
|
|
363
|
+
delete: typing.ClassVar[__delete_spec]
|
|
362
364
|
|
|
363
365
|
class _Dict(modal._object._Object):
|
|
364
366
|
"""Distributed dictionary for storage in Modal apps.
|
|
@@ -448,6 +450,7 @@ class _Dict(modal._object._Object):
|
|
|
448
450
|
namespace=None,
|
|
449
451
|
environment_name: typing.Optional[str] = None,
|
|
450
452
|
create_if_missing: bool = False,
|
|
453
|
+
client: typing.Optional[modal.client._Client] = None,
|
|
451
454
|
) -> _Dict:
|
|
452
455
|
"""Reference a named Dict, creating if necessary.
|
|
453
456
|
|
|
@@ -576,8 +579,6 @@ class _Dict(modal._object._Object):
|
|
|
576
579
|
"""
|
|
577
580
|
...
|
|
578
581
|
|
|
579
|
-
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
|
580
|
-
|
|
581
582
|
class Dict(modal.object.Object):
|
|
582
583
|
"""Distributed dictionary for storage in Modal apps.
|
|
583
584
|
|
|
@@ -633,30 +634,59 @@ class Dict(modal.object.Object):
|
|
|
633
634
|
def name(self) -> typing.Optional[str]: ...
|
|
634
635
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
|
635
636
|
def _get_metadata(self) -> modal_proto.api_pb2.DictMetadata: ...
|
|
636
|
-
@classmethod
|
|
637
|
-
def ephemeral(
|
|
638
|
-
cls: type[Dict],
|
|
639
|
-
data: typing.Optional[dict] = None,
|
|
640
|
-
client: typing.Optional[modal.client.Client] = None,
|
|
641
|
-
environment_name: typing.Optional[str] = None,
|
|
642
|
-
_heartbeat_sleep: float = 300,
|
|
643
|
-
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Dict]:
|
|
644
|
-
"""Creates a new ephemeral Dict within a context manager:
|
|
645
637
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
638
|
+
class __ephemeral_spec(typing_extensions.Protocol):
|
|
639
|
+
def __call__(
|
|
640
|
+
self,
|
|
641
|
+
/,
|
|
642
|
+
data: typing.Optional[dict] = None,
|
|
643
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
644
|
+
environment_name: typing.Optional[str] = None,
|
|
645
|
+
_heartbeat_sleep: float = 300,
|
|
646
|
+
) -> synchronicity.combined_types.AsyncAndBlockingContextManager[Dict]:
|
|
647
|
+
"""Creates a new ephemeral Dict within a context manager:
|
|
649
648
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
649
|
+
Usage:
|
|
650
|
+
```python
|
|
651
|
+
from modal import Dict
|
|
653
652
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
653
|
+
with Dict.ephemeral() as d:
|
|
654
|
+
d["foo"] = "bar"
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
```python notest
|
|
658
|
+
async with Dict.ephemeral() as d:
|
|
659
|
+
await d.put.aio("foo", "bar")
|
|
660
|
+
```
|
|
661
|
+
"""
|
|
662
|
+
...
|
|
663
|
+
|
|
664
|
+
def aio(
|
|
665
|
+
self,
|
|
666
|
+
/,
|
|
667
|
+
data: typing.Optional[dict] = None,
|
|
668
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
669
|
+
environment_name: typing.Optional[str] = None,
|
|
670
|
+
_heartbeat_sleep: float = 300,
|
|
671
|
+
) -> typing.AsyncContextManager[Dict]:
|
|
672
|
+
"""Creates a new ephemeral Dict within a context manager:
|
|
673
|
+
|
|
674
|
+
Usage:
|
|
675
|
+
```python
|
|
676
|
+
from modal import Dict
|
|
677
|
+
|
|
678
|
+
with Dict.ephemeral() as d:
|
|
679
|
+
d["foo"] = "bar"
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
```python notest
|
|
683
|
+
async with Dict.ephemeral() as d:
|
|
684
|
+
await d.put.aio("foo", "bar")
|
|
685
|
+
```
|
|
686
|
+
"""
|
|
687
|
+
...
|
|
688
|
+
|
|
689
|
+
ephemeral: typing.ClassVar[__ephemeral_spec]
|
|
660
690
|
|
|
661
691
|
@staticmethod
|
|
662
692
|
def from_name(
|
|
@@ -666,6 +696,7 @@ class Dict(modal.object.Object):
|
|
|
666
696
|
namespace=None,
|
|
667
697
|
environment_name: typing.Optional[str] = None,
|
|
668
698
|
create_if_missing: bool = False,
|
|
699
|
+
client: typing.Optional[modal.client.Client] = None,
|
|
669
700
|
) -> Dict:
|
|
670
701
|
"""Reference a named Dict, creating if necessary.
|
|
671
702
|
|
|
@@ -717,9 +748,9 @@ class Dict(modal.object.Object):
|
|
|
717
748
|
"""
|
|
718
749
|
...
|
|
719
750
|
|
|
720
|
-
delete: __delete_spec
|
|
751
|
+
delete: typing.ClassVar[__delete_spec]
|
|
721
752
|
|
|
722
|
-
class __info_spec(typing_extensions.Protocol
|
|
753
|
+
class __info_spec(typing_extensions.Protocol):
|
|
723
754
|
def __call__(self, /) -> DictInfo:
|
|
724
755
|
"""Return information about the Dict object."""
|
|
725
756
|
...
|
|
@@ -728,9 +759,9 @@ class Dict(modal.object.Object):
|
|
|
728
759
|
"""Return information about the Dict object."""
|
|
729
760
|
...
|
|
730
761
|
|
|
731
|
-
info: __info_spec
|
|
762
|
+
info: __info_spec
|
|
732
763
|
|
|
733
|
-
class __clear_spec(typing_extensions.Protocol
|
|
764
|
+
class __clear_spec(typing_extensions.Protocol):
|
|
734
765
|
def __call__(self, /) -> None:
|
|
735
766
|
"""Remove all items from the Dict."""
|
|
736
767
|
...
|
|
@@ -739,9 +770,9 @@ class Dict(modal.object.Object):
|
|
|
739
770
|
"""Remove all items from the Dict."""
|
|
740
771
|
...
|
|
741
772
|
|
|
742
|
-
clear: __clear_spec
|
|
773
|
+
clear: __clear_spec
|
|
743
774
|
|
|
744
|
-
class __get_spec(typing_extensions.Protocol
|
|
775
|
+
class __get_spec(typing_extensions.Protocol):
|
|
745
776
|
def __call__(self, /, key: typing.Any, default: typing.Optional[typing.Any] = None) -> typing.Any:
|
|
746
777
|
"""Get the value associated with a key.
|
|
747
778
|
|
|
@@ -756,9 +787,9 @@ class Dict(modal.object.Object):
|
|
|
756
787
|
"""
|
|
757
788
|
...
|
|
758
789
|
|
|
759
|
-
get: __get_spec
|
|
790
|
+
get: __get_spec
|
|
760
791
|
|
|
761
|
-
class __contains_spec(typing_extensions.Protocol
|
|
792
|
+
class __contains_spec(typing_extensions.Protocol):
|
|
762
793
|
def __call__(self, /, key: typing.Any) -> bool:
|
|
763
794
|
"""Return if a key is present."""
|
|
764
795
|
...
|
|
@@ -767,9 +798,9 @@ class Dict(modal.object.Object):
|
|
|
767
798
|
"""Return if a key is present."""
|
|
768
799
|
...
|
|
769
800
|
|
|
770
|
-
contains: __contains_spec
|
|
801
|
+
contains: __contains_spec
|
|
771
802
|
|
|
772
|
-
class __len_spec(typing_extensions.Protocol
|
|
803
|
+
class __len_spec(typing_extensions.Protocol):
|
|
773
804
|
def __call__(self, /) -> int:
|
|
774
805
|
"""Return the length of the Dict.
|
|
775
806
|
|
|
@@ -784,9 +815,9 @@ class Dict(modal.object.Object):
|
|
|
784
815
|
"""
|
|
785
816
|
...
|
|
786
817
|
|
|
787
|
-
len: __len_spec
|
|
818
|
+
len: __len_spec
|
|
788
819
|
|
|
789
|
-
class ____getitem___spec(typing_extensions.Protocol
|
|
820
|
+
class ____getitem___spec(typing_extensions.Protocol):
|
|
790
821
|
def __call__(self, /, key: typing.Any) -> typing.Any:
|
|
791
822
|
"""Get the value associated with a key.
|
|
792
823
|
|
|
@@ -801,9 +832,9 @@ class Dict(modal.object.Object):
|
|
|
801
832
|
"""
|
|
802
833
|
...
|
|
803
834
|
|
|
804
|
-
__getitem__: ____getitem___spec
|
|
835
|
+
__getitem__: ____getitem___spec
|
|
805
836
|
|
|
806
|
-
class __update_spec(typing_extensions.Protocol
|
|
837
|
+
class __update_spec(typing_extensions.Protocol):
|
|
807
838
|
def __call__(self, other: typing.Optional[collections.abc.Mapping] = None, /, **kwargs) -> None:
|
|
808
839
|
"""Update the Dict with additional items."""
|
|
809
840
|
...
|
|
@@ -812,9 +843,9 @@ class Dict(modal.object.Object):
|
|
|
812
843
|
"""Update the Dict with additional items."""
|
|
813
844
|
...
|
|
814
845
|
|
|
815
|
-
update: __update_spec
|
|
846
|
+
update: __update_spec
|
|
816
847
|
|
|
817
|
-
class __put_spec(typing_extensions.Protocol
|
|
848
|
+
class __put_spec(typing_extensions.Protocol):
|
|
818
849
|
def __call__(self, /, key: typing.Any, value: typing.Any, *, skip_if_exists: bool = False) -> bool:
|
|
819
850
|
"""Add a specific key-value pair to the Dict.
|
|
820
851
|
|
|
@@ -831,9 +862,9 @@ class Dict(modal.object.Object):
|
|
|
831
862
|
"""
|
|
832
863
|
...
|
|
833
864
|
|
|
834
|
-
put: __put_spec
|
|
865
|
+
put: __put_spec
|
|
835
866
|
|
|
836
|
-
class ____setitem___spec(typing_extensions.Protocol
|
|
867
|
+
class ____setitem___spec(typing_extensions.Protocol):
|
|
837
868
|
def __call__(self, /, key: typing.Any, value: typing.Any) -> None:
|
|
838
869
|
"""Set a specific key-value pair to the Dict.
|
|
839
870
|
|
|
@@ -848,9 +879,9 @@ class Dict(modal.object.Object):
|
|
|
848
879
|
"""
|
|
849
880
|
...
|
|
850
881
|
|
|
851
|
-
__setitem__: ____setitem___spec
|
|
882
|
+
__setitem__: ____setitem___spec
|
|
852
883
|
|
|
853
|
-
class __pop_spec(typing_extensions.Protocol
|
|
884
|
+
class __pop_spec(typing_extensions.Protocol):
|
|
854
885
|
def __call__(self, /, key: typing.Any, default: typing.Any = ...) -> typing.Any:
|
|
855
886
|
"""Remove a key from the Dict, returning the value if it exists.
|
|
856
887
|
|
|
@@ -865,9 +896,9 @@ class Dict(modal.object.Object):
|
|
|
865
896
|
"""
|
|
866
897
|
...
|
|
867
898
|
|
|
868
|
-
pop: __pop_spec
|
|
899
|
+
pop: __pop_spec
|
|
869
900
|
|
|
870
|
-
class ____delitem___spec(typing_extensions.Protocol
|
|
901
|
+
class ____delitem___spec(typing_extensions.Protocol):
|
|
871
902
|
def __call__(self, /, key: typing.Any) -> typing.Any:
|
|
872
903
|
"""Delete a key from the Dict.
|
|
873
904
|
|
|
@@ -882,9 +913,9 @@ class Dict(modal.object.Object):
|
|
|
882
913
|
"""
|
|
883
914
|
...
|
|
884
915
|
|
|
885
|
-
__delitem__: ____delitem___spec
|
|
916
|
+
__delitem__: ____delitem___spec
|
|
886
917
|
|
|
887
|
-
class ____contains___spec(typing_extensions.Protocol
|
|
918
|
+
class ____contains___spec(typing_extensions.Protocol):
|
|
888
919
|
def __call__(self, /, key: typing.Any) -> bool:
|
|
889
920
|
"""Return if a key is present.
|
|
890
921
|
|
|
@@ -899,9 +930,9 @@ class Dict(modal.object.Object):
|
|
|
899
930
|
"""
|
|
900
931
|
...
|
|
901
932
|
|
|
902
|
-
__contains__: ____contains___spec
|
|
933
|
+
__contains__: ____contains___spec
|
|
903
934
|
|
|
904
|
-
class __keys_spec(typing_extensions.Protocol
|
|
935
|
+
class __keys_spec(typing_extensions.Protocol):
|
|
905
936
|
def __call__(self, /) -> typing.Iterator[typing.Any]:
|
|
906
937
|
"""Return an iterator over the keys in this Dict.
|
|
907
938
|
|
|
@@ -918,9 +949,9 @@ class Dict(modal.object.Object):
|
|
|
918
949
|
"""
|
|
919
950
|
...
|
|
920
951
|
|
|
921
|
-
keys: __keys_spec
|
|
952
|
+
keys: __keys_spec
|
|
922
953
|
|
|
923
|
-
class __values_spec(typing_extensions.Protocol
|
|
954
|
+
class __values_spec(typing_extensions.Protocol):
|
|
924
955
|
def __call__(self, /) -> typing.Iterator[typing.Any]:
|
|
925
956
|
"""Return an iterator over the values in this Dict.
|
|
926
957
|
|
|
@@ -937,9 +968,9 @@ class Dict(modal.object.Object):
|
|
|
937
968
|
"""
|
|
938
969
|
...
|
|
939
970
|
|
|
940
|
-
values: __values_spec
|
|
971
|
+
values: __values_spec
|
|
941
972
|
|
|
942
|
-
class __items_spec(typing_extensions.Protocol
|
|
973
|
+
class __items_spec(typing_extensions.Protocol):
|
|
943
974
|
def __call__(self, /) -> typing.Iterator[tuple[typing.Any, typing.Any]]:
|
|
944
975
|
"""Return an iterator over the (key, value) tuples in this Dict.
|
|
945
976
|
|
|
@@ -956,4 +987,4 @@ class Dict(modal.object.Object):
|
|
|
956
987
|
"""
|
|
957
988
|
...
|
|
958
989
|
|
|
959
|
-
items: __items_spec
|
|
990
|
+
items: __items_spec
|
modal/environments.py
CHANGED
|
@@ -8,10 +8,10 @@ from google.protobuf.wrappers_pb2 import StringValue
|
|
|
8
8
|
|
|
9
9
|
from modal_proto import api_pb2
|
|
10
10
|
|
|
11
|
+
from ._load_context import LoadContext
|
|
11
12
|
from ._object import _Object
|
|
12
13
|
from ._resolver import Resolver
|
|
13
14
|
from ._utils.async_utils import synchronize_api, synchronizer
|
|
14
|
-
from ._utils.grpc_utils import retry_transient_errors
|
|
15
15
|
from ._utils.name_utils import check_object_name
|
|
16
16
|
from .client import _Client
|
|
17
17
|
from .config import config, logger
|
|
@@ -53,6 +53,7 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
53
53
|
name: str,
|
|
54
54
|
*,
|
|
55
55
|
create_if_missing: bool = False,
|
|
56
|
+
client: Optional[_Client] = None,
|
|
56
57
|
):
|
|
57
58
|
if name:
|
|
58
59
|
# Allow null names for the case where we want to look up the "default" environment,
|
|
@@ -62,7 +63,9 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
62
63
|
# environments as part of public API when we make this class more useful.
|
|
63
64
|
check_object_name(name, "Environment")
|
|
64
65
|
|
|
65
|
-
async def _load(
|
|
66
|
+
async def _load(
|
|
67
|
+
self: _Environment, resolver: Resolver, load_context: LoadContext, existing_object_id: Optional[str]
|
|
68
|
+
):
|
|
66
69
|
request = api_pb2.EnvironmentGetOrCreateRequest(
|
|
67
70
|
deployment_name=name,
|
|
68
71
|
object_creation_type=(
|
|
@@ -71,12 +74,17 @@ class _Environment(_Object, type_prefix="en"):
|
|
|
71
74
|
else api_pb2.OBJECT_CREATION_TYPE_UNSPECIFIED
|
|
72
75
|
),
|
|
73
76
|
)
|
|
74
|
-
response = await
|
|
77
|
+
response = await load_context.client.stub.EnvironmentGetOrCreate(request)
|
|
75
78
|
logger.debug(f"Created environment with id {response.environment_id}")
|
|
76
|
-
self._hydrate(response.environment_id,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
self._hydrate(response.environment_id, load_context.client, response.metadata)
|
|
80
|
+
|
|
81
|
+
return _Environment._from_loader(
|
|
82
|
+
_load,
|
|
83
|
+
f"Environment.from_name({name!r})",
|
|
84
|
+
is_another_app=True,
|
|
85
|
+
hydrate_lazily=True,
|
|
86
|
+
load_context_overrides=LoadContext(client=client),
|
|
87
|
+
)
|
|
80
88
|
|
|
81
89
|
|
|
82
90
|
Environment = synchronize_api(_Environment)
|
|
@@ -89,7 +97,7 @@ ENVIRONMENT_CACHE: dict[str, _Environment] = {}
|
|
|
89
97
|
async def _get_environment_cached(name: str, client: _Client) -> _Environment:
|
|
90
98
|
if name in ENVIRONMENT_CACHE:
|
|
91
99
|
return ENVIRONMENT_CACHE[name]
|
|
92
|
-
environment = await _Environment.from_name(name).hydrate(
|
|
100
|
+
environment = await _Environment.from_name(name, client=client).hydrate()
|
|
93
101
|
ENVIRONMENT_CACHE[name] = environment
|
|
94
102
|
return environment
|
|
95
103
|
|
modal/environments.pyi
CHANGED
|
@@ -45,7 +45,9 @@ class _Environment(modal._object._Object):
|
|
|
45
45
|
|
|
46
46
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
|
47
47
|
@staticmethod
|
|
48
|
-
def from_name(
|
|
48
|
+
def from_name(
|
|
49
|
+
name: str, *, create_if_missing: bool = False, client: typing.Optional[modal.client._Client] = None
|
|
50
|
+
): ...
|
|
49
51
|
|
|
50
52
|
class Environment(modal.object.Object):
|
|
51
53
|
_settings: EnvironmentSettings
|
|
@@ -56,7 +58,9 @@ class Environment(modal.object.Object):
|
|
|
56
58
|
|
|
57
59
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
|
58
60
|
@staticmethod
|
|
59
|
-
def from_name(
|
|
61
|
+
def from_name(
|
|
62
|
+
name: str, *, create_if_missing: bool = False, client: typing.Optional[modal.client.Client] = None
|
|
63
|
+
): ...
|
|
60
64
|
|
|
61
65
|
async def _get_environment_cached(name: str, client: modal.client._Client) -> _Environment: ...
|
|
62
66
|
|