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
|
@@ -1,138 +1,11 @@
|
|
|
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
|
|
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.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
import abc
|
|
19
|
-
import io
|
|
20
|
-
import os
|
|
21
|
-
from typing import Any, Awaitable, Callable, Literal, Optional, TypedDict, Union, cast
|
|
22
|
-
|
|
23
|
-
import pandas
|
|
24
|
-
from fastapi import UploadFile
|
|
25
|
-
from pydantic import ConfigDict
|
|
1
|
+
from typing_extensions import TypeAlias
|
|
26
2
|
|
|
27
|
-
from dara.core.base_definitions import CachedRegistryEntry, UploadResolverDef
|
|
28
3
|
from dara.core.interactivity.any_variable import AnyVariable
|
|
29
|
-
from dara.core.interactivity.filtering import FilterQuery
|
|
30
|
-
from dara.core.internal.cache_store.cache_store import CacheStore
|
|
31
|
-
from dara.core.internal.registry_lookup import RegistryLookup
|
|
32
|
-
from dara.core.internal.utils import run_user_handler
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class AnyDataVariable(AnyVariable, abc.ABC):
|
|
36
|
-
"""
|
|
37
|
-
AnyDataVariable represents any variable that is specifically designed to hold datasets (i.e. DataVariable, DerivedDataVariable)
|
|
38
|
-
|
|
39
|
-
:param uid: the unique identifier for this variable; if not provided a random one is generated
|
|
40
|
-
:param filters: a dictionary of filters to apply to the data
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
uid: str
|
|
44
|
-
filters: Optional[FilterQuery] = None
|
|
45
|
-
|
|
46
|
-
def __init__(self, uid: Optional[str] = None, **kwargs) -> None:
|
|
47
|
-
super().__init__(uid=uid, **kwargs)
|
|
48
|
-
|
|
49
|
-
def filter(self, filters: FilterQuery):
|
|
50
|
-
return self.copy(update={'filters': filters}, deep=True)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class FieldType(TypedDict):
|
|
54
|
-
name: Union[str, tuple[str, ...]]
|
|
55
|
-
type: Literal['integer', 'number', 'boolean', 'datetime', 'duration', 'any', 'str']
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class DataFrameSchema(TypedDict):
|
|
59
|
-
fields: list[FieldType]
|
|
60
|
-
primaryKey: list[str]
|
|
61
|
-
|
|
62
4
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Registry entry for DataVariable.
|
|
66
|
-
"""
|
|
5
|
+
# re-export for backwards compatibility
|
|
6
|
+
from .tabular_variable import * # noqa: F403
|
|
67
7
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
get_total_count: Callable[..., Awaitable[int]]
|
|
73
|
-
"""Handler to get the total number of rows in the data variable. Defaults to DataVariable.get_total_count for type=plain, and DerivedDataVariable.get_total_count for type=derived"""
|
|
74
|
-
|
|
75
|
-
get_schema: Callable[..., Awaitable[DataFrameSchema]]
|
|
76
|
-
"""Handler to get the schema for data variable. Defaults to DataVariable.get_schema for type=plain, and DerivedDataVariable.get_schema for type=derived"""
|
|
77
|
-
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
async def upload(data: UploadFile, data_uid: Optional[str] = None, resolver_id: Optional[str] = None):
|
|
81
|
-
"""
|
|
82
|
-
Handler for uploading data.
|
|
83
|
-
|
|
84
|
-
:param data: the file to upload
|
|
85
|
-
:param data_uid: optional uid of the data variable to upload to
|
|
86
|
-
:param resolver_id: optional id of the upload resolver to use, falls back to default handlers for csv/xlsx
|
|
87
|
-
"""
|
|
88
|
-
from dara.core.interactivity.data_variable import DataVariable
|
|
89
|
-
from dara.core.internal.registries import (
|
|
90
|
-
data_variable_registry,
|
|
91
|
-
upload_resolver_registry,
|
|
92
|
-
utils_registry,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
store: CacheStore = utils_registry.get('Store')
|
|
96
|
-
registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
|
|
97
|
-
|
|
98
|
-
if data.filename is None:
|
|
99
|
-
raise ValueError('Filename not provided')
|
|
100
|
-
|
|
101
|
-
variable = None
|
|
102
|
-
|
|
103
|
-
_name, file_type = os.path.splitext(data.filename)
|
|
104
|
-
|
|
105
|
-
if data_uid is not None:
|
|
106
|
-
try:
|
|
107
|
-
variable = await registry_mgr.get(data_variable_registry, data_uid)
|
|
108
|
-
except KeyError:
|
|
109
|
-
raise ValueError(f'Data Variable {data_uid} does not exist')
|
|
110
|
-
|
|
111
|
-
if variable.type == 'derived':
|
|
112
|
-
raise ValueError('Cannot upload data to DerivedDataVariable')
|
|
113
|
-
|
|
114
|
-
content = cast(bytes, await data.read())
|
|
115
|
-
|
|
116
|
-
resolver = None
|
|
117
|
-
|
|
118
|
-
# If Id is provided, lookup the definition from registry
|
|
119
|
-
if resolver_id is not None:
|
|
120
|
-
resolver_def: UploadResolverDef = await registry_mgr.get(upload_resolver_registry, resolver_id)
|
|
121
|
-
resolver = resolver_def.resolver
|
|
122
|
-
|
|
123
|
-
if resolver:
|
|
124
|
-
content = await run_user_handler(handler=resolver, args=(content, data.filename))
|
|
125
|
-
# If resolver is not provided, follow roughly the cl_dataset_parser logic
|
|
126
|
-
elif file_type == '.xlsx':
|
|
127
|
-
file_object_xlsx = io.BytesIO(content)
|
|
128
|
-
content = pandas.read_excel(file_object_xlsx, index_col=None)
|
|
129
|
-
content.columns = content.columns.str.replace('Unnamed: *', 'column_', regex=True) # type: ignore
|
|
130
|
-
else:
|
|
131
|
-
# default to csv
|
|
132
|
-
file_object_csv = io.StringIO(content.decode('utf-8'))
|
|
133
|
-
content = pandas.read_csv(file_object_csv, index_col=0)
|
|
134
|
-
content.columns = content.columns.str.replace('Unnamed: *', 'column_', regex=True) # type: ignore
|
|
135
|
-
|
|
136
|
-
# If a data variable is provided, update it with the new content
|
|
137
|
-
if variable:
|
|
138
|
-
DataVariable.update_value(variable, store, content)
|
|
8
|
+
AnyDataVariable: TypeAlias = AnyVariable
|
|
9
|
+
"""
|
|
10
|
+
Deprecated alias. Tabular variables are now DerivedVariable or ServerVariable
|
|
11
|
+
"""
|
|
@@ -30,9 +30,8 @@ from fastapi.encoders import jsonable_encoder
|
|
|
30
30
|
from pydantic import ConfigDict
|
|
31
31
|
|
|
32
32
|
from dara.core.auth.definitions import SESSION_ID, USER, UserData
|
|
33
|
-
from dara.core.base_definitions import BaseTask
|
|
33
|
+
from dara.core.base_definitions import BaseTask, PendingTask
|
|
34
34
|
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
35
|
-
from dara.core.base_definitions import PendingTask
|
|
36
35
|
from dara.core.interactivity.condition import Condition, Operator
|
|
37
36
|
from dara.core.internal.cache_store import CacheStore
|
|
38
37
|
from dara.core.internal.tasks import TaskManager
|
|
@@ -112,7 +111,7 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
|
|
|
112
111
|
user_identity = None
|
|
113
112
|
|
|
114
113
|
if current_user is not None:
|
|
115
|
-
user_identity = current_user.identity_id
|
|
114
|
+
user_identity = current_user.identity_id
|
|
116
115
|
elif isinstance(auth_config, BasicAuthConfig):
|
|
117
116
|
# basic auth - assume it's the single existing user
|
|
118
117
|
user_identity = list(auth_config.users.keys())[0]
|
|
@@ -204,7 +203,6 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
|
|
|
204
203
|
|
|
205
204
|
for session, channels in session_channels.items():
|
|
206
205
|
for ws in channels:
|
|
207
|
-
|
|
208
206
|
raw_result = raw_results[ws]
|
|
209
207
|
# Skip values from clients where the variable is not registered
|
|
210
208
|
if raw_result == NOT_REGISTERED:
|
|
@@ -238,12 +236,12 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
|
|
|
238
236
|
|
|
239
237
|
# If we're returning multiple values, in Jupyter environments print an explainer
|
|
240
238
|
try:
|
|
241
|
-
from IPython import get_ipython
|
|
239
|
+
from IPython import get_ipython # pyright: ignore[reportMissingImports]
|
|
242
240
|
except ImportError:
|
|
243
241
|
pass
|
|
244
242
|
else:
|
|
245
243
|
if get_ipython() is not None:
|
|
246
|
-
from IPython.display import HTML, display
|
|
244
|
+
from IPython.display import HTML, display # pyright: ignore[reportMissingImports]
|
|
247
245
|
|
|
248
246
|
display(
|
|
249
247
|
HTML(
|
|
@@ -272,7 +270,7 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
|
|
|
272
270
|
return results
|
|
273
271
|
|
|
274
272
|
|
|
275
|
-
class AnyVariable(BaseModel, abc.ABC):
|
|
273
|
+
class AnyVariable(BaseModel, abc.ABC): # noqa: PLW1641 # we override equals to create conditions, otherwise we should define hash
|
|
276
274
|
"""
|
|
277
275
|
Base class for all variables. Used for typing to specify that any variable can be provided.
|
|
278
276
|
"""
|
|
@@ -282,7 +280,6 @@ class AnyVariable(BaseModel, abc.ABC):
|
|
|
282
280
|
uid: str
|
|
283
281
|
|
|
284
282
|
def __init__(self, uid: Optional[str] = None, **kwargs) -> None:
|
|
285
|
-
|
|
286
283
|
new_uid = uid
|
|
287
284
|
if new_uid is None:
|
|
288
285
|
new_uid = str(uuid.uuid4())
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
|
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.
|
|
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 ClientVariable(AnyVariable, abc.ABC):
|
|
27
|
+
"""
|
|
28
|
+
Client represents any variable which can be ordinarily serialized to the client
|
|
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,40 +17,15 @@ limitations under the License.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import
|
|
21
|
-
from typing import Optional, Union, cast
|
|
20
|
+
from typing import Optional
|
|
22
21
|
|
|
23
|
-
from anyio.abc import TaskGroup
|
|
24
22
|
from pandas import DataFrame
|
|
25
|
-
from pandas.io.json._table_schema import build_table_schema
|
|
26
|
-
from pydantic import (
|
|
27
|
-
BaseModel,
|
|
28
|
-
ConfigDict,
|
|
29
|
-
SerializerFunctionWrapHandler,
|
|
30
|
-
model_serializer,
|
|
31
|
-
)
|
|
32
23
|
|
|
33
|
-
from dara.core.base_definitions import
|
|
34
|
-
from dara.core.interactivity.
|
|
35
|
-
AnyDataVariable,
|
|
36
|
-
DataFrameSchema,
|
|
37
|
-
DataVariableRegistryEntry,
|
|
38
|
-
)
|
|
39
|
-
from dara.core.interactivity.filtering import (
|
|
40
|
-
FilterQuery,
|
|
41
|
-
Pagination,
|
|
42
|
-
apply_filters,
|
|
43
|
-
coerce_to_filter_query,
|
|
44
|
-
)
|
|
45
|
-
from dara.core.internal.cache_store import CacheStore
|
|
46
|
-
from dara.core.internal.hashing import hash_object
|
|
47
|
-
from dara.core.internal.pandas_utils import append_index, df_convert_to_internal
|
|
48
|
-
from dara.core.internal.utils import call_async
|
|
49
|
-
from dara.core.internal.websocket import WebsocketManager
|
|
50
|
-
from dara.core.logging import eng_logger
|
|
24
|
+
from dara.core.base_definitions import Cache, CacheArgType
|
|
25
|
+
from dara.core.interactivity.server_variable import ServerVariable
|
|
51
26
|
|
|
52
27
|
|
|
53
|
-
class DataVariable(
|
|
28
|
+
class DataVariable(ServerVariable):
|
|
54
29
|
"""
|
|
55
30
|
DataVariable represents a variable that is specifically designed to hold datasets.
|
|
56
31
|
|
|
@@ -69,11 +44,6 @@ class DataVariable(AnyDataVariable):
|
|
|
69
44
|
>>> UpdateVariable(func=some_data_transformation, variable=session_data)
|
|
70
45
|
"""
|
|
71
46
|
|
|
72
|
-
uid: str
|
|
73
|
-
filters: Optional[FilterQuery] = None
|
|
74
|
-
cache: Optional[BaseCachePolicy] = None
|
|
75
|
-
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True, use_enum_values=True)
|
|
76
|
-
|
|
77
47
|
def __init__(
|
|
78
48
|
self,
|
|
79
49
|
data: Optional[DataFrame] = None,
|
|
@@ -89,239 +59,11 @@ class DataVariable(AnyDataVariable):
|
|
|
89
59
|
:param filters: a dictionary of filters to apply to the data
|
|
90
60
|
:param cache: how to cache the result; 'user' per user, 'session' per session, 'global' for all users
|
|
91
61
|
"""
|
|
92
|
-
|
|
62
|
+
cache_policy = Cache.Policy.from_arg(cache)
|
|
93
63
|
|
|
94
|
-
if data is not None and
|
|
64
|
+
if data is not None and cache_policy.cache_type is not Cache.Type.GLOBAL:
|
|
95
65
|
raise ValueError('Data cannot be cached per session or per user if provided upfront')
|
|
96
66
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
# Register the variable with the dataset
|
|
100
|
-
from dara.core.internal.registries import data_variable_registry
|
|
101
|
-
|
|
102
|
-
var_entry = DataVariableRegistryEntry(
|
|
103
|
-
cache=cache,
|
|
104
|
-
uid=str(self.uid),
|
|
105
|
-
type='plain',
|
|
106
|
-
get_data=DataVariable.get_value,
|
|
107
|
-
get_total_count=DataVariable.get_total_count,
|
|
108
|
-
get_schema=DataVariable.get_schema,
|
|
109
|
-
)
|
|
110
|
-
data_variable_registry.register(
|
|
111
|
-
str(self.uid),
|
|
112
|
-
var_entry,
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
# Put the data entry into the store if not empty (so cache='global')
|
|
116
|
-
# We don't create an entry in a different case since session key will be global anyway at this point
|
|
117
|
-
if data is not None:
|
|
118
|
-
from dara.core.internal.registries import utils_registry
|
|
119
|
-
|
|
120
|
-
store: CacheStore = utils_registry.get('Store')
|
|
121
|
-
call_async(self._update, var_entry, store, data)
|
|
122
|
-
|
|
123
|
-
@staticmethod
|
|
124
|
-
def _get_cache_key(uid: str) -> str:
|
|
125
|
-
"""
|
|
126
|
-
Get a unique cache key for the data variable.
|
|
127
|
-
|
|
128
|
-
:param uid: uid of the DataVariable
|
|
129
|
-
"""
|
|
130
|
-
return f'data-{uid}'
|
|
131
|
-
|
|
132
|
-
@staticmethod
|
|
133
|
-
def _get_schema_cache_key(uid: str) -> str:
|
|
134
|
-
"""
|
|
135
|
-
Get a unique cache key for the data variable's schema.
|
|
136
|
-
|
|
137
|
-
:param uid: uid of the DataVariable
|
|
138
|
-
"""
|
|
139
|
-
return f'schema-{uid}'
|
|
140
|
-
|
|
141
|
-
@classmethod
|
|
142
|
-
def _get_count_cache_key(cls, uid: str, filters: Optional[Union[FilterQuery, dict]]) -> str:
|
|
143
|
-
return f'{cls._get_cache_key(uid)}_{hash_object(filters)}'
|
|
144
|
-
|
|
145
|
-
@classmethod
|
|
146
|
-
async def _update(cls, var_entry: DataVariableRegistryEntry, store: CacheStore, data: Optional[DataFrame]):
|
|
147
|
-
"""
|
|
148
|
-
Internal helper which updates the data variable entry in store.
|
|
149
|
-
|
|
150
|
-
TODO: for now data is always kept in store, in the future depending on the size data might be cached on disk
|
|
151
|
-
"""
|
|
152
|
-
await store.set(var_entry, key=cls._get_cache_key(var_entry.uid), value=DataStoreEntry(data=append_index(data)))
|
|
153
|
-
|
|
154
|
-
@classmethod
|
|
155
|
-
def update_value(cls, var_entry: DataVariableRegistryEntry, store: CacheStore, data: Optional[DataFrame]):
|
|
156
|
-
"""
|
|
157
|
-
Update the data entry and notify all clients about the update.
|
|
158
|
-
|
|
159
|
-
:param var_entry: the entry in the registry
|
|
160
|
-
:param data: the data to update
|
|
161
|
-
:param store: store instance
|
|
162
|
-
"""
|
|
163
|
-
from dara.core.internal.registries import utils_registry
|
|
164
|
-
|
|
165
|
-
ws_mgr: WebsocketManager = utils_registry.get('WebsocketManager')
|
|
166
|
-
task_group: TaskGroup = utils_registry.get('TaskGroup')
|
|
167
|
-
|
|
168
|
-
# Update store
|
|
169
|
-
task_group.start_soon(cls._update, var_entry, store, data)
|
|
170
|
-
|
|
171
|
-
# Broadcast the update to all clients
|
|
172
|
-
task_group.start_soon(
|
|
173
|
-
ws_mgr.broadcast,
|
|
174
|
-
{
|
|
175
|
-
'data_id': str(var_entry.uid),
|
|
176
|
-
},
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
@classmethod
|
|
180
|
-
async def get_value(
|
|
181
|
-
cls,
|
|
182
|
-
var_entry: DataVariableRegistryEntry,
|
|
183
|
-
store: CacheStore,
|
|
184
|
-
filters: Optional[Union[FilterQuery, dict]] = None,
|
|
185
|
-
pagination: Optional[Pagination] = None,
|
|
186
|
-
format_for_display: bool = False,
|
|
187
|
-
) -> Optional[DataFrame]:
|
|
188
|
-
"""
|
|
189
|
-
Get the value of this DataVariable.
|
|
190
|
-
"""
|
|
191
|
-
_uid_short = f'{var_entry.uid[:3]}..{var_entry.uid[-3:]}'
|
|
192
|
-
eng_logger.info(
|
|
193
|
-
f'Data Variable {_uid_short} get_value',
|
|
194
|
-
{'uid': var_entry.uid, 'filters': filters, 'pagination': pagination},
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
cache_key = cls._get_cache_key(var_entry.uid)
|
|
198
|
-
entry = await store.get(var_entry, key=cache_key)
|
|
199
|
-
|
|
200
|
-
eng_logger.debug(
|
|
201
|
-
f'Data Variable {_uid_short}',
|
|
202
|
-
'retrieved from cache',
|
|
203
|
-
{
|
|
204
|
-
'uid': var_entry.uid,
|
|
205
|
-
'size': len(entry.data.index) if entry is not None and entry.data is not None else 0,
|
|
206
|
-
},
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
if entry is None:
|
|
210
|
-
await asyncio.gather(
|
|
211
|
-
store.set(var_entry, key=cls._get_count_cache_key(var_entry.uid, filters), value=0, pin=True),
|
|
212
|
-
store.set(var_entry, key=cls._get_schema_cache_key(var_entry.uid), value=None, pin=True),
|
|
213
|
-
)
|
|
214
|
-
return None
|
|
215
|
-
|
|
216
|
-
data = None
|
|
217
|
-
|
|
218
|
-
if entry.data is not None:
|
|
219
|
-
filtered_data, count = apply_filters(entry.data, coerce_to_filter_query(filters), pagination)
|
|
220
|
-
if format_for_display and filtered_data is not None:
|
|
221
|
-
filtered_data = filtered_data.copy()
|
|
222
|
-
for col in filtered_data.columns:
|
|
223
|
-
if filtered_data[col].dtype == 'object':
|
|
224
|
-
# We need to convert all values to string to avoid issues with displaying data in the Table component, for example when displaying datetime and number objects in the same column
|
|
225
|
-
filtered_data.loc[:, col] = filtered_data[col].apply(str)
|
|
226
|
-
data = filtered_data
|
|
227
|
-
# Store count for given filters and schema
|
|
228
|
-
await asyncio.gather(
|
|
229
|
-
store.set(var_entry, key=cls._get_count_cache_key(var_entry.uid, filters), value=count, pin=True),
|
|
230
|
-
store.set(
|
|
231
|
-
var_entry,
|
|
232
|
-
key=cls._get_schema_cache_key(var_entry.uid),
|
|
233
|
-
value=build_table_schema(df_convert_to_internal(entry.data)),
|
|
234
|
-
pin=True,
|
|
235
|
-
),
|
|
236
|
-
)
|
|
237
|
-
else:
|
|
238
|
-
await asyncio.gather(
|
|
239
|
-
store.set(var_entry, key=cls._get_count_cache_key(var_entry.uid, filters), value=0, pin=True),
|
|
240
|
-
store.set(var_entry, key=cls._get_schema_cache_key(var_entry.uid), value=None, pin=True),
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
# TODO: once path is supported, stream&filter from disk
|
|
244
|
-
if entry.path:
|
|
245
|
-
raise NotImplementedError('DataVariable.get_value() does not support disk caching yet')
|
|
246
|
-
|
|
247
|
-
eng_logger.info(
|
|
248
|
-
f'Data Variable {_uid_short} returning filtered data',
|
|
249
|
-
{'uid': var_entry.uid, 'size': len(data.index) if data is not None else 0},
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
return data
|
|
253
|
-
|
|
254
|
-
@classmethod
|
|
255
|
-
async def get_total_count(
|
|
256
|
-
cls, var_entry: DataVariableRegistryEntry, store: CacheStore, filters: Optional[FilterQuery]
|
|
257
|
-
):
|
|
258
|
-
"""
|
|
259
|
-
Get total count of the data variable.
|
|
260
|
-
|
|
261
|
-
:param var_entry: variable entry
|
|
262
|
-
:param store: store
|
|
263
|
-
:param filters: filters to get count for
|
|
264
|
-
"""
|
|
265
|
-
cache_key = cls._get_count_cache_key(var_entry.uid, filters)
|
|
266
|
-
entry = await store.get(var_entry, key=cache_key, unpin=True)
|
|
267
|
-
|
|
268
|
-
if entry is None:
|
|
269
|
-
raise ValueError('Requested count for filter setup which has not been performed yet')
|
|
270
|
-
|
|
271
|
-
return entry
|
|
272
|
-
|
|
273
|
-
@classmethod
|
|
274
|
-
async def get_schema(cls, var_entry: DataVariableRegistryEntry, store: CacheStore):
|
|
275
|
-
"""
|
|
276
|
-
Get the schema of the data variable.
|
|
277
|
-
|
|
278
|
-
:param var_entry: variable entry
|
|
279
|
-
:param store: store
|
|
280
|
-
"""
|
|
281
|
-
cache_key = cls._get_schema_cache_key(var_entry.uid)
|
|
282
|
-
entry = await store.get(var_entry, key=cache_key, unpin=True)
|
|
283
|
-
|
|
284
|
-
return cast(DataFrameSchema, entry)
|
|
285
|
-
|
|
286
|
-
def reset(self):
|
|
287
|
-
raise NotImplementedError('DataVariable cannot be reset')
|
|
288
|
-
|
|
289
|
-
def update(self, value: Optional[DataFrame]):
|
|
290
|
-
"""
|
|
291
|
-
Create an action to update the value of this Variable to a provided value.
|
|
292
|
-
|
|
293
|
-
```python
|
|
294
|
-
import pandas as pd
|
|
295
|
-
from dara.core import DataVariable
|
|
296
|
-
from dara.components import Button
|
|
297
|
-
|
|
298
|
-
data = DataVariable(pd.DataFrame({'a': [1, 2, 3]}))
|
|
299
|
-
|
|
300
|
-
Button(
|
|
301
|
-
'Empty Data',
|
|
302
|
-
onclick=data.update(None),
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
```
|
|
306
|
-
"""
|
|
307
|
-
from dara.core.interactivity.actions import UpdateVariableImpl
|
|
308
|
-
|
|
309
|
-
return UpdateVariableImpl(variable=self, value=value)
|
|
310
|
-
|
|
311
|
-
@model_serializer(mode='wrap')
|
|
312
|
-
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
313
|
-
parent_dict = nxt(self)
|
|
314
|
-
if 'data' in parent_dict:
|
|
315
|
-
parent_dict.pop('data') # make sure data is not included in the serialised dict
|
|
316
|
-
return {**parent_dict, '__typename': 'DataVariable', 'uid': str(parent_dict['uid'])}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
class DataStoreEntry(BaseModel):
|
|
320
|
-
"""
|
|
321
|
-
Entry in the cache store for a DataVariable.
|
|
322
|
-
Can either be a DataFrame or a path to a file on disk.
|
|
323
|
-
"""
|
|
67
|
+
scope = 'global' if cache_policy.cache_type == Cache.Type.GLOBAL else 'user'
|
|
324
68
|
|
|
325
|
-
|
|
326
|
-
path: Optional[str] = None
|
|
327
|
-
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
|
|
69
|
+
super().__init__(scope=scope, uid=uid, default=data, **kwargs)
|