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.
Files changed (82) hide show
  1. dara/core/__init__.py +0 -3
  2. dara/core/actions.py +2 -1
  3. dara/core/auth/basic.py +16 -22
  4. dara/core/auth/definitions.py +2 -2
  5. dara/core/auth/routes.py +5 -5
  6. dara/core/auth/utils.py +5 -5
  7. dara/core/base_definitions.py +64 -22
  8. dara/core/cli.py +7 -8
  9. dara/core/configuration.py +2 -5
  10. dara/core/css.py +2 -1
  11. dara/core/data_utils.py +19 -18
  12. dara/core/defaults.py +7 -6
  13. dara/core/definitions.py +19 -50
  14. dara/core/http.py +3 -7
  15. dara/core/interactivity/__init__.py +0 -6
  16. dara/core/interactivity/actions.py +50 -52
  17. dara/core/interactivity/any_data_variable.py +134 -7
  18. dara/core/interactivity/any_variable.py +8 -5
  19. dara/core/interactivity/data_variable.py +266 -8
  20. dara/core/interactivity/derived_data_variable.py +290 -7
  21. dara/core/interactivity/derived_variable.py +176 -416
  22. dara/core/interactivity/filtering.py +27 -46
  23. dara/core/interactivity/loop_variable.py +2 -2
  24. dara/core/interactivity/non_data_variable.py +68 -5
  25. dara/core/interactivity/plain_variable.py +15 -89
  26. dara/core/interactivity/switch_variable.py +19 -19
  27. dara/core/interactivity/url_variable.py +90 -10
  28. dara/core/internal/cache_store/base_impl.py +1 -2
  29. dara/core/internal/cache_store/cache_store.py +25 -22
  30. dara/core/internal/cache_store/keep_all.py +1 -4
  31. dara/core/internal/cache_store/lru.py +1 -5
  32. dara/core/internal/cache_store/ttl.py +1 -4
  33. dara/core/internal/cgroup.py +1 -1
  34. dara/core/internal/dependency_resolution.py +66 -60
  35. dara/core/internal/devtools.py +5 -12
  36. dara/core/internal/download.py +4 -13
  37. dara/core/internal/encoder_registry.py +7 -7
  38. dara/core/internal/execute_action.py +13 -13
  39. dara/core/internal/hashing.py +3 -1
  40. dara/core/internal/import_discovery.py +4 -3
  41. dara/core/internal/normalization.py +18 -9
  42. dara/core/internal/pandas_utils.py +5 -107
  43. dara/core/internal/pool/definitions.py +1 -1
  44. dara/core/internal/pool/task_pool.py +16 -25
  45. dara/core/internal/pool/utils.py +18 -21
  46. dara/core/internal/pool/worker.py +2 -3
  47. dara/core/internal/port_utils.py +1 -1
  48. dara/core/internal/registries.py +6 -12
  49. dara/core/internal/registry.py +2 -4
  50. dara/core/internal/registry_lookup.py +5 -11
  51. dara/core/internal/routing.py +145 -109
  52. dara/core/internal/scheduler.py +8 -13
  53. dara/core/internal/settings.py +2 -2
  54. dara/core/internal/store.py +29 -2
  55. dara/core/internal/tasks.py +195 -379
  56. dara/core/internal/utils.py +13 -36
  57. dara/core/internal/websocket.py +20 -21
  58. dara/core/js_tooling/js_utils.py +26 -28
  59. dara/core/js_tooling/templates/vite.config.template.ts +3 -12
  60. dara/core/logging.py +12 -13
  61. dara/core/main.py +11 -14
  62. dara/core/metrics/cache.py +1 -1
  63. dara/core/metrics/utils.py +3 -3
  64. dara/core/persistence.py +5 -27
  65. dara/core/umd/dara.core.umd.js +55428 -59098
  66. dara/core/visual/components/__init__.py +2 -2
  67. dara/core/visual/components/fallback.py +4 -30
  68. dara/core/visual/components/for_cmp.py +1 -4
  69. dara/core/visual/css/__init__.py +31 -30
  70. dara/core/visual/dynamic_component.py +28 -31
  71. dara/core/visual/progress_updater.py +3 -4
  72. {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/METADATA +11 -12
  73. dara_core-1.20.1a1.dist-info/RECORD +114 -0
  74. dara/core/interactivity/client_variable.py +0 -71
  75. dara/core/interactivity/server_variable.py +0 -325
  76. dara/core/interactivity/state_variable.py +0 -69
  77. dara/core/interactivity/tabular_variable.py +0 -94
  78. dara/core/internal/multi_resource_lock.py +0 -70
  79. dara_core-1.20.0.dist-info/RECORD +0 -119
  80. {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/LICENSE +0 -0
  81. {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/WHEEL +0 -0
  82. {dara_core-1.20.0.dist-info → dara_core-1.20.1a1.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,138 @@
1
- from typing_extensions import TypeAlias
1
+ """
2
+ Copyright 2023 Impulse Innovations Limited
2
3
 
3
- from dara.core.interactivity.any_variable import AnyVariable
4
4
 
5
- # re-export for backwards compatibility
6
- from .tabular_variable import * # noqa: F403
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
7
8
 
8
- AnyDataVariable: TypeAlias = AnyVariable
9
- """
10
- Deprecated alias. Tabular variables are now DerivedVariable or ServerVariable
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.
11
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
26
+
27
+ from dara.core.base_definitions import CachedRegistryEntry, UploadResolverDef
28
+ 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
+
63
+ class DataVariableRegistryEntry(CachedRegistryEntry):
64
+ """
65
+ Registry entry for DataVariable.
66
+ """
67
+
68
+ type: Literal['plain', 'derived']
69
+ get_data: Callable[..., Awaitable[Any]]
70
+ """Handler to get the data from the data variable. Defaults to DataVariable.get_value for type=plain, and DerivedDataVariable.get_data for type=derived"""
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)
@@ -30,8 +30,9 @@ 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, PendingTask
33
+ from dara.core.base_definitions import BaseTask
34
34
  from dara.core.base_definitions import DaraBaseModel as BaseModel
35
+ from dara.core.base_definitions import PendingTask
35
36
  from dara.core.interactivity.condition import Condition, Operator
36
37
  from dara.core.internal.cache_store import CacheStore
37
38
  from dara.core.internal.tasks import TaskManager
@@ -111,7 +112,7 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
111
112
  user_identity = None
112
113
 
113
114
  if current_user is not None:
114
- user_identity = current_user.identity_id
115
+ user_identity = current_user.identity_id or current_user.identity_name
115
116
  elif isinstance(auth_config, BasicAuthConfig):
116
117
  # basic auth - assume it's the single existing user
117
118
  user_identity = list(auth_config.users.keys())[0]
@@ -203,6 +204,7 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
203
204
 
204
205
  for session, channels in session_channels.items():
205
206
  for ws in channels:
207
+
206
208
  raw_result = raw_results[ws]
207
209
  # Skip values from clients where the variable is not registered
208
210
  if raw_result == NOT_REGISTERED:
@@ -236,12 +238,12 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
236
238
 
237
239
  # If we're returning multiple values, in Jupyter environments print an explainer
238
240
  try:
239
- from IPython import get_ipython # pyright: ignore[reportMissingImports]
241
+ from IPython import get_ipython
240
242
  except ImportError:
241
243
  pass
242
244
  else:
243
245
  if get_ipython() is not None:
244
- from IPython.display import HTML, display # pyright: ignore[reportMissingImports]
246
+ from IPython.display import HTML, display
245
247
 
246
248
  display(
247
249
  HTML(
@@ -270,7 +272,7 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
270
272
  return results
271
273
 
272
274
 
273
- class AnyVariable(BaseModel, abc.ABC): # noqa: PLW1641 # we override equals to create conditions, otherwise we should define hash
275
+ class AnyVariable(BaseModel, abc.ABC):
274
276
  """
275
277
  Base class for all variables. Used for typing to specify that any variable can be provided.
276
278
  """
@@ -280,6 +282,7 @@ class AnyVariable(BaseModel, abc.ABC): # noqa: PLW1641 # we override equals to
280
282
  uid: str
281
283
 
282
284
  def __init__(self, uid: Optional[str] = None, **kwargs) -> None:
285
+
283
286
  new_uid = uid
284
287
  if new_uid is None:
285
288
  new_uid = str(uuid.uuid4())
@@ -17,15 +17,40 @@ limitations under the License.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from typing import Optional
20
+ import asyncio
21
+ from typing import Optional, Union, cast
21
22
 
23
+ from anyio.abc import TaskGroup
22
24
  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
+ )
23
32
 
24
- from dara.core.base_definitions import Cache, CacheArgType
25
- from dara.core.interactivity.server_variable import ServerVariable
33
+ from dara.core.base_definitions import BaseCachePolicy, Cache, CacheArgType
34
+ from dara.core.interactivity.any_data_variable import (
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
26
51
 
27
52
 
28
- class DataVariable(ServerVariable):
53
+ class DataVariable(AnyDataVariable):
29
54
  """
30
55
  DataVariable represents a variable that is specifically designed to hold datasets.
31
56
 
@@ -44,6 +69,11 @@ class DataVariable(ServerVariable):
44
69
  >>> UpdateVariable(func=some_data_transformation, variable=session_data)
45
70
  """
46
71
 
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
+
47
77
  def __init__(
48
78
  self,
49
79
  data: Optional[DataFrame] = None,
@@ -59,11 +89,239 @@ class DataVariable(ServerVariable):
59
89
  :param filters: a dictionary of filters to apply to the data
60
90
  :param cache: how to cache the result; 'user' per user, 'session' per session, 'global' for all users
61
91
  """
62
- cache_policy = Cache.Policy.from_arg(cache)
92
+ cache = Cache.Policy.from_arg(cache)
63
93
 
64
- if data is not None and cache_policy.cache_type is not Cache.Type.GLOBAL:
94
+ if data is not None and cache.cache_type is not Cache.Type.GLOBAL:
65
95
  raise ValueError('Data cannot be cached per session or per user if provided upfront')
66
96
 
67
- scope = 'global' if cache_policy.cache_type == Cache.Type.GLOBAL else 'user'
97
+ super().__init__(cache=cache, uid=uid, **kwargs)
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
+ """
68
324
 
69
- super().__init__(scope=scope, uid=uid, default=data, **kwargs)
325
+ data: Optional[DataFrame] = None
326
+ path: Optional[str] = None
327
+ model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)