dara-core 1.20.1a1__py3-none-any.whl → 1.20.1a3__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.
- dara/core/__init__.py +3 -0
- dara/core/actions.py +1 -2
- dara/core/auth/basic.py +22 -16
- dara/core/auth/definitions.py +2 -2
- dara/core/auth/routes.py +5 -5
- dara/core/auth/utils.py +5 -5
- dara/core/base_definitions.py +22 -64
- dara/core/cli.py +8 -7
- dara/core/configuration.py +5 -2
- dara/core/css.py +1 -2
- dara/core/data_utils.py +18 -19
- dara/core/defaults.py +6 -7
- dara/core/definitions.py +50 -19
- dara/core/http.py +7 -3
- dara/core/interactivity/__init__.py +6 -0
- dara/core/interactivity/actions.py +52 -50
- dara/core/interactivity/any_data_variable.py +7 -134
- dara/core/interactivity/any_variable.py +5 -8
- dara/core/interactivity/client_variable.py +71 -0
- dara/core/interactivity/data_variable.py +8 -266
- dara/core/interactivity/derived_data_variable.py +7 -290
- dara/core/interactivity/derived_variable.py +416 -176
- dara/core/interactivity/filtering.py +46 -27
- dara/core/interactivity/loop_variable.py +2 -2
- dara/core/interactivity/non_data_variable.py +5 -68
- dara/core/interactivity/plain_variable.py +89 -15
- dara/core/interactivity/server_variable.py +325 -0
- dara/core/interactivity/state_variable.py +69 -0
- dara/core/interactivity/switch_variable.py +19 -19
- dara/core/interactivity/tabular_variable.py +94 -0
- dara/core/interactivity/url_variable.py +10 -90
- dara/core/internal/cache_store/base_impl.py +2 -1
- dara/core/internal/cache_store/cache_store.py +22 -25
- dara/core/internal/cache_store/keep_all.py +4 -1
- dara/core/internal/cache_store/lru.py +5 -1
- dara/core/internal/cache_store/ttl.py +4 -1
- dara/core/internal/cgroup.py +1 -1
- dara/core/internal/dependency_resolution.py +60 -66
- dara/core/internal/devtools.py +12 -5
- dara/core/internal/download.py +13 -4
- dara/core/internal/encoder_registry.py +7 -7
- dara/core/internal/execute_action.py +13 -13
- dara/core/internal/hashing.py +1 -3
- dara/core/internal/import_discovery.py +3 -4
- dara/core/internal/multi_resource_lock.py +70 -0
- dara/core/internal/normalization.py +9 -18
- dara/core/internal/pandas_utils.py +107 -5
- dara/core/internal/pool/definitions.py +1 -1
- dara/core/internal/pool/task_pool.py +25 -16
- dara/core/internal/pool/utils.py +21 -18
- dara/core/internal/pool/worker.py +3 -2
- dara/core/internal/port_utils.py +1 -1
- dara/core/internal/registries.py +12 -6
- dara/core/internal/registry.py +4 -2
- dara/core/internal/registry_lookup.py +11 -5
- dara/core/internal/routing.py +109 -145
- dara/core/internal/scheduler.py +13 -8
- dara/core/internal/settings.py +2 -2
- dara/core/internal/store.py +2 -29
- dara/core/internal/tasks.py +379 -195
- dara/core/internal/utils.py +36 -13
- dara/core/internal/websocket.py +21 -20
- dara/core/js_tooling/js_utils.py +28 -26
- dara/core/js_tooling/templates/vite.config.template.ts +12 -3
- dara/core/logging.py +13 -12
- dara/core/main.py +14 -11
- dara/core/metrics/cache.py +1 -1
- dara/core/metrics/utils.py +3 -3
- dara/core/persistence.py +27 -5
- dara/core/umd/dara.core.umd.js +68291 -64718
- dara/core/visual/components/__init__.py +2 -2
- dara/core/visual/components/fallback.py +30 -4
- dara/core/visual/components/for_cmp.py +4 -1
- dara/core/visual/css/__init__.py +30 -31
- dara/core/visual/dynamic_component.py +31 -28
- dara/core/visual/progress_updater.py +4 -3
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/METADATA +12 -11
- dara_core-1.20.1a3.dist-info/RECORD +119 -0
- dara_core-1.20.1a1.dist-info/RECORD +0 -114
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/LICENSE +0 -0
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/WHEEL +0 -0
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/entry_points.txt +0 -0
|
@@ -20,10 +20,11 @@ from __future__ import annotations
|
|
|
20
20
|
import re
|
|
21
21
|
from datetime import datetime, timezone
|
|
22
22
|
from enum import Enum
|
|
23
|
-
from typing import Any, List, Optional, Tuple, Union
|
|
23
|
+
from typing import Any, List, Optional, Tuple, Union, cast, overload
|
|
24
24
|
|
|
25
25
|
import numpy
|
|
26
|
-
from pandas import DataFrame, Series
|
|
26
|
+
from pandas import DataFrame, Series
|
|
27
|
+
from pydantic import field_validator # noqa: F401
|
|
27
28
|
|
|
28
29
|
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
29
30
|
from dara.core.logging import dev_logger
|
|
@@ -31,6 +32,13 @@ from dara.core.logging import dev_logger
|
|
|
31
32
|
COLUMN_PREFIX_REGEX = re.compile(r'__(?:col|index)__\d+__')
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def clean_column_name(col: str) -> str:
|
|
36
|
+
"""
|
|
37
|
+
Cleans a column name by removing the index or col prefix
|
|
38
|
+
"""
|
|
39
|
+
return re.sub(COLUMN_PREFIX_REGEX, '', col)
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
class Pagination(BaseModel):
|
|
35
43
|
"""
|
|
36
44
|
Model representing pagination to be applied to a dataset.
|
|
@@ -44,6 +52,13 @@ class Pagination(BaseModel):
|
|
|
44
52
|
orderBy: Optional[str] = None
|
|
45
53
|
index: Optional[str] = None
|
|
46
54
|
|
|
55
|
+
@field_validator('orderBy', mode='before')
|
|
56
|
+
@classmethod
|
|
57
|
+
def clean_order_by(cls, order_by):
|
|
58
|
+
if order_by is None:
|
|
59
|
+
return None
|
|
60
|
+
return clean_column_name(order_by)
|
|
61
|
+
|
|
47
62
|
|
|
48
63
|
class QueryCombinator(str, Enum):
|
|
49
64
|
AND = 'AND'
|
|
@@ -157,11 +172,11 @@ def infer_column_type(series: Series) -> ColumnType:
|
|
|
157
172
|
return ColumnType.CATEGORICAL
|
|
158
173
|
|
|
159
174
|
|
|
160
|
-
def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, value: Any) -> Optional[
|
|
175
|
+
def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, value: Any) -> Optional[Series]:
|
|
161
176
|
"""
|
|
162
177
|
Convert a single filter to a Series[bool] for filtering
|
|
163
178
|
"""
|
|
164
|
-
series = data[column]
|
|
179
|
+
series = cast(Series, data[column])
|
|
165
180
|
|
|
166
181
|
# Contains is a special case, we always treat the column as a string
|
|
167
182
|
if operator == QueryOperator.CONTAINS:
|
|
@@ -175,19 +190,15 @@ def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, val
|
|
|
175
190
|
return series.isin(value)
|
|
176
191
|
# Converts date passed from frontend to the right format to compare with pandas
|
|
177
192
|
if col_type == ColumnType.DATETIME:
|
|
178
|
-
if isinstance(value, List)
|
|
179
|
-
value = [parseISO(value[0]), parseISO(value[1])]
|
|
180
|
-
else:
|
|
181
|
-
value = parseISO(value)
|
|
193
|
+
value = [parseISO(value[0]), parseISO(value[1])] if isinstance(value, List) else parseISO(value)
|
|
182
194
|
elif col_type == ColumnType.CATEGORICAL:
|
|
183
195
|
value = str(value)
|
|
196
|
+
elif isinstance(value, List):
|
|
197
|
+
lower_bound = float(value[0]) if '.' in str(value[0]) else int(value[0])
|
|
198
|
+
upper_bound = float(value[1]) if '.' in str(value[1]) else int(value[1])
|
|
199
|
+
value = [lower_bound, upper_bound]
|
|
184
200
|
else:
|
|
185
|
-
if
|
|
186
|
-
lower_bound = float(value[0]) if '.' in str(value[0]) else int(value[0])
|
|
187
|
-
upper_bound = float(value[1]) if '.' in str(value[1]) else int(value[1])
|
|
188
|
-
value = [lower_bound, upper_bound]
|
|
189
|
-
else:
|
|
190
|
-
value = float(value) if '.' in str(value) else int(value)
|
|
201
|
+
value = float(value) if '.' in str(value) else int(value)
|
|
191
202
|
|
|
192
203
|
if operator == QueryOperator.GT:
|
|
193
204
|
return series > value
|
|
@@ -208,12 +219,14 @@ def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, val
|
|
|
208
219
|
return None
|
|
209
220
|
|
|
210
221
|
|
|
211
|
-
def _resolve_filter_query(data: DataFrame, query: FilterQuery) ->
|
|
222
|
+
def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> Optional[Series]:
|
|
212
223
|
"""
|
|
213
224
|
Resolve a FilterQuery to a Series[bool] for filtering. Strips the internal column index from the query.
|
|
214
225
|
"""
|
|
215
226
|
if isinstance(query, ValueQuery):
|
|
216
|
-
return _filter_to_series(
|
|
227
|
+
return _filter_to_series(
|
|
228
|
+
data, re.sub(COLUMN_PREFIX_REGEX, repl='', string=query.column, count=1), query.operator, query.value
|
|
229
|
+
)
|
|
217
230
|
elif isinstance(query, ClauseQuery):
|
|
218
231
|
filters = None
|
|
219
232
|
|
|
@@ -222,15 +235,9 @@ def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> 'Optional[Seri
|
|
|
222
235
|
|
|
223
236
|
if resolved_clause is not None:
|
|
224
237
|
if query.combinator == QueryCombinator.AND:
|
|
225
|
-
if filters is None
|
|
226
|
-
filters = resolved_clause
|
|
227
|
-
else:
|
|
228
|
-
filters = filters & resolved_clause
|
|
238
|
+
filters = resolved_clause if filters is None else filters & resolved_clause
|
|
229
239
|
elif query.combinator == QueryCombinator.OR:
|
|
230
|
-
if filters is None
|
|
231
|
-
filters = resolved_clause
|
|
232
|
-
else:
|
|
233
|
-
filters = filters | resolved_clause
|
|
240
|
+
filters = resolved_clause if filters is None else filters | resolved_clause
|
|
234
241
|
else:
|
|
235
242
|
raise ValueError(f'Unknown combinator {query.combinator}')
|
|
236
243
|
|
|
@@ -239,6 +246,18 @@ def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> 'Optional[Seri
|
|
|
239
246
|
raise ValueError(f'Unknown query type {type(query)}')
|
|
240
247
|
|
|
241
248
|
|
|
249
|
+
@overload
|
|
250
|
+
def apply_filters(
|
|
251
|
+
data: DataFrame, filters: Optional[FilterQuery] = None, pagination: Optional[Pagination] = None
|
|
252
|
+
) -> Tuple[DataFrame, int]: ...
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@overload
|
|
256
|
+
def apply_filters(
|
|
257
|
+
data: None, filters: Optional[FilterQuery] = None, pagination: Optional[Pagination] = None
|
|
258
|
+
) -> Tuple[None, int]: ...
|
|
259
|
+
|
|
260
|
+
|
|
242
261
|
def apply_filters(
|
|
243
262
|
data: Optional[DataFrame], filters: Optional[FilterQuery] = None, pagination: Optional[Pagination] = None
|
|
244
263
|
) -> Tuple[Optional[DataFrame], int]:
|
|
@@ -262,7 +281,7 @@ def apply_filters(
|
|
|
262
281
|
if pagination is not None:
|
|
263
282
|
# ON FETCHING SPECIFIC ROW
|
|
264
283
|
if pagination.index is not None:
|
|
265
|
-
return data[int(pagination.index) : int(pagination.index) + 1], total_count
|
|
284
|
+
return cast(DataFrame, data[int(pagination.index) : int(pagination.index) + 1]), total_count
|
|
266
285
|
|
|
267
286
|
# SORT
|
|
268
287
|
if pagination.orderBy is not None:
|
|
@@ -278,7 +297,7 @@ def apply_filters(
|
|
|
278
297
|
if col == 'index':
|
|
279
298
|
new_data = new_data.sort_index(ascending=ascending, inplace=False)
|
|
280
299
|
else:
|
|
281
|
-
new_data = new_data.sort_values(by=col, ascending=ascending, inplace=False)
|
|
300
|
+
new_data = new_data.sort_values(by=col, ascending=ascending, inplace=False) # type: ignore
|
|
282
301
|
|
|
283
302
|
# PAGINATE
|
|
284
303
|
start_index = pagination.offset if pagination.offset is not None else 0
|
|
@@ -286,4 +305,4 @@ def apply_filters(
|
|
|
286
305
|
|
|
287
306
|
new_data = new_data.iloc[start_index:stop_index]
|
|
288
307
|
|
|
289
|
-
return new_data, total_count
|
|
308
|
+
return cast(DataFrame, new_data), total_count
|
|
@@ -2,10 +2,10 @@ from typing import List, Optional
|
|
|
2
2
|
|
|
3
3
|
from pydantic import Field, SerializerFunctionWrapHandler, model_serializer
|
|
4
4
|
|
|
5
|
-
from .
|
|
5
|
+
from .client_variable import ClientVariable
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class LoopVariable(
|
|
8
|
+
class LoopVariable(ClientVariable):
|
|
9
9
|
"""
|
|
10
10
|
A LoopVariable is a type of variable that represents an item in a list.
|
|
11
11
|
It should be constructed using a parent Variable's `.list_item` property.
|
|
@@ -1,71 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Copyright 2023 Impulse Innovations Limited
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
you may not use this file except in compliance with the License.
|
|
7
|
-
You may obtain a copy of the License at
|
|
1
|
+
from typing_extensions import TypeAlias
|
|
8
2
|
|
|
9
|
-
|
|
3
|
+
from .client_variable import * # noqa: F403
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
See the License for the specific language governing permissions and
|
|
15
|
-
limitations under the License.
|
|
5
|
+
NonDataVariable: TypeAlias = ClientVariable # noqa: F405
|
|
6
|
+
"""
|
|
7
|
+
Deprecated alias for ClientVariable
|
|
16
8
|
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import abc
|
|
21
|
-
from typing import Optional
|
|
22
|
-
|
|
23
|
-
from dara.core.interactivity.any_variable import AnyVariable
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class NonDataVariable(AnyVariable, abc.ABC):
|
|
27
|
-
"""
|
|
28
|
-
NonDataVariable represents any variable that is not specifically designed to hold datasets (i.e. Variable, DerivedVariable, UrlVariable)
|
|
29
|
-
|
|
30
|
-
:param uid: the unique identifier for this variable; if not provided a random one is generated
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
uid: str
|
|
34
|
-
|
|
35
|
-
def __init__(self, uid: Optional[str] = None, **kwargs) -> None:
|
|
36
|
-
super().__init__(uid=uid, **kwargs)
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def list_item(self):
|
|
40
|
-
"""
|
|
41
|
-
Get a LoopVariable that represents the current item in the list.
|
|
42
|
-
Should only be used in conjunction with the `For` component.
|
|
43
|
-
|
|
44
|
-
Note that it is a type of a Variable so it can be used in places where a regular Variable is expected.
|
|
45
|
-
|
|
46
|
-
By default, the entire list item is used as the item.
|
|
47
|
-
|
|
48
|
-
`LoopVariable` supports nested property access using `get` or index access i.e. `[]`.
|
|
49
|
-
You can mix and match those two methods to access nested properties as they are equivalent.
|
|
50
|
-
|
|
51
|
-
```python
|
|
52
|
-
my_list = Variable(['foo', 'bar', 'baz'])
|
|
53
|
-
|
|
54
|
-
# Represents the entire item in the list
|
|
55
|
-
my_list.list_item
|
|
56
|
-
|
|
57
|
-
my_list_of_objects = Variable([
|
|
58
|
-
{'id': 1, 'name': 'John', 'data': {'city': 'London', 'country': 'UK'}},
|
|
59
|
-
{'id': 2, 'name': 'Jane', 'data': {'city': 'Paris', 'country': 'France'}},
|
|
60
|
-
])
|
|
61
|
-
|
|
62
|
-
# Represents the item 'name' property
|
|
63
|
-
my_list_of_objects.list_item['name']
|
|
64
|
-
|
|
65
|
-
# Represents the item 'data.country' property
|
|
66
|
-
my_list_of_objects.list_item.get('data')['country']
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
from .loop_variable import LoopVariable
|
|
70
|
-
|
|
71
|
-
return LoopVariable()
|
|
@@ -17,9 +17,10 @@ limitations under the License.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import warnings
|
|
20
21
|
from contextlib import contextmanager
|
|
21
22
|
from contextvars import ContextVar
|
|
22
|
-
from typing import Any, Callable, Generic, List, Optional, TypeVar
|
|
23
|
+
from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union
|
|
23
24
|
|
|
24
25
|
from fastapi.encoders import jsonable_encoder
|
|
25
26
|
from pydantic import (
|
|
@@ -30,26 +31,25 @@ from pydantic import (
|
|
|
30
31
|
model_serializer,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
|
-
from dara.core.interactivity.
|
|
34
|
+
from dara.core.interactivity.client_variable import ClientVariable
|
|
34
35
|
from dara.core.interactivity.derived_variable import DerivedVariable
|
|
35
|
-
from dara.core.interactivity.non_data_variable import NonDataVariable
|
|
36
36
|
from dara.core.internal.utils import call_async
|
|
37
37
|
from dara.core.logging import dev_logger
|
|
38
|
-
from dara.core.persistence import PersistenceStore
|
|
38
|
+
from dara.core.persistence import BackendStore, BrowserStore, PersistenceStore
|
|
39
39
|
|
|
40
40
|
VARIABLE_INIT_OVERRIDE = ContextVar[Optional[Callable[[dict], dict]]]('VARIABLE_INIT_OVERRIDE', default=None)
|
|
41
41
|
|
|
42
42
|
VariableType = TypeVar('VariableType')
|
|
43
43
|
PersistenceStoreType_co = TypeVar('PersistenceStoreType_co', bound=PersistenceStore, covariant=True)
|
|
44
44
|
|
|
45
|
+
|
|
45
46
|
# TODO: once Python supports a default value for a generic type properly we can make PersistenceStoreType a second generic param
|
|
46
|
-
class Variable(
|
|
47
|
+
class Variable(ClientVariable, Generic[VariableType]):
|
|
47
48
|
"""
|
|
48
49
|
A Variable represents a dynamic value in the system that can be read and written to by components and actions
|
|
49
50
|
"""
|
|
50
51
|
|
|
51
52
|
default: Optional[VariableType] = None
|
|
52
|
-
persist_value: bool = False
|
|
53
53
|
store: Optional[PersistenceStore] = None
|
|
54
54
|
uid: str
|
|
55
55
|
nested: List[str] = Field(default_factory=list)
|
|
@@ -62,6 +62,7 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
62
62
|
uid: Optional[str] = None,
|
|
63
63
|
store: Optional[PersistenceStoreType_co] = None,
|
|
64
64
|
nested: Optional[List[str]] = None,
|
|
65
|
+
**kwargs,
|
|
65
66
|
):
|
|
66
67
|
"""
|
|
67
68
|
A Variable represents a dynamic value in the system that can be read and written to by components and actions
|
|
@@ -73,18 +74,32 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
73
74
|
"""
|
|
74
75
|
if nested is None:
|
|
75
76
|
nested = []
|
|
76
|
-
kwargs = {
|
|
77
|
+
kwargs = {
|
|
78
|
+
'default': default,
|
|
79
|
+
'uid': uid,
|
|
80
|
+
'store': store,
|
|
81
|
+
'nested': nested,
|
|
82
|
+
**kwargs,
|
|
83
|
+
}
|
|
77
84
|
|
|
78
85
|
# If an override is active, run the kwargs through it
|
|
79
86
|
override = VARIABLE_INIT_OVERRIDE.get()
|
|
80
87
|
if override is not None:
|
|
81
88
|
kwargs = override(kwargs)
|
|
82
89
|
|
|
83
|
-
if kwargs.get('store') is not None and
|
|
90
|
+
if kwargs.get('store') is not None and persist_value:
|
|
84
91
|
# TODO: this is temporary, persist_value will eventually become a type of store
|
|
85
92
|
raise ValueError('Cannot provide a Variable with both a store and persist_value set to True')
|
|
86
93
|
|
|
87
|
-
|
|
94
|
+
if persist_value:
|
|
95
|
+
warnings.warn(
|
|
96
|
+
'`persist_value` is deprecated and will be removed in a future version. Use `store=dara.core.persistence.BrowserStore(...)` instead.',
|
|
97
|
+
DeprecationWarning,
|
|
98
|
+
stacklevel=2,
|
|
99
|
+
)
|
|
100
|
+
kwargs['store'] = BrowserStore()
|
|
101
|
+
|
|
102
|
+
super().__init__(**kwargs) # type: ignore
|
|
88
103
|
|
|
89
104
|
if self.store:
|
|
90
105
|
call_async(self.store.init, self)
|
|
@@ -115,7 +130,7 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
115
130
|
Override the init function of all Variables created within the context of this function.
|
|
116
131
|
|
|
117
132
|
```python
|
|
118
|
-
with Variable.init_override(lambda kwargs: {**kwargs, '
|
|
133
|
+
with Variable.init_override(lambda kwargs: {**kwargs, 'store': ...}):
|
|
119
134
|
var = Variable()
|
|
120
135
|
```
|
|
121
136
|
|
|
@@ -252,12 +267,71 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
252
267
|
|
|
253
268
|
:param default: the initial value for the variable, defaults to None
|
|
254
269
|
"""
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
return cls(default=other) # type: ignore
|
|
271
|
+
|
|
272
|
+
async def write(self, value: Any, notify=True, ignore_channel: Optional[str] = None):
|
|
273
|
+
"""
|
|
274
|
+
Persist a value to the variable's BackendStore.
|
|
275
|
+
Raises an error if the variable does not have a BackendStore attached.
|
|
276
|
+
|
|
277
|
+
If scope='user', the value is written for the current user so the method can only
|
|
278
|
+
be used in authenticated contexts.
|
|
279
|
+
|
|
280
|
+
:param value: value to write
|
|
281
|
+
:param notify: whether to broadcast the new value to clients
|
|
282
|
+
:param ignore_channel: if passed, ignore the specified websocket channel when broadcasting
|
|
283
|
+
"""
|
|
284
|
+
assert isinstance(self.store, BackendStore), 'This method can only be used with a BackendStore'
|
|
285
|
+
return await self.store.write(value, notify=notify, ignore_channel=ignore_channel)
|
|
286
|
+
|
|
287
|
+
async def write_partial(self, data: Union[List[Dict[str, Any]], Any], notify: bool = True):
|
|
288
|
+
"""
|
|
289
|
+
Apply partial updates to the variable's BackendStore using JSON Patch operations or automatic diffing.
|
|
290
|
+
Raises an error if the variable does not have a BackendStore attached.
|
|
291
|
+
|
|
292
|
+
If scope='user', the patches are applied for the current user so the method can only
|
|
293
|
+
be used in authenticated contexts.
|
|
294
|
+
|
|
295
|
+
:param data: Either a list of JSON patch operations (RFC 6902) or a full object to diff against current value
|
|
296
|
+
:param notify: whether to broadcast the patches to clients
|
|
297
|
+
"""
|
|
298
|
+
assert isinstance(self.store, BackendStore), 'This method can only be used with a BackendStore'
|
|
299
|
+
return await self.store.write_partial(data, notify=notify)
|
|
300
|
+
|
|
301
|
+
async def read(self):
|
|
302
|
+
"""
|
|
303
|
+
Read a value from the variable's BackendStore.
|
|
304
|
+
Raises an error if the variable does not have a BackendStore attached.
|
|
305
|
+
|
|
306
|
+
If scope='user', the value is read for the current user so the method can only
|
|
307
|
+
be used in authenticated contexts.
|
|
308
|
+
"""
|
|
309
|
+
assert isinstance(self.store, BackendStore), 'This method can only be used with a BackendStore'
|
|
310
|
+
return await self.store.read()
|
|
311
|
+
|
|
312
|
+
async def delete(self, notify=True):
|
|
313
|
+
"""
|
|
314
|
+
Delete the persisted value from the variable's BackendStore.
|
|
315
|
+
Raises an error if the variable does not have a BackendStore attached.
|
|
259
316
|
|
|
260
|
-
|
|
317
|
+
If scope='user', the value is deleted for the current user so the method can only
|
|
318
|
+
be used in authenticated contexts.
|
|
319
|
+
|
|
320
|
+
:param notify: whether to broadcast that the value was deleted to clients
|
|
321
|
+
"""
|
|
322
|
+
assert isinstance(self.store, BackendStore), 'This method can only be used with a BackendStore'
|
|
323
|
+
return await self.store.delete(notify=notify)
|
|
324
|
+
|
|
325
|
+
async def get_all(self) -> Dict[str, Any]:
|
|
326
|
+
"""
|
|
327
|
+
Get all the values from the variable's BackendStore as a dictionary of key-value pairs.
|
|
328
|
+
Raises an error if the variable does not have a BackendStore attached.
|
|
329
|
+
|
|
330
|
+
For global scope, the dictionary contains a single key-value pair `{'global': value}`.
|
|
331
|
+
For user scope, the dictionary contains a key-value pair for each user `{'user1': value1, 'user2': value2, ...}`.
|
|
332
|
+
"""
|
|
333
|
+
assert isinstance(self.store, BackendStore), 'This method can only be used with a BackendStore'
|
|
334
|
+
return await self.store.get_all()
|
|
261
335
|
|
|
262
336
|
@model_serializer(mode='wrap')
|
|
263
337
|
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|