dara-core 1.19.1__py3-none-any.whl → 1.20.0a1__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 (51) hide show
  1. dara/core/__init__.py +1 -0
  2. dara/core/auth/basic.py +13 -7
  3. dara/core/auth/definitions.py +2 -2
  4. dara/core/auth/utils.py +1 -1
  5. dara/core/base_definitions.py +7 -42
  6. dara/core/data_utils.py +16 -17
  7. dara/core/definitions.py +8 -8
  8. dara/core/interactivity/__init__.py +4 -0
  9. dara/core/interactivity/actions.py +20 -22
  10. dara/core/interactivity/any_data_variable.py +7 -135
  11. dara/core/interactivity/any_variable.py +1 -1
  12. dara/core/interactivity/client_variable.py +71 -0
  13. dara/core/interactivity/data_variable.py +8 -266
  14. dara/core/interactivity/derived_data_variable.py +6 -290
  15. dara/core/interactivity/derived_variable.py +333 -199
  16. dara/core/interactivity/filtering.py +29 -2
  17. dara/core/interactivity/loop_variable.py +2 -2
  18. dara/core/interactivity/non_data_variable.py +5 -68
  19. dara/core/interactivity/plain_variable.py +87 -14
  20. dara/core/interactivity/server_variable.py +325 -0
  21. dara/core/interactivity/state_variable.py +2 -2
  22. dara/core/interactivity/switch_variable.py +15 -15
  23. dara/core/interactivity/tabular_variable.py +94 -0
  24. dara/core/interactivity/url_variable.py +10 -90
  25. dara/core/internal/cache_store/cache_store.py +5 -20
  26. dara/core/internal/dependency_resolution.py +27 -69
  27. dara/core/internal/devtools.py +10 -3
  28. dara/core/internal/execute_action.py +9 -3
  29. dara/core/internal/multi_resource_lock.py +70 -0
  30. dara/core/internal/normalization.py +0 -5
  31. dara/core/internal/pandas_utils.py +105 -3
  32. dara/core/internal/pool/definitions.py +1 -1
  33. dara/core/internal/pool/task_pool.py +1 -1
  34. dara/core/internal/registries.py +3 -2
  35. dara/core/internal/registry.py +1 -1
  36. dara/core/internal/registry_lookup.py +5 -3
  37. dara/core/internal/routing.py +52 -121
  38. dara/core/internal/store.py +2 -29
  39. dara/core/internal/tasks.py +372 -182
  40. dara/core/internal/utils.py +25 -3
  41. dara/core/internal/websocket.py +1 -1
  42. dara/core/js_tooling/js_utils.py +2 -0
  43. dara/core/logging.py +10 -6
  44. dara/core/persistence.py +26 -4
  45. dara/core/umd/dara.core.umd.js +742 -1381
  46. dara/core/visual/dynamic_component.py +10 -13
  47. {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/METADATA +10 -10
  48. {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/RECORD +51 -47
  49. {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/LICENSE +0 -0
  50. {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/WHEEL +0 -0
  51. {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/entry_points.txt +0 -0
@@ -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 asyncio
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 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
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(AnyDataVariable):
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
- cache = Cache.Policy.from_arg(cache)
62
+ cache_policy = Cache.Policy.from_arg(cache)
93
63
 
94
- if data is not None and cache.cache_type is not Cache.Type.GLOBAL:
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
- 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
- """
67
+ scope = 'global' if cache_policy.cache_type == Cache.Type.GLOBAL else 'user'
324
68
 
325
- data: Optional[DataFrame] = None
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)