modal 1.1.2.dev11__py3-none-any.whl → 1.1.2.dev13__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 +0 -1
- modal/_utils/time_utils.py +28 -4
- modal/cli/dict.py +6 -7
- modal/cli/launch.py +78 -2
- modal/cli/programs/launch_instance_ssh.py +94 -0
- modal/cli/programs/run_marimo.py +95 -0
- modal/cli/queues.py +40 -15
- modal/cli/secret.py +33 -7
- modal/cli/volume.py +6 -9
- modal/client.pyi +2 -2
- modal/dict.py +78 -5
- modal/dict.pyi +115 -1
- modal/experimental/__init__.py +104 -0
- modal/functions.pyi +6 -6
- modal/queue.py +76 -3
- modal/queue.pyi +114 -0
- modal/secret.py +68 -1
- modal/secret.pyi +114 -0
- modal/volume.py +79 -6
- modal/volume.pyi +118 -4
- {modal-1.1.2.dev11.dist-info → modal-1.1.2.dev13.dist-info}/METADATA +2 -2
- {modal-1.1.2.dev11.dist-info → modal-1.1.2.dev13.dist-info}/RECORD +30 -28
- modal_docs/mdmd/mdmd.py +11 -1
- modal_proto/api.proto +4 -4
- modal_proto/api_pb2.pyi +4 -4
- modal_version/__init__.py +1 -1
- {modal-1.1.2.dev11.dist-info → modal-1.1.2.dev13.dist-info}/WHEEL +0 -0
- {modal-1.1.2.dev11.dist-info → modal-1.1.2.dev13.dist-info}/entry_points.txt +0 -0
- {modal-1.1.2.dev11.dist-info → modal-1.1.2.dev13.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.2.dev11.dist-info → modal-1.1.2.dev13.dist-info}/top_level.txt +0 -0
modal/dict.py
CHANGED
@@ -2,25 +2,32 @@
|
|
2
2
|
from collections.abc import AsyncIterator, Mapping
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from datetime import datetime
|
5
|
-
from typing import Any, Optional
|
5
|
+
from typing import Any, Optional, Union
|
6
6
|
|
7
7
|
from google.protobuf.message import Message
|
8
8
|
from grpclib import GRPCError
|
9
|
+
from synchronicity import classproperty
|
9
10
|
from synchronicity.async_wrap import asynccontextmanager
|
10
11
|
|
11
12
|
from modal_proto import api_pb2
|
12
13
|
|
13
|
-
from ._object import
|
14
|
+
from ._object import (
|
15
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
16
|
+
_get_environment_name,
|
17
|
+
_Object,
|
18
|
+
live_method,
|
19
|
+
live_method_gen,
|
20
|
+
)
|
14
21
|
from ._resolver import Resolver
|
15
22
|
from ._serialization import deserialize, serialize
|
16
23
|
from ._utils.async_utils import TaskContext, synchronize_api
|
17
24
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
18
25
|
from ._utils.grpc_utils import retry_transient_errors
|
19
26
|
from ._utils.name_utils import check_object_name
|
20
|
-
from ._utils.time_utils import timestamp_to_localized_dt
|
27
|
+
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
21
28
|
from .client import _Client
|
22
29
|
from .config import logger
|
23
|
-
from .exception import RequestSizeError
|
30
|
+
from .exception import InvalidError, RequestSizeError
|
24
31
|
|
25
32
|
|
26
33
|
def _serialize_dict(data):
|
@@ -29,7 +36,7 @@ def _serialize_dict(data):
|
|
29
36
|
|
30
37
|
@dataclass
|
31
38
|
class DictInfo:
|
32
|
-
"""Information about
|
39
|
+
"""Information about a Dict object."""
|
33
40
|
|
34
41
|
# This dataclass should be limited to information that is unchanging over the lifetime of the Dict,
|
35
42
|
# since it is transmitted from the server when the object is hydrated and could be stale when accessed.
|
@@ -39,6 +46,68 @@ class DictInfo:
|
|
39
46
|
created_by: Optional[str]
|
40
47
|
|
41
48
|
|
49
|
+
class _DictManager:
|
50
|
+
"""Namespace with methods for managing named Dict objects."""
|
51
|
+
|
52
|
+
@staticmethod
|
53
|
+
async def list(
|
54
|
+
*,
|
55
|
+
max_objects: Optional[int] = None, # Limit results to this size
|
56
|
+
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
57
|
+
environment_name: str = "", # Uses active environment if not specified
|
58
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
59
|
+
) -> list["_Dict"]:
|
60
|
+
"""Return a list of hydrated Dict objects.
|
61
|
+
|
62
|
+
**Examples:**
|
63
|
+
|
64
|
+
```python
|
65
|
+
dicts = modal.Dict.objects.list()
|
66
|
+
print([d.name for d in dicts])
|
67
|
+
```
|
68
|
+
|
69
|
+
Dicts will be retreived from the active environment, or another one can be specified:
|
70
|
+
|
71
|
+
```python notest
|
72
|
+
dev_dicts = modal.Dict.objects.list(environment_name="dev")
|
73
|
+
```
|
74
|
+
|
75
|
+
By default, all named Dict are returned, newest to oldest. It's also possible to limit the
|
76
|
+
number of results and to filter by creation date:
|
77
|
+
|
78
|
+
```python
|
79
|
+
dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
|
80
|
+
```
|
81
|
+
|
82
|
+
"""
|
83
|
+
client = await _Client.from_env() if client is None else client
|
84
|
+
if max_objects is not None and max_objects < 0:
|
85
|
+
raise InvalidError("max_objects cannot be negative")
|
86
|
+
|
87
|
+
items: list[api_pb2.DictListResponse.DictInfo] = []
|
88
|
+
|
89
|
+
async def retrieve_page(created_before: float) -> bool:
|
90
|
+
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
91
|
+
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
92
|
+
req = api_pb2.DictListRequest(environment_name=environment_name, pagination=pagination)
|
93
|
+
resp = await retry_transient_errors(client.stub.DictList, req)
|
94
|
+
items.extend(resp.dicts)
|
95
|
+
finished = (len(resp.dicts) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
96
|
+
return finished
|
97
|
+
|
98
|
+
finished = await retrieve_page(as_timestamp(created_before))
|
99
|
+
while True:
|
100
|
+
if finished:
|
101
|
+
break
|
102
|
+
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
103
|
+
|
104
|
+
dicts = [_Dict._new_hydrated(item.dict_id, client, item.metadata, is_another_app=True) for item in items]
|
105
|
+
return dicts[:max_objects] if max_objects is not None else dicts
|
106
|
+
|
107
|
+
|
108
|
+
DictManager = synchronize_api(_DictManager)
|
109
|
+
|
110
|
+
|
42
111
|
class _Dict(_Object, type_prefix="di"):
|
43
112
|
"""Distributed dictionary for storage in Modal apps.
|
44
113
|
|
@@ -90,6 +159,10 @@ class _Dict(_Object, type_prefix="di"):
|
|
90
159
|
"`Dict(...)` constructor is not allowed. Please use `Dict.from_name` or `Dict.ephemeral` instead"
|
91
160
|
)
|
92
161
|
|
162
|
+
@classproperty
|
163
|
+
def objects(cls) -> _DictManager:
|
164
|
+
return _DictManager
|
165
|
+
|
93
166
|
@property
|
94
167
|
def name(self) -> Optional[str]:
|
95
168
|
return self._name
|
modal/dict.pyi
CHANGED
@@ -5,6 +5,7 @@ import modal._object
|
|
5
5
|
import modal.client
|
6
6
|
import modal.object
|
7
7
|
import modal_proto.api_pb2
|
8
|
+
import synchronicity
|
8
9
|
import synchronicity.combined_types
|
9
10
|
import typing
|
10
11
|
import typing_extensions
|
@@ -12,7 +13,7 @@ import typing_extensions
|
|
12
13
|
def _serialize_dict(data): ...
|
13
14
|
|
14
15
|
class DictInfo:
|
15
|
-
"""Information about
|
16
|
+
"""Information about a Dict object."""
|
16
17
|
|
17
18
|
name: typing.Optional[str]
|
18
19
|
created_at: datetime.datetime
|
@@ -32,6 +33,115 @@ class DictInfo:
|
|
32
33
|
"""Return self==value."""
|
33
34
|
...
|
34
35
|
|
36
|
+
class _DictManager:
|
37
|
+
"""Namespace with methods for managing named Dict objects."""
|
38
|
+
@staticmethod
|
39
|
+
async def list(
|
40
|
+
*,
|
41
|
+
max_objects: typing.Optional[int] = None,
|
42
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
43
|
+
environment_name: str = "",
|
44
|
+
client: typing.Optional[modal.client._Client] = None,
|
45
|
+
) -> list[_Dict]:
|
46
|
+
"""Return a list of hydrated Dict objects.
|
47
|
+
|
48
|
+
**Examples:**
|
49
|
+
|
50
|
+
```python
|
51
|
+
dicts = modal.Dict.objects.list()
|
52
|
+
print([d.name for d in dicts])
|
53
|
+
```
|
54
|
+
|
55
|
+
Dicts will be retreived from the active environment, or another one can be specified:
|
56
|
+
|
57
|
+
```python notest
|
58
|
+
dev_dicts = modal.Dict.objects.list(environment_name="dev")
|
59
|
+
```
|
60
|
+
|
61
|
+
By default, all named Dict are returned, newest to oldest. It's also possible to limit the
|
62
|
+
number of results and to filter by creation date:
|
63
|
+
|
64
|
+
```python
|
65
|
+
dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
|
66
|
+
```
|
67
|
+
"""
|
68
|
+
...
|
69
|
+
|
70
|
+
class DictManager:
|
71
|
+
"""Namespace with methods for managing named Dict objects."""
|
72
|
+
def __init__(self, /, *args, **kwargs):
|
73
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
74
|
+
...
|
75
|
+
|
76
|
+
class __list_spec(typing_extensions.Protocol):
|
77
|
+
def __call__(
|
78
|
+
self,
|
79
|
+
/,
|
80
|
+
*,
|
81
|
+
max_objects: typing.Optional[int] = None,
|
82
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
83
|
+
environment_name: str = "",
|
84
|
+
client: typing.Optional[modal.client.Client] = None,
|
85
|
+
) -> list[Dict]:
|
86
|
+
"""Return a list of hydrated Dict objects.
|
87
|
+
|
88
|
+
**Examples:**
|
89
|
+
|
90
|
+
```python
|
91
|
+
dicts = modal.Dict.objects.list()
|
92
|
+
print([d.name for d in dicts])
|
93
|
+
```
|
94
|
+
|
95
|
+
Dicts will be retreived from the active environment, or another one can be specified:
|
96
|
+
|
97
|
+
```python notest
|
98
|
+
dev_dicts = modal.Dict.objects.list(environment_name="dev")
|
99
|
+
```
|
100
|
+
|
101
|
+
By default, all named Dict are returned, newest to oldest. It's also possible to limit the
|
102
|
+
number of results and to filter by creation date:
|
103
|
+
|
104
|
+
```python
|
105
|
+
dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
|
106
|
+
```
|
107
|
+
"""
|
108
|
+
...
|
109
|
+
|
110
|
+
async def aio(
|
111
|
+
self,
|
112
|
+
/,
|
113
|
+
*,
|
114
|
+
max_objects: typing.Optional[int] = None,
|
115
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
116
|
+
environment_name: str = "",
|
117
|
+
client: typing.Optional[modal.client.Client] = None,
|
118
|
+
) -> list[Dict]:
|
119
|
+
"""Return a list of hydrated Dict objects.
|
120
|
+
|
121
|
+
**Examples:**
|
122
|
+
|
123
|
+
```python
|
124
|
+
dicts = modal.Dict.objects.list()
|
125
|
+
print([d.name for d in dicts])
|
126
|
+
```
|
127
|
+
|
128
|
+
Dicts will be retreived from the active environment, or another one can be specified:
|
129
|
+
|
130
|
+
```python notest
|
131
|
+
dev_dicts = modal.Dict.objects.list(environment_name="dev")
|
132
|
+
```
|
133
|
+
|
134
|
+
By default, all named Dict are returned, newest to oldest. It's also possible to limit the
|
135
|
+
number of results and to filter by creation date:
|
136
|
+
|
137
|
+
```python
|
138
|
+
dicts = modal.Dict.objects.list(max_objects=10, created_before="2025-01-01")
|
139
|
+
```
|
140
|
+
"""
|
141
|
+
...
|
142
|
+
|
143
|
+
list: __list_spec
|
144
|
+
|
35
145
|
class _Dict(modal._object._Object):
|
36
146
|
"""Distributed dictionary for storage in Modal apps.
|
37
147
|
|
@@ -81,6 +191,8 @@ class _Dict(modal._object._Object):
|
|
81
191
|
"""mdmd:hidden"""
|
82
192
|
...
|
83
193
|
|
194
|
+
@synchronicity.classproperty
|
195
|
+
def objects(cls) -> _DictManager: ...
|
84
196
|
@property
|
85
197
|
def name(self) -> typing.Optional[str]: ...
|
86
198
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
@@ -308,6 +420,8 @@ class Dict(modal.object.Object):
|
|
308
420
|
"""mdmd:hidden"""
|
309
421
|
...
|
310
422
|
|
423
|
+
@synchronicity.classproperty
|
424
|
+
def objects(cls) -> DictManager: ...
|
311
425
|
@property
|
312
426
|
def name(self) -> typing.Optional[str]: ...
|
313
427
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
modal/experimental/__init__.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright Modal Labs 2025
|
2
2
|
import os
|
3
|
+
import shlex
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from pathlib import Path
|
5
6
|
from typing import Literal, Optional, Union
|
@@ -212,6 +213,109 @@ async def raw_registry_image(
|
|
212
213
|
)
|
213
214
|
|
214
215
|
|
216
|
+
@synchronizer.create_blocking
|
217
|
+
async def notebook_base_image(*, python_version: Optional[str] = None, force_build: bool = False) -> _Image:
|
218
|
+
"""Default image used for Modal notebook kernels, with common libraries.
|
219
|
+
|
220
|
+
This can be used to bootstrap development workflows quickly. We don't
|
221
|
+
recommend using this image for production Modal Functions though, as it may
|
222
|
+
change at any time in the future.
|
223
|
+
"""
|
224
|
+
# Include several common packages, as well as kernelshim dependencies (except 'modal').
|
225
|
+
# These packages aren't pinned, so they may change over time with builds.
|
226
|
+
#
|
227
|
+
# We plan to use `--exclude-newer` in the future, with date-specific image builds.
|
228
|
+
base_image = _Image.debian_slim(python_version=python_version)
|
229
|
+
|
230
|
+
environment_packages: list[str] = [
|
231
|
+
"accelerate",
|
232
|
+
"aiohttp",
|
233
|
+
"altair",
|
234
|
+
"anthropic",
|
235
|
+
"asyncpg",
|
236
|
+
"beautifulsoup4",
|
237
|
+
"bokeh",
|
238
|
+
"boto3[crt]",
|
239
|
+
"click",
|
240
|
+
"diffusers[torch,flax]",
|
241
|
+
"dm-sonnet",
|
242
|
+
"flax",
|
243
|
+
"ftfy",
|
244
|
+
"h5py",
|
245
|
+
"urllib3",
|
246
|
+
"httpx",
|
247
|
+
"huggingface-hub",
|
248
|
+
"ipywidgets",
|
249
|
+
"jax[cuda12]",
|
250
|
+
"keras",
|
251
|
+
"matplotlib",
|
252
|
+
"nbformat",
|
253
|
+
"numba",
|
254
|
+
"numpy",
|
255
|
+
"openai",
|
256
|
+
"optax",
|
257
|
+
"pandas",
|
258
|
+
"plotly[express]",
|
259
|
+
"polars",
|
260
|
+
"psycopg2",
|
261
|
+
"requests",
|
262
|
+
"safetensors",
|
263
|
+
"scikit-image",
|
264
|
+
"scikit-learn",
|
265
|
+
"scipy",
|
266
|
+
"seaborn",
|
267
|
+
"sentencepiece",
|
268
|
+
"sqlalchemy",
|
269
|
+
"statsmodels",
|
270
|
+
"sympy",
|
271
|
+
"tabulate",
|
272
|
+
"tensorboard",
|
273
|
+
"toml",
|
274
|
+
"transformers",
|
275
|
+
"triton",
|
276
|
+
"typer",
|
277
|
+
"vega-datasets",
|
278
|
+
"watchfiles",
|
279
|
+
"websockets",
|
280
|
+
]
|
281
|
+
|
282
|
+
# Kernelshim dependencies. (see NOTEBOOK_KERNELSHIM_DEPENDENCIES)
|
283
|
+
kernelshim_packages: list[str] = [
|
284
|
+
"authlib>=1.3",
|
285
|
+
"basedpyright>=1.28",
|
286
|
+
"fastapi>=0.100",
|
287
|
+
"ipykernel>=6",
|
288
|
+
"pydantic>=2",
|
289
|
+
"pyzmq>=26",
|
290
|
+
"ruff>=0.11",
|
291
|
+
"uvicorn>=0.32",
|
292
|
+
]
|
293
|
+
|
294
|
+
commands: list[str] = [
|
295
|
+
"apt-get update",
|
296
|
+
"apt-get install -y libpq-dev pkg-config cmake git curl wget unzip zip libsqlite3-dev openssh-server",
|
297
|
+
# Install uv since it's faster than pip for installing packages.
|
298
|
+
"pip install uv",
|
299
|
+
# https://github.com/astral-sh/uv/issues/11480
|
300
|
+
"pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126",
|
301
|
+
f"uv pip install --system {shlex.join(sorted(environment_packages))}",
|
302
|
+
f"uv pip install --system {shlex.join(sorted(kernelshim_packages))}",
|
303
|
+
]
|
304
|
+
|
305
|
+
# TODO: Also install the CUDA Toolkit, so `nvcc` is available.
|
306
|
+
# https://github.com/charlesfrye/cuda-modal/blob/7fef8db12402986cf42d9c8cca8c63d1da6d7700/cuda/use_cuda.py#L158-L188
|
307
|
+
|
308
|
+
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec:
|
309
|
+
return DockerfileSpec(commands=["FROM base", *(f"RUN {cmd}" for cmd in commands)], context_files={})
|
310
|
+
|
311
|
+
return _Image._from_args(
|
312
|
+
base_images={"base": base_image},
|
313
|
+
dockerfile_function=build_dockerfile,
|
314
|
+
force_build=force_build,
|
315
|
+
_namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
316
|
+
)
|
317
|
+
|
318
|
+
|
215
319
|
@synchronizer.create_blocking
|
216
320
|
async def update_autoscaler(
|
217
321
|
obj: Union[_Function, _Obj],
|
modal/functions.pyi
CHANGED
@@ -433,7 +433,7 @@ class Function(
|
|
433
433
|
|
434
434
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
435
435
|
|
436
|
-
class __remote_spec(typing_extensions.Protocol[
|
436
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
437
437
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
438
438
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
439
439
|
...
|
@@ -442,7 +442,7 @@ class Function(
|
|
442
442
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
443
443
|
...
|
444
444
|
|
445
|
-
remote: __remote_spec[modal._functions.
|
445
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
446
446
|
|
447
447
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
448
448
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
@@ -469,7 +469,7 @@ class Function(
|
|
469
469
|
"""
|
470
470
|
...
|
471
471
|
|
472
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
472
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
473
473
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
474
474
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
475
475
|
|
@@ -493,7 +493,7 @@ class Function(
|
|
493
493
|
...
|
494
494
|
|
495
495
|
_experimental_spawn: ___experimental_spawn_spec[
|
496
|
-
modal._functions.
|
496
|
+
modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
|
497
497
|
]
|
498
498
|
|
499
499
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
@@ -502,7 +502,7 @@ class Function(
|
|
502
502
|
|
503
503
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
504
504
|
|
505
|
-
class __spawn_spec(typing_extensions.Protocol[
|
505
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
506
506
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
507
507
|
"""Calls the function with the given arguments, without waiting for the results.
|
508
508
|
|
@@ -523,7 +523,7 @@ class Function(
|
|
523
523
|
"""
|
524
524
|
...
|
525
525
|
|
526
|
-
spawn: __spawn_spec[modal._functions.
|
526
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
527
527
|
|
528
528
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
529
529
|
"""Return the inner Python object wrapped by this Modal Function."""
|
modal/queue.py
CHANGED
@@ -5,22 +5,29 @@ import warnings
|
|
5
5
|
from collections.abc import AsyncGenerator, AsyncIterator
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from datetime import datetime
|
8
|
-
from typing import Any, Optional
|
8
|
+
from typing import Any, Optional, Union
|
9
9
|
|
10
10
|
from google.protobuf.message import Message
|
11
11
|
from grpclib import GRPCError, Status
|
12
|
+
from synchronicity import classproperty
|
12
13
|
from synchronicity.async_wrap import asynccontextmanager
|
13
14
|
|
14
15
|
from modal_proto import api_pb2
|
15
16
|
|
16
|
-
from ._object import
|
17
|
+
from ._object import (
|
18
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
19
|
+
_get_environment_name,
|
20
|
+
_Object,
|
21
|
+
live_method,
|
22
|
+
live_method_gen,
|
23
|
+
)
|
17
24
|
from ._resolver import Resolver
|
18
25
|
from ._serialization import deserialize, serialize
|
19
26
|
from ._utils.async_utils import TaskContext, synchronize_api, warn_if_generator_is_not_consumed
|
20
27
|
from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
21
28
|
from ._utils.grpc_utils import retry_transient_errors
|
22
29
|
from ._utils.name_utils import check_object_name
|
23
|
-
from ._utils.time_utils import timestamp_to_localized_dt
|
30
|
+
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
24
31
|
from .client import _Client
|
25
32
|
from .exception import InvalidError, RequestSizeError
|
26
33
|
|
@@ -37,6 +44,68 @@ class QueueInfo:
|
|
37
44
|
created_by: Optional[str]
|
38
45
|
|
39
46
|
|
47
|
+
class _QueueManager:
|
48
|
+
"""Namespace with methods for managing named Queue objects."""
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
async def list(
|
52
|
+
*,
|
53
|
+
max_objects: Optional[int] = None, # Limit requests to this size
|
54
|
+
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
55
|
+
environment_name: str = "", # Uses active environment if not specified
|
56
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
57
|
+
) -> list["_Queue"]:
|
58
|
+
"""Return a list of hydrated Queue objects.
|
59
|
+
|
60
|
+
**Examples:**
|
61
|
+
|
62
|
+
```python
|
63
|
+
queues = modal.Queue.objects.list()
|
64
|
+
print([q.name for q in queues])
|
65
|
+
```
|
66
|
+
|
67
|
+
Queues will be retreived from the active environment, or another one can be specified:
|
68
|
+
|
69
|
+
```python notest
|
70
|
+
dev_queues = modal.Queue.objects.list(environment_name="dev")
|
71
|
+
```
|
72
|
+
|
73
|
+
By default, all named Queues are returned, newest to oldest. It's also possible to limit the
|
74
|
+
number of results and to filter by creation date:
|
75
|
+
|
76
|
+
```python
|
77
|
+
queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
|
78
|
+
```
|
79
|
+
|
80
|
+
"""
|
81
|
+
client = await _Client.from_env() if client is None else client
|
82
|
+
if max_objects is not None and max_objects < 0:
|
83
|
+
raise InvalidError("max_objects cannot be negative")
|
84
|
+
|
85
|
+
items: list[api_pb2.QueueListResponse.QueueInfo] = []
|
86
|
+
|
87
|
+
async def retrieve_page(created_before: float) -> bool:
|
88
|
+
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
89
|
+
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
90
|
+
req = api_pb2.QueueListRequest(environment_name=environment_name, pagination=pagination)
|
91
|
+
resp = await retry_transient_errors(client.stub.QueueList, req)
|
92
|
+
items.extend(resp.queues)
|
93
|
+
finished = (len(resp.queues) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
94
|
+
return finished
|
95
|
+
|
96
|
+
finished = await retrieve_page(as_timestamp(created_before))
|
97
|
+
while True:
|
98
|
+
if finished:
|
99
|
+
break
|
100
|
+
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
101
|
+
|
102
|
+
queues = [_Queue._new_hydrated(item.queue_id, client, item.metadata, is_another_app=True) for item in items]
|
103
|
+
return queues[:max_objects] if max_objects is not None else queues
|
104
|
+
|
105
|
+
|
106
|
+
QueueManager = synchronize_api(_QueueManager)
|
107
|
+
|
108
|
+
|
40
109
|
class _Queue(_Object, type_prefix="qu"):
|
41
110
|
"""Distributed, FIFO queue for data flow in Modal apps.
|
42
111
|
|
@@ -116,6 +185,10 @@ class _Queue(_Object, type_prefix="qu"):
|
|
116
185
|
"""mdmd:hidden"""
|
117
186
|
raise RuntimeError("Queue() is not allowed. Please use `Queue.from_name(...)` or `Queue.ephemeral()` instead.")
|
118
187
|
|
188
|
+
@classproperty
|
189
|
+
def objects(cls) -> _QueueManager:
|
190
|
+
return _QueueManager
|
191
|
+
|
119
192
|
@property
|
120
193
|
def name(self) -> Optional[str]:
|
121
194
|
return self._name
|
modal/queue.pyi
CHANGED
@@ -5,6 +5,7 @@ import modal._object
|
|
5
5
|
import modal.client
|
6
6
|
import modal.object
|
7
7
|
import modal_proto.api_pb2
|
8
|
+
import synchronicity
|
8
9
|
import synchronicity.combined_types
|
9
10
|
import typing
|
10
11
|
import typing_extensions
|
@@ -30,6 +31,115 @@ class QueueInfo:
|
|
30
31
|
"""Return self==value."""
|
31
32
|
...
|
32
33
|
|
34
|
+
class _QueueManager:
|
35
|
+
"""Namespace with methods for managing named Queue objects."""
|
36
|
+
@staticmethod
|
37
|
+
async def list(
|
38
|
+
*,
|
39
|
+
max_objects: typing.Optional[int] = None,
|
40
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
41
|
+
environment_name: str = "",
|
42
|
+
client: typing.Optional[modal.client._Client] = None,
|
43
|
+
) -> list[_Queue]:
|
44
|
+
"""Return a list of hydrated Queue objects.
|
45
|
+
|
46
|
+
**Examples:**
|
47
|
+
|
48
|
+
```python
|
49
|
+
queues = modal.Queue.objects.list()
|
50
|
+
print([q.name for q in queues])
|
51
|
+
```
|
52
|
+
|
53
|
+
Queues will be retreived from the active environment, or another one can be specified:
|
54
|
+
|
55
|
+
```python notest
|
56
|
+
dev_queues = modal.Queue.objects.list(environment_name="dev")
|
57
|
+
```
|
58
|
+
|
59
|
+
By default, all named Queues are returned, newest to oldest. It's also possible to limit the
|
60
|
+
number of results and to filter by creation date:
|
61
|
+
|
62
|
+
```python
|
63
|
+
queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
|
64
|
+
```
|
65
|
+
"""
|
66
|
+
...
|
67
|
+
|
68
|
+
class QueueManager:
|
69
|
+
"""Namespace with methods for managing named Queue objects."""
|
70
|
+
def __init__(self, /, *args, **kwargs):
|
71
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
72
|
+
...
|
73
|
+
|
74
|
+
class __list_spec(typing_extensions.Protocol):
|
75
|
+
def __call__(
|
76
|
+
self,
|
77
|
+
/,
|
78
|
+
*,
|
79
|
+
max_objects: typing.Optional[int] = None,
|
80
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
81
|
+
environment_name: str = "",
|
82
|
+
client: typing.Optional[modal.client.Client] = None,
|
83
|
+
) -> list[Queue]:
|
84
|
+
"""Return a list of hydrated Queue objects.
|
85
|
+
|
86
|
+
**Examples:**
|
87
|
+
|
88
|
+
```python
|
89
|
+
queues = modal.Queue.objects.list()
|
90
|
+
print([q.name for q in queues])
|
91
|
+
```
|
92
|
+
|
93
|
+
Queues will be retreived from the active environment, or another one can be specified:
|
94
|
+
|
95
|
+
```python notest
|
96
|
+
dev_queues = modal.Queue.objects.list(environment_name="dev")
|
97
|
+
```
|
98
|
+
|
99
|
+
By default, all named Queues are returned, newest to oldest. It's also possible to limit the
|
100
|
+
number of results and to filter by creation date:
|
101
|
+
|
102
|
+
```python
|
103
|
+
queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
|
104
|
+
```
|
105
|
+
"""
|
106
|
+
...
|
107
|
+
|
108
|
+
async def aio(
|
109
|
+
self,
|
110
|
+
/,
|
111
|
+
*,
|
112
|
+
max_objects: typing.Optional[int] = None,
|
113
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
114
|
+
environment_name: str = "",
|
115
|
+
client: typing.Optional[modal.client.Client] = None,
|
116
|
+
) -> list[Queue]:
|
117
|
+
"""Return a list of hydrated Queue objects.
|
118
|
+
|
119
|
+
**Examples:**
|
120
|
+
|
121
|
+
```python
|
122
|
+
queues = modal.Queue.objects.list()
|
123
|
+
print([q.name for q in queues])
|
124
|
+
```
|
125
|
+
|
126
|
+
Queues will be retreived from the active environment, or another one can be specified:
|
127
|
+
|
128
|
+
```python notest
|
129
|
+
dev_queues = modal.Queue.objects.list(environment_name="dev")
|
130
|
+
```
|
131
|
+
|
132
|
+
By default, all named Queues are returned, newest to oldest. It's also possible to limit the
|
133
|
+
number of results and to filter by creation date:
|
134
|
+
|
135
|
+
```python
|
136
|
+
queues = modal.Queue.objects.list(max_objects=10, created_before="2025-01-01")
|
137
|
+
```
|
138
|
+
"""
|
139
|
+
...
|
140
|
+
|
141
|
+
list: __list_spec
|
142
|
+
|
33
143
|
class _Queue(modal._object._Object):
|
34
144
|
"""Distributed, FIFO queue for data flow in Modal apps.
|
35
145
|
|
@@ -109,6 +219,8 @@ class _Queue(modal._object._Object):
|
|
109
219
|
"""mdmd:hidden"""
|
110
220
|
...
|
111
221
|
|
222
|
+
@synchronicity.classproperty
|
223
|
+
def objects(cls) -> _QueueManager: ...
|
112
224
|
@property
|
113
225
|
def name(self) -> typing.Optional[str]: ...
|
114
226
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
@@ -377,6 +489,8 @@ class Queue(modal.object.Object):
|
|
377
489
|
"""mdmd:hidden"""
|
378
490
|
...
|
379
491
|
|
492
|
+
@synchronicity.classproperty
|
493
|
+
def objects(cls) -> QueueManager: ...
|
380
494
|
@property
|
381
495
|
def name(self) -> typing.Optional[str]: ...
|
382
496
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|