dara-core 1.20.0__py3-none-any.whl → 1.20.1a1__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 +0 -3
- dara/core/actions.py +2 -1
- dara/core/auth/basic.py +16 -22
- 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 +64 -22
- dara/core/cli.py +7 -8
- dara/core/configuration.py +2 -5
- dara/core/css.py +2 -1
- dara/core/data_utils.py +19 -18
- dara/core/defaults.py +7 -6
- dara/core/definitions.py +19 -50
- dara/core/http.py +3 -7
- dara/core/interactivity/__init__.py +0 -6
- dara/core/interactivity/actions.py +50 -52
- dara/core/interactivity/any_data_variable.py +134 -7
- dara/core/interactivity/any_variable.py +8 -5
- dara/core/interactivity/data_variable.py +266 -8
- dara/core/interactivity/derived_data_variable.py +290 -7
- dara/core/interactivity/derived_variable.py +176 -416
- dara/core/interactivity/filtering.py +27 -46
- dara/core/interactivity/loop_variable.py +2 -2
- dara/core/interactivity/non_data_variable.py +68 -5
- dara/core/interactivity/plain_variable.py +15 -89
- dara/core/interactivity/switch_variable.py +19 -19
- dara/core/interactivity/url_variable.py +90 -10
- dara/core/internal/cache_store/base_impl.py +1 -2
- dara/core/internal/cache_store/cache_store.py +25 -22
- dara/core/internal/cache_store/keep_all.py +1 -4
- dara/core/internal/cache_store/lru.py +1 -5
- dara/core/internal/cache_store/ttl.py +1 -4
- dara/core/internal/cgroup.py +1 -1
- dara/core/internal/dependency_resolution.py +66 -60
- dara/core/internal/devtools.py +5 -12
- dara/core/internal/download.py +4 -13
- dara/core/internal/encoder_registry.py +7 -7
- dara/core/internal/execute_action.py +13 -13
- dara/core/internal/hashing.py +3 -1
- dara/core/internal/import_discovery.py +4 -3
- dara/core/internal/normalization.py +18 -9
- dara/core/internal/pandas_utils.py +5 -107
- dara/core/internal/pool/definitions.py +1 -1
- dara/core/internal/pool/task_pool.py +16 -25
- dara/core/internal/pool/utils.py +18 -21
- dara/core/internal/pool/worker.py +2 -3
- dara/core/internal/port_utils.py +1 -1
- dara/core/internal/registries.py +6 -12
- dara/core/internal/registry.py +2 -4
- dara/core/internal/registry_lookup.py +5 -11
- dara/core/internal/routing.py +145 -109
- dara/core/internal/scheduler.py +8 -13
- dara/core/internal/settings.py +2 -2
- dara/core/internal/store.py +29 -2
- dara/core/internal/tasks.py +195 -379
- dara/core/internal/utils.py +13 -36
- dara/core/internal/websocket.py +20 -21
- dara/core/js_tooling/js_utils.py +26 -28
- dara/core/js_tooling/templates/vite.config.template.ts +3 -12
- dara/core/logging.py +12 -13
- dara/core/main.py +11 -14
- dara/core/metrics/cache.py +1 -1
- dara/core/metrics/utils.py +3 -3
- dara/core/persistence.py +5 -27
- dara/core/umd/dara.core.umd.js +55428 -59098
- dara/core/visual/components/__init__.py +2 -2
- dara/core/visual/components/fallback.py +4 -30
- dara/core/visual/components/for_cmp.py +1 -4
- dara/core/visual/css/__init__.py +31 -30
- dara/core/visual/dynamic_component.py +28 -31
- dara/core/visual/progress_updater.py +3 -4
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/METADATA +11 -12
- dara_core-1.20.1a1.dist-info/RECORD +114 -0
- dara/core/interactivity/client_variable.py +0 -71
- dara/core/interactivity/server_variable.py +0 -325
- dara/core/interactivity/state_variable.py +0 -69
- dara/core/interactivity/tabular_variable.py +0 -94
- dara/core/internal/multi_resource_lock.py +0 -70
- dara_core-1.20.0.dist-info/RECORD +0 -119
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/LICENSE +0 -0
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/WHEEL +0 -0
- {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/entry_points.txt +0 -0
|
@@ -20,11 +20,10 @@ 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
|
|
24
24
|
|
|
25
25
|
import numpy
|
|
26
|
-
from pandas import DataFrame, Series
|
|
27
|
-
from pydantic import field_validator # noqa: F401
|
|
26
|
+
from pandas import DataFrame, Series # pylint: disable=unused-import
|
|
28
27
|
|
|
29
28
|
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
30
29
|
from dara.core.logging import dev_logger
|
|
@@ -32,13 +31,6 @@ from dara.core.logging import dev_logger
|
|
|
32
31
|
COLUMN_PREFIX_REGEX = re.compile(r'__(?:col|index)__\d+__')
|
|
33
32
|
|
|
34
33
|
|
|
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
|
-
|
|
42
34
|
class Pagination(BaseModel):
|
|
43
35
|
"""
|
|
44
36
|
Model representing pagination to be applied to a dataset.
|
|
@@ -52,13 +44,6 @@ class Pagination(BaseModel):
|
|
|
52
44
|
orderBy: Optional[str] = None
|
|
53
45
|
index: Optional[str] = None
|
|
54
46
|
|
|
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
|
-
|
|
62
47
|
|
|
63
48
|
class QueryCombinator(str, Enum):
|
|
64
49
|
AND = 'AND'
|
|
@@ -172,11 +157,11 @@ def infer_column_type(series: Series) -> ColumnType:
|
|
|
172
157
|
return ColumnType.CATEGORICAL
|
|
173
158
|
|
|
174
159
|
|
|
175
|
-
def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, value: Any) -> Optional[Series]:
|
|
160
|
+
def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, value: Any) -> Optional['Series[bool]']:
|
|
176
161
|
"""
|
|
177
162
|
Convert a single filter to a Series[bool] for filtering
|
|
178
163
|
"""
|
|
179
|
-
series =
|
|
164
|
+
series = data[column]
|
|
180
165
|
|
|
181
166
|
# Contains is a special case, we always treat the column as a string
|
|
182
167
|
if operator == QueryOperator.CONTAINS:
|
|
@@ -190,15 +175,19 @@ def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, val
|
|
|
190
175
|
return series.isin(value)
|
|
191
176
|
# Converts date passed from frontend to the right format to compare with pandas
|
|
192
177
|
if col_type == ColumnType.DATETIME:
|
|
193
|
-
|
|
178
|
+
if isinstance(value, List):
|
|
179
|
+
value = [parseISO(value[0]), parseISO(value[1])]
|
|
180
|
+
else:
|
|
181
|
+
value = parseISO(value)
|
|
194
182
|
elif col_type == ColumnType.CATEGORICAL:
|
|
195
183
|
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]
|
|
200
184
|
else:
|
|
201
|
-
|
|
185
|
+
if isinstance(value, List):
|
|
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)
|
|
202
191
|
|
|
203
192
|
if operator == QueryOperator.GT:
|
|
204
193
|
return series > value
|
|
@@ -219,14 +208,12 @@ def _filter_to_series(data: DataFrame, column: str, operator: QueryOperator, val
|
|
|
219
208
|
return None
|
|
220
209
|
|
|
221
210
|
|
|
222
|
-
def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> Optional[Series]:
|
|
211
|
+
def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> 'Optional[Series[bool]]':
|
|
223
212
|
"""
|
|
224
213
|
Resolve a FilterQuery to a Series[bool] for filtering. Strips the internal column index from the query.
|
|
225
214
|
"""
|
|
226
215
|
if isinstance(query, ValueQuery):
|
|
227
|
-
return _filter_to_series(
|
|
228
|
-
data, re.sub(COLUMN_PREFIX_REGEX, repl='', string=query.column, count=1), query.operator, query.value
|
|
229
|
-
)
|
|
216
|
+
return _filter_to_series(data, re.sub(COLUMN_PREFIX_REGEX, '', query.column, 1), query.operator, query.value)
|
|
230
217
|
elif isinstance(query, ClauseQuery):
|
|
231
218
|
filters = None
|
|
232
219
|
|
|
@@ -235,9 +222,15 @@ def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> Optional[Serie
|
|
|
235
222
|
|
|
236
223
|
if resolved_clause is not None:
|
|
237
224
|
if query.combinator == QueryCombinator.AND:
|
|
238
|
-
|
|
225
|
+
if filters is None:
|
|
226
|
+
filters = resolved_clause
|
|
227
|
+
else:
|
|
228
|
+
filters = filters & resolved_clause
|
|
239
229
|
elif query.combinator == QueryCombinator.OR:
|
|
240
|
-
|
|
230
|
+
if filters is None:
|
|
231
|
+
filters = resolved_clause
|
|
232
|
+
else:
|
|
233
|
+
filters = filters | resolved_clause
|
|
241
234
|
else:
|
|
242
235
|
raise ValueError(f'Unknown combinator {query.combinator}')
|
|
243
236
|
|
|
@@ -246,18 +239,6 @@ def _resolve_filter_query(data: DataFrame, query: FilterQuery) -> Optional[Serie
|
|
|
246
239
|
raise ValueError(f'Unknown query type {type(query)}')
|
|
247
240
|
|
|
248
241
|
|
|
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
|
-
|
|
261
242
|
def apply_filters(
|
|
262
243
|
data: Optional[DataFrame], filters: Optional[FilterQuery] = None, pagination: Optional[Pagination] = None
|
|
263
244
|
) -> Tuple[Optional[DataFrame], int]:
|
|
@@ -281,7 +262,7 @@ def apply_filters(
|
|
|
281
262
|
if pagination is not None:
|
|
282
263
|
# ON FETCHING SPECIFIC ROW
|
|
283
264
|
if pagination.index is not None:
|
|
284
|
-
return
|
|
265
|
+
return data[int(pagination.index) : int(pagination.index) + 1], total_count
|
|
285
266
|
|
|
286
267
|
# SORT
|
|
287
268
|
if pagination.orderBy is not None:
|
|
@@ -297,7 +278,7 @@ def apply_filters(
|
|
|
297
278
|
if col == 'index':
|
|
298
279
|
new_data = new_data.sort_index(ascending=ascending, inplace=False)
|
|
299
280
|
else:
|
|
300
|
-
new_data = new_data.sort_values(by=col, ascending=ascending, inplace=False)
|
|
281
|
+
new_data = new_data.sort_values(by=col, ascending=ascending, inplace=False)
|
|
301
282
|
|
|
302
283
|
# PAGINATE
|
|
303
284
|
start_index = pagination.offset if pagination.offset is not None else 0
|
|
@@ -305,4 +286,4 @@ def apply_filters(
|
|
|
305
286
|
|
|
306
287
|
new_data = new_data.iloc[start_index:stop_index]
|
|
307
288
|
|
|
308
|
-
return
|
|
289
|
+
return 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 .non_data_variable import NonDataVariable
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class LoopVariable(
|
|
8
|
+
class LoopVariable(NonDataVariable):
|
|
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,8 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2023 Impulse Innovations Limited
|
|
2
3
|
|
|
3
|
-
from .client_variable import * # noqa: F403
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
8
16
|
"""
|
|
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,10 +17,9 @@ limitations under the License.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import warnings
|
|
21
20
|
from contextlib import contextmanager
|
|
22
21
|
from contextvars import ContextVar
|
|
23
|
-
from typing import Any, Callable,
|
|
22
|
+
from typing import Any, Callable, Generic, List, Optional, TypeVar
|
|
24
23
|
|
|
25
24
|
from fastapi.encoders import jsonable_encoder
|
|
26
25
|
from pydantic import (
|
|
@@ -31,25 +30,26 @@ from pydantic import (
|
|
|
31
30
|
model_serializer,
|
|
32
31
|
)
|
|
33
32
|
|
|
34
|
-
from dara.core.interactivity.
|
|
33
|
+
from dara.core.interactivity.derived_data_variable import DerivedDataVariable
|
|
35
34
|
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
|
|
38
|
+
from dara.core.persistence import 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
|
-
|
|
46
45
|
# TODO: once Python supports a default value for a generic type properly we can make PersistenceStoreType a second generic param
|
|
47
|
-
class Variable(
|
|
46
|
+
class Variable(NonDataVariable, Generic[VariableType]):
|
|
48
47
|
"""
|
|
49
48
|
A Variable represents a dynamic value in the system that can be read and written to by components and actions
|
|
50
49
|
"""
|
|
51
50
|
|
|
52
51
|
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,7 +62,6 @@ class Variable(ClientVariable, Generic[VariableType]):
|
|
|
62
62
|
uid: Optional[str] = None,
|
|
63
63
|
store: Optional[PersistenceStoreType_co] = None,
|
|
64
64
|
nested: Optional[List[str]] = None,
|
|
65
|
-
**kwargs,
|
|
66
65
|
):
|
|
67
66
|
"""
|
|
68
67
|
A Variable represents a dynamic value in the system that can be read and written to by components and actions
|
|
@@ -74,32 +73,18 @@ class Variable(ClientVariable, Generic[VariableType]):
|
|
|
74
73
|
"""
|
|
75
74
|
if nested is None:
|
|
76
75
|
nested = []
|
|
77
|
-
kwargs = {
|
|
78
|
-
'default': default,
|
|
79
|
-
'uid': uid,
|
|
80
|
-
'store': store,
|
|
81
|
-
'nested': nested,
|
|
82
|
-
**kwargs,
|
|
83
|
-
}
|
|
76
|
+
kwargs = {'default': default, 'persist_value': persist_value, 'uid': uid, 'store': store, 'nested': nested}
|
|
84
77
|
|
|
85
78
|
# If an override is active, run the kwargs through it
|
|
86
79
|
override = VARIABLE_INIT_OVERRIDE.get()
|
|
87
80
|
if override is not None:
|
|
88
81
|
kwargs = override(kwargs)
|
|
89
82
|
|
|
90
|
-
if kwargs.get('store') is not None and persist_value:
|
|
83
|
+
if kwargs.get('store') is not None and kwargs.get('persist_value'):
|
|
91
84
|
# TODO: this is temporary, persist_value will eventually become a type of store
|
|
92
85
|
raise ValueError('Cannot provide a Variable with both a store and persist_value set to True')
|
|
93
86
|
|
|
94
|
-
|
|
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
|
|
87
|
+
super().__init__(**kwargs) # type: ignore
|
|
103
88
|
|
|
104
89
|
if self.store:
|
|
105
90
|
call_async(self.store.init, self)
|
|
@@ -130,7 +115,7 @@ class Variable(ClientVariable, Generic[VariableType]):
|
|
|
130
115
|
Override the init function of all Variables created within the context of this function.
|
|
131
116
|
|
|
132
117
|
```python
|
|
133
|
-
with Variable.init_override(lambda kwargs: {**kwargs, '
|
|
118
|
+
with Variable.init_override(lambda kwargs: {**kwargs, 'persist_value': True}):
|
|
134
119
|
var = Variable()
|
|
135
120
|
```
|
|
136
121
|
|
|
@@ -267,71 +252,12 @@ class Variable(ClientVariable, Generic[VariableType]):
|
|
|
267
252
|
|
|
268
253
|
:param default: the initial value for the variable, defaults to None
|
|
269
254
|
"""
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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.
|
|
316
|
-
|
|
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.
|
|
255
|
+
if isinstance(other, DerivedDataVariable):
|
|
256
|
+
raise ValueError(
|
|
257
|
+
'Cannot create a Variable from a DerivedDataVariable, only standard DerivedVariables are allowed'
|
|
258
|
+
)
|
|
329
259
|
|
|
330
|
-
|
|
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()
|
|
260
|
+
return cls(default=other) # type: ignore
|
|
335
261
|
|
|
336
262
|
@model_serializer(mode='wrap')
|
|
337
263
|
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
@@ -21,11 +21,11 @@ from typing import Any, Dict, Optional, Union
|
|
|
21
21
|
|
|
22
22
|
from pydantic import SerializerFunctionWrapHandler, field_validator, model_serializer
|
|
23
23
|
|
|
24
|
-
from dara.core.interactivity.client_variable import ClientVariable
|
|
25
24
|
from dara.core.interactivity.condition import Condition
|
|
25
|
+
from dara.core.interactivity.non_data_variable import NonDataVariable
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class SwitchVariable(
|
|
28
|
+
class SwitchVariable(NonDataVariable):
|
|
29
29
|
"""
|
|
30
30
|
A SwitchVariable represents a conditional value that switches between
|
|
31
31
|
different values based on a condition or variable value.
|
|
@@ -216,21 +216,21 @@ class SwitchVariable(ClientVariable):
|
|
|
216
216
|
Key Serialization:
|
|
217
217
|
When using mappings with SwitchVariable, be aware that JavaScript object keys
|
|
218
218
|
are always strings. The system automatically converts lookup keys to strings:
|
|
219
|
-
- Python:
|
|
220
|
-
- JavaScript:
|
|
219
|
+
- Python: {True: 'admin', False: 'user'}
|
|
220
|
+
- JavaScript: {"true": "admin", "false": "user"}
|
|
221
221
|
- Boolean values are converted to lowercase strings ("true"/"false")
|
|
222
222
|
- Other values use standard string conversion to match JavaScript's String() behavior
|
|
223
223
|
"""
|
|
224
224
|
|
|
225
|
-
value: Optional[Union[Condition,
|
|
225
|
+
value: Optional[Union[Condition, NonDataVariable, Any]] = None
|
|
226
226
|
# must be typed as any, otherwise pydantic is trying to instantiate the variables incorrectly
|
|
227
227
|
value_map: Optional[Any] = None
|
|
228
228
|
default: Optional[Any] = None
|
|
229
229
|
|
|
230
230
|
def __init__(
|
|
231
231
|
self,
|
|
232
|
-
value: Union[Condition,
|
|
233
|
-
value_map: Dict[Any, Any] |
|
|
232
|
+
value: Union[Condition, NonDataVariable, Any],
|
|
233
|
+
value_map: Dict[Any, Any] | NonDataVariable,
|
|
234
234
|
default: Optional[Any] = None,
|
|
235
235
|
uid: Optional[str] = None,
|
|
236
236
|
):
|
|
@@ -253,28 +253,28 @@ class SwitchVariable(ClientVariable):
|
|
|
253
253
|
@classmethod
|
|
254
254
|
def validate_value_map(cls, v):
|
|
255
255
|
"""
|
|
256
|
-
Validate that value_map is either a dict or a
|
|
256
|
+
Validate that value_map is either a dict or a NonDataVariable.
|
|
257
257
|
|
|
258
258
|
:param v: The value to validate
|
|
259
259
|
:return: The validated value
|
|
260
|
-
:raises ValueError: If value_map is not a dict or
|
|
260
|
+
:raises ValueError: If value_map is not a dict or NonDataVariable
|
|
261
261
|
"""
|
|
262
262
|
if v is None:
|
|
263
263
|
return v
|
|
264
264
|
if isinstance(v, dict):
|
|
265
265
|
return v
|
|
266
|
-
if isinstance(v,
|
|
266
|
+
if isinstance(v, NonDataVariable):
|
|
267
267
|
return v
|
|
268
|
-
raise ValueError(f'value_map must be a dict or
|
|
268
|
+
raise ValueError(f'value_map must be a dict or NonDataVariable, got {type(v)}')
|
|
269
269
|
|
|
270
270
|
@classmethod
|
|
271
271
|
def when(
|
|
272
272
|
cls,
|
|
273
|
-
condition: Union[Condition,
|
|
274
|
-
true_value: Union[Any,
|
|
275
|
-
false_value: Union[Any,
|
|
273
|
+
condition: Union[Condition, NonDataVariable, Any],
|
|
274
|
+
true_value: Union[Any, NonDataVariable],
|
|
275
|
+
false_value: Union[Any, NonDataVariable],
|
|
276
276
|
uid: Optional[str] = None,
|
|
277
|
-
) -> SwitchVariable:
|
|
277
|
+
) -> 'SwitchVariable':
|
|
278
278
|
"""
|
|
279
279
|
Create a SwitchVariable for boolean conditions.
|
|
280
280
|
|
|
@@ -346,11 +346,11 @@ class SwitchVariable(ClientVariable):
|
|
|
346
346
|
@classmethod
|
|
347
347
|
def match(
|
|
348
348
|
cls,
|
|
349
|
-
value: Union[
|
|
350
|
-
mapping: Union[Dict[Any, Any],
|
|
351
|
-
default: Optional[Union[Any,
|
|
349
|
+
value: Union[NonDataVariable, Any],
|
|
350
|
+
mapping: Union[Dict[Any, Any], NonDataVariable],
|
|
351
|
+
default: Optional[Union[Any, NonDataVariable]] = None,
|
|
352
352
|
uid: Optional[str] = None,
|
|
353
|
-
) -> SwitchVariable:
|
|
353
|
+
) -> 'SwitchVariable':
|
|
354
354
|
"""
|
|
355
355
|
Create a SwitchVariable with a custom mapping.
|
|
356
356
|
|
|
@@ -17,21 +17,16 @@ limitations under the License.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import Optional, TypeVar
|
|
20
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
21
21
|
|
|
22
|
-
from pydantic import ConfigDict
|
|
23
|
-
from typing_extensions import deprecated
|
|
22
|
+
from pydantic import ConfigDict, SerializerFunctionWrapHandler, model_serializer
|
|
24
23
|
|
|
25
|
-
from dara.core.interactivity.
|
|
26
|
-
from dara.core.persistence import QueryParamStore
|
|
24
|
+
from dara.core.interactivity.non_data_variable import NonDataVariable
|
|
27
25
|
|
|
28
26
|
VariableType = TypeVar('VariableType')
|
|
29
27
|
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
'UrlVariable is deprecated and will be removed in a future version. Use dara.core.interactivity.plain_variable.Variable with dara.core.persistence.QueryParamStore instead'
|
|
33
|
-
)
|
|
34
|
-
class UrlVariable(Variable[VariableType]):
|
|
29
|
+
class UrlVariable(NonDataVariable, Generic[VariableType]):
|
|
35
30
|
"""
|
|
36
31
|
A UrlVariable is very similar to a normal Variable however rather than it's state being stored in the memory of
|
|
37
32
|
the client it's value is stored in the url of page as a query parameter. This is very useful for parameterizing
|
|
@@ -53,4 +48,89 @@ class UrlVariable(Variable[VariableType]):
|
|
|
53
48
|
:param default: the initial value for the variable, defaults to None
|
|
54
49
|
:param uid: the unique identifier for this variable; if not provided a random one is generated
|
|
55
50
|
"""
|
|
56
|
-
super().__init__(default=default, uid=uid
|
|
51
|
+
super().__init__(query=query, default=default, uid=uid)
|
|
52
|
+
|
|
53
|
+
def sync(self):
|
|
54
|
+
"""
|
|
55
|
+
Create an action to synchronise the value of this UrlVariable with input value sent from the component.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
|
|
59
|
+
from dara.core import UrlVariable
|
|
60
|
+
from dara.components import Select
|
|
61
|
+
|
|
62
|
+
var = UrlVariable('first', query='num')
|
|
63
|
+
another_var = UrlVariable('second', query='num_two')
|
|
64
|
+
|
|
65
|
+
Select(
|
|
66
|
+
value=var,
|
|
67
|
+
items=['first', 'second', 'third'],
|
|
68
|
+
onchange=another_var.sync(),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
"""
|
|
73
|
+
from dara.core.interactivity.actions import (
|
|
74
|
+
UpdateVariableImpl,
|
|
75
|
+
assert_no_context,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert_no_context('ctx.update')
|
|
79
|
+
return UpdateVariableImpl(variable=self, value=UpdateVariableImpl.INPUT)
|
|
80
|
+
|
|
81
|
+
def toggle(self):
|
|
82
|
+
"""
|
|
83
|
+
Create an action to toggle the value of this UrlVariable. Note this only works for boolean variables.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
|
|
87
|
+
from dara.core import UrlVariable
|
|
88
|
+
from dara.components import Button
|
|
89
|
+
|
|
90
|
+
var = UrlVariable(True, query='show')
|
|
91
|
+
|
|
92
|
+
Button(
|
|
93
|
+
'Toggle',
|
|
94
|
+
onclick=var.toggle(),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
"""
|
|
99
|
+
from dara.core.interactivity.actions import (
|
|
100
|
+
UpdateVariableImpl,
|
|
101
|
+
assert_no_context,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
assert_no_context('ctx.update')
|
|
105
|
+
return UpdateVariableImpl(variable=self, value=UpdateVariableImpl.TOGGLE)
|
|
106
|
+
|
|
107
|
+
def update(self, value: Any):
|
|
108
|
+
"""
|
|
109
|
+
Create an action to update the value of this UrlVariable to a provided value.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
|
|
113
|
+
from dara.core import UrlVariable
|
|
114
|
+
from dara.components import Button
|
|
115
|
+
|
|
116
|
+
show = UrlVariable(True, query='show')
|
|
117
|
+
|
|
118
|
+
Button(
|
|
119
|
+
'Hide',
|
|
120
|
+
onclick=show.update(False),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
"""
|
|
125
|
+
from dara.core.interactivity.actions import (
|
|
126
|
+
UpdateVariableImpl,
|
|
127
|
+
assert_no_context,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
assert_no_context('ctx.update')
|
|
131
|
+
return UpdateVariableImpl(variable=self, value=value)
|
|
132
|
+
|
|
133
|
+
@model_serializer(mode='wrap')
|
|
134
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
135
|
+
parent_dict = nxt(self)
|
|
136
|
+
return {**parent_dict, '__typename': 'UrlVariable', 'uid': str(parent_dict['uid'])}
|
|
@@ -19,13 +19,12 @@ class CacheStoreImpl(abc.ABC, Generic[PolicyT]):
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
@abc.abstractmethod
|
|
22
|
-
async def get(self, key: str, unpin: bool = False
|
|
22
|
+
async def get(self, key: str, unpin: bool = False) -> Any:
|
|
23
23
|
"""
|
|
24
24
|
Retrieve an entry from the cache.
|
|
25
25
|
|
|
26
26
|
:param key: The key of the entry to retrieve.
|
|
27
27
|
:param unpin: If true, the entry will be unpinned if it is pinned.
|
|
28
|
-
:param raise_for_missing: If true, an exception will be raised if the entry is not found
|
|
29
28
|
"""
|
|
30
29
|
|
|
31
30
|
@abc.abstractmethod
|