modal 1.1.2.dev10__py3-none-any.whl → 1.1.2.dev12__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/_utils/time_utils.py +28 -4
- modal/cli/dict.py +6 -7
- 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/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.dev10.dist-info → modal-1.1.2.dev12.dist-info}/METADATA +2 -2
- {modal-1.1.2.dev10.dist-info → modal-1.1.2.dev12.dist-info}/RECORD +26 -26
- modal_docs/mdmd/mdmd.py +11 -1
- modal_proto/api.proto +6 -4
- modal_proto/api_pb2.py +320 -320
- modal_proto/api_pb2.pyi +10 -5
- modal_version/__init__.py +1 -1
- {modal-1.1.2.dev10.dist-info → modal-1.1.2.dev12.dist-info}/WHEEL +0 -0
- {modal-1.1.2.dev10.dist-info → modal-1.1.2.dev12.dist-info}/entry_points.txt +0 -0
- {modal-1.1.2.dev10.dist-info → modal-1.1.2.dev12.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.2.dev10.dist-info → modal-1.1.2.dev12.dist-info}/top_level.txt +0 -0
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]): ...
|
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,7 +17,7 @@ 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
22
|
from .exception import InvalidError, NotFoundError
|
22
23
|
|
@@ -35,6 +36,68 @@ 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 list(
|
44
|
+
*,
|
45
|
+
max_objects: Optional[int] = None, # Limit requests to this size
|
46
|
+
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
47
|
+
environment_name: str = "", # Uses active environment if not specified
|
48
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
49
|
+
) -> list["_Secret"]:
|
50
|
+
"""Return a list of hydrated Secret objects.
|
51
|
+
|
52
|
+
**Examples:**
|
53
|
+
|
54
|
+
```python
|
55
|
+
secrets = modal.Secret.objects.list()
|
56
|
+
print([s.name for s in secrets])
|
57
|
+
```
|
58
|
+
|
59
|
+
Secrets will be retreived from the active environment, or another one can be specified:
|
60
|
+
|
61
|
+
```python notest
|
62
|
+
dev_secrets = modal.Secret.objects.list(environment_name="dev")
|
63
|
+
```
|
64
|
+
|
65
|
+
By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
|
66
|
+
number of results and to filter by creation date:
|
67
|
+
|
68
|
+
```python
|
69
|
+
secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
|
70
|
+
```
|
71
|
+
|
72
|
+
"""
|
73
|
+
client = await _Client.from_env() if client is None else client
|
74
|
+
if max_objects is not None and max_objects < 0:
|
75
|
+
raise InvalidError("max_objects cannot be negative")
|
76
|
+
|
77
|
+
items: list[api_pb2.SecretListItem] = []
|
78
|
+
|
79
|
+
async def retrieve_page(created_before: float) -> bool:
|
80
|
+
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
81
|
+
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
82
|
+
req = api_pb2.SecretListRequest(environment_name=environment_name, pagination=pagination)
|
83
|
+
resp = await retry_transient_errors(client.stub.SecretList, req)
|
84
|
+
items.extend(resp.items)
|
85
|
+
finished = (len(resp.items) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
86
|
+
return finished
|
87
|
+
|
88
|
+
finished = await retrieve_page(as_timestamp(created_before))
|
89
|
+
while True:
|
90
|
+
if finished:
|
91
|
+
break
|
92
|
+
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
93
|
+
|
94
|
+
secrets = [_Secret._new_hydrated(item.secret_id, client, item.metadata, is_another_app=True) for item in items]
|
95
|
+
return secrets[:max_objects] if max_objects is not None else secrets
|
96
|
+
|
97
|
+
|
98
|
+
SecretManager = synchronize_api(_SecretManager)
|
99
|
+
|
100
|
+
|
38
101
|
class _Secret(_Object, type_prefix="st"):
|
39
102
|
"""Secrets provide a dictionary of environment variables for images.
|
40
103
|
|
@@ -47,6 +110,10 @@ class _Secret(_Object, type_prefix="st"):
|
|
47
110
|
|
48
111
|
_metadata: Optional[api_pb2.SecretMetadata] = None
|
49
112
|
|
113
|
+
@classproperty
|
114
|
+
def objects(cls) -> _SecretManager:
|
115
|
+
return _SecretManager
|
116
|
+
|
50
117
|
@property
|
51
118
|
def name(self) -> Optional[str]:
|
52
119
|
return self._name
|
modal/secret.pyi
CHANGED
@@ -4,6 +4,7 @@ import modal._object
|
|
4
4
|
import modal.client
|
5
5
|
import modal.object
|
6
6
|
import modal_proto.api_pb2
|
7
|
+
import synchronicity
|
7
8
|
import typing
|
8
9
|
import typing_extensions
|
9
10
|
|
@@ -28,6 +29,115 @@ class SecretInfo:
|
|
28
29
|
"""Return self==value."""
|
29
30
|
...
|
30
31
|
|
32
|
+
class _SecretManager:
|
33
|
+
"""Namespace with methods for managing named Secret objects."""
|
34
|
+
@staticmethod
|
35
|
+
async def list(
|
36
|
+
*,
|
37
|
+
max_objects: typing.Optional[int] = None,
|
38
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
39
|
+
environment_name: str = "",
|
40
|
+
client: typing.Optional[modal.client._Client] = None,
|
41
|
+
) -> list[_Secret]:
|
42
|
+
"""Return a list of hydrated Secret objects.
|
43
|
+
|
44
|
+
**Examples:**
|
45
|
+
|
46
|
+
```python
|
47
|
+
secrets = modal.Secret.objects.list()
|
48
|
+
print([s.name for s in secrets])
|
49
|
+
```
|
50
|
+
|
51
|
+
Secrets will be retreived from the active environment, or another one can be specified:
|
52
|
+
|
53
|
+
```python notest
|
54
|
+
dev_secrets = modal.Secret.objects.list(environment_name="dev")
|
55
|
+
```
|
56
|
+
|
57
|
+
By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
|
58
|
+
number of results and to filter by creation date:
|
59
|
+
|
60
|
+
```python
|
61
|
+
secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
|
62
|
+
```
|
63
|
+
"""
|
64
|
+
...
|
65
|
+
|
66
|
+
class SecretManager:
|
67
|
+
"""Namespace with methods for managing named Secret objects."""
|
68
|
+
def __init__(self, /, *args, **kwargs):
|
69
|
+
"""Initialize self. See help(type(self)) for accurate signature."""
|
70
|
+
...
|
71
|
+
|
72
|
+
class __list_spec(typing_extensions.Protocol):
|
73
|
+
def __call__(
|
74
|
+
self,
|
75
|
+
/,
|
76
|
+
*,
|
77
|
+
max_objects: typing.Optional[int] = None,
|
78
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
79
|
+
environment_name: str = "",
|
80
|
+
client: typing.Optional[modal.client.Client] = None,
|
81
|
+
) -> list[Secret]:
|
82
|
+
"""Return a list of hydrated Secret objects.
|
83
|
+
|
84
|
+
**Examples:**
|
85
|
+
|
86
|
+
```python
|
87
|
+
secrets = modal.Secret.objects.list()
|
88
|
+
print([s.name for s in secrets])
|
89
|
+
```
|
90
|
+
|
91
|
+
Secrets will be retreived from the active environment, or another one can be specified:
|
92
|
+
|
93
|
+
```python notest
|
94
|
+
dev_secrets = modal.Secret.objects.list(environment_name="dev")
|
95
|
+
```
|
96
|
+
|
97
|
+
By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
|
98
|
+
number of results and to filter by creation date:
|
99
|
+
|
100
|
+
```python
|
101
|
+
secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
|
102
|
+
```
|
103
|
+
"""
|
104
|
+
...
|
105
|
+
|
106
|
+
async def aio(
|
107
|
+
self,
|
108
|
+
/,
|
109
|
+
*,
|
110
|
+
max_objects: typing.Optional[int] = None,
|
111
|
+
created_before: typing.Union[datetime.datetime, str, None] = None,
|
112
|
+
environment_name: str = "",
|
113
|
+
client: typing.Optional[modal.client.Client] = None,
|
114
|
+
) -> list[Secret]:
|
115
|
+
"""Return a list of hydrated Secret objects.
|
116
|
+
|
117
|
+
**Examples:**
|
118
|
+
|
119
|
+
```python
|
120
|
+
secrets = modal.Secret.objects.list()
|
121
|
+
print([s.name for s in secrets])
|
122
|
+
```
|
123
|
+
|
124
|
+
Secrets will be retreived from the active environment, or another one can be specified:
|
125
|
+
|
126
|
+
```python notest
|
127
|
+
dev_secrets = modal.Secret.objects.list(environment_name="dev")
|
128
|
+
```
|
129
|
+
|
130
|
+
By default, all named Secrets are returned, newest to oldest. It's also possible to limit the
|
131
|
+
number of results and to filter by creation date:
|
132
|
+
|
133
|
+
```python
|
134
|
+
secrets = modal.Secret.objects.list(max_objects=10, created_before="2025-01-01")
|
135
|
+
```
|
136
|
+
"""
|
137
|
+
...
|
138
|
+
|
139
|
+
list: __list_spec
|
140
|
+
|
31
141
|
class _Secret(modal._object._Object):
|
32
142
|
"""Secrets provide a dictionary of environment variables for images.
|
33
143
|
|
@@ -40,6 +150,8 @@ class _Secret(modal._object._Object):
|
|
40
150
|
|
41
151
|
_metadata: typing.Optional[modal_proto.api_pb2.SecretMetadata]
|
42
152
|
|
153
|
+
@synchronicity.classproperty
|
154
|
+
def objects(cls) -> _SecretManager: ...
|
43
155
|
@property
|
44
156
|
def name(self) -> typing.Optional[str]: ...
|
45
157
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
@@ -157,6 +269,8 @@ class Secret(modal.object.Object):
|
|
157
269
|
"""mdmd:hidden"""
|
158
270
|
...
|
159
271
|
|
272
|
+
@synchronicity.classproperty
|
273
|
+
def objects(cls) -> SecretManager: ...
|
160
274
|
@property
|
161
275
|
def name(self) -> typing.Optional[str]: ...
|
162
276
|
def _hydrate_metadata(self, metadata: typing.Optional[google.protobuf.message.Message]): ...
|
modal/volume.py
CHANGED
@@ -25,6 +25,7 @@ from typing import (
|
|
25
25
|
|
26
26
|
from google.protobuf.message import Message
|
27
27
|
from grpclib import GRPCError, Status
|
28
|
+
from synchronicity import classproperty
|
28
29
|
from synchronicity.async_wrap import asynccontextmanager
|
29
30
|
|
30
31
|
import modal.exception
|
@@ -32,7 +33,13 @@ import modal_proto.api_pb2
|
|
32
33
|
from modal.exception import InvalidError, VolumeUploadTimeoutError
|
33
34
|
from modal_proto import api_pb2
|
34
35
|
|
35
|
-
from ._object import
|
36
|
+
from ._object import (
|
37
|
+
EPHEMERAL_OBJECT_HEARTBEAT_SLEEP,
|
38
|
+
_get_environment_name,
|
39
|
+
_Object,
|
40
|
+
live_method,
|
41
|
+
live_method_gen,
|
42
|
+
)
|
36
43
|
from ._resolver import Resolver
|
37
44
|
from ._utils.async_utils import (
|
38
45
|
TaskContext,
|
@@ -55,7 +62,7 @@ from ._utils.deprecation import deprecation_warning, warn_if_passing_namespace
|
|
55
62
|
from ._utils.grpc_utils import retry_transient_errors
|
56
63
|
from ._utils.http_utils import ClientSessionRegistry
|
57
64
|
from ._utils.name_utils import check_object_name
|
58
|
-
from ._utils.time_utils import timestamp_to_localized_dt
|
65
|
+
from ._utils.time_utils import as_timestamp, timestamp_to_localized_dt
|
59
66
|
from .client import _Client
|
60
67
|
from .config import logger
|
61
68
|
|
@@ -106,6 +113,68 @@ class VolumeInfo:
|
|
106
113
|
created_by: Optional[str]
|
107
114
|
|
108
115
|
|
116
|
+
class _VolumeManager:
|
117
|
+
"""Namespace with methods for managing named Volume objects."""
|
118
|
+
|
119
|
+
@staticmethod
|
120
|
+
async def list(
|
121
|
+
*,
|
122
|
+
max_objects: Optional[int] = None, # Limit requests to this size
|
123
|
+
created_before: Optional[Union[datetime, str]] = None, # Limit based on creation date
|
124
|
+
environment_name: str = "", # Uses active environment if not specified
|
125
|
+
client: Optional[_Client] = None, # Optional client with Modal credentials
|
126
|
+
) -> list["_Volume"]:
|
127
|
+
"""Return a list of hydrated Volume objects.
|
128
|
+
|
129
|
+
**Examples:**
|
130
|
+
|
131
|
+
```python
|
132
|
+
volumes = modal.Volume.objects.list()
|
133
|
+
print([v.name for v in volumes])
|
134
|
+
```
|
135
|
+
|
136
|
+
Volumes will be retreived from the active environment, or another one can be specified:
|
137
|
+
|
138
|
+
```python notest
|
139
|
+
dev_volumes = modal.Volume.objects.list(environment_name="dev")
|
140
|
+
```
|
141
|
+
|
142
|
+
By default, all named Volumes are returned, newest to oldest. It's also possible to limit the
|
143
|
+
number of results and to filter by creation date:
|
144
|
+
|
145
|
+
```python
|
146
|
+
volumes = modal.Volume.objects.list(max_objects=10, created_before="2025-01-01")
|
147
|
+
```
|
148
|
+
|
149
|
+
"""
|
150
|
+
client = await _Client.from_env() if client is None else client
|
151
|
+
if max_objects is not None and max_objects < 0:
|
152
|
+
raise InvalidError("max_objects cannot be negative")
|
153
|
+
|
154
|
+
items: list[api_pb2.VolumeListItem] = []
|
155
|
+
|
156
|
+
async def retrieve_page(created_before: float) -> bool:
|
157
|
+
max_page_size = 100 if max_objects is None else min(100, max_objects - len(items))
|
158
|
+
pagination = api_pb2.ListPagination(max_objects=max_page_size, created_before=created_before)
|
159
|
+
req = api_pb2.VolumeListRequest(environment_name=environment_name, pagination=pagination)
|
160
|
+
resp = await retry_transient_errors(client.stub.VolumeList, req)
|
161
|
+
items.extend(resp.items)
|
162
|
+
finished = (len(resp.items) < max_page_size) or (max_objects is not None and len(items) >= max_objects)
|
163
|
+
return finished
|
164
|
+
|
165
|
+
finished = await retrieve_page(as_timestamp(created_before))
|
166
|
+
while True:
|
167
|
+
if finished:
|
168
|
+
break
|
169
|
+
finished = await retrieve_page(items[-1].metadata.creation_info.created_at)
|
170
|
+
|
171
|
+
volumes = [_Volume._new_hydrated(item.volume_id, client, item.metadata, is_another_app=True) for item in items]
|
172
|
+
return volumes[:max_objects] if max_objects is not None else volumes
|
173
|
+
|
174
|
+
|
175
|
+
VolumeManager = synchronize_api(_VolumeManager)
|
176
|
+
|
177
|
+
|
109
178
|
class _Volume(_Object, type_prefix="vo"):
|
110
179
|
"""A writeable volume that can be used to share files between one or more Modal functions.
|
111
180
|
|
@@ -152,6 +221,14 @@ class _Volume(_Object, type_prefix="vo"):
|
|
152
221
|
_metadata: "typing.Optional[api_pb2.VolumeMetadata]"
|
153
222
|
_read_only: bool = False
|
154
223
|
|
224
|
+
@classproperty
|
225
|
+
def objects(cls) -> _VolumeManager:
|
226
|
+
return _VolumeManager
|
227
|
+
|
228
|
+
@property
|
229
|
+
def name(self) -> Optional[str]:
|
230
|
+
return self._name
|
231
|
+
|
155
232
|
def read_only(self) -> "_Volume":
|
156
233
|
"""Configure Volume to mount as read-only.
|
157
234
|
|
@@ -181,10 +258,6 @@ class _Volume(_Object, type_prefix="vo"):
|
|
181
258
|
obj = _Volume._from_loader(_load, "Volume()", hydrate_lazily=True, deps=lambda: [self])
|
182
259
|
return obj
|
183
260
|
|
184
|
-
@property
|
185
|
-
def name(self) -> Optional[str]:
|
186
|
-
return self._name
|
187
|
-
|
188
261
|
def _hydrate_metadata(self, metadata: Optional[Message]):
|
189
262
|
if metadata:
|
190
263
|
assert isinstance(metadata, api_pb2.VolumeMetadata)
|