dara-core 1.19.0__py3-none-any.whl → 1.20.0__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 (52) 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 +6 -0
  9. dara/core/interactivity/actions.py +26 -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 +381 -201
  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 +69 -0
  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 +9 -6
  34. dara/core/internal/pool/utils.py +19 -14
  35. dara/core/internal/registries.py +3 -2
  36. dara/core/internal/registry.py +1 -1
  37. dara/core/internal/registry_lookup.py +5 -3
  38. dara/core/internal/routing.py +52 -121
  39. dara/core/internal/store.py +2 -29
  40. dara/core/internal/tasks.py +372 -182
  41. dara/core/internal/utils.py +25 -3
  42. dara/core/internal/websocket.py +1 -1
  43. dara/core/js_tooling/js_utils.py +2 -0
  44. dara/core/logging.py +10 -6
  45. dara/core/persistence.py +26 -4
  46. dara/core/umd/dara.core.umd.js +1091 -1469
  47. dara/core/visual/dynamic_component.py +17 -13
  48. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/METADATA +11 -11
  49. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/RECORD +52 -47
  50. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/LICENSE +0 -0
  51. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/WHEEL +0 -0
  52. {dara_core-1.19.0.dist-info → dara_core-1.20.0.dist-info}/entry_points.txt +0 -0
@@ -17,47 +17,26 @@ limitations under the License.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import asyncio
21
20
  from collections.abc import Coroutine
22
- from typing import Any, Callable, List, Optional, Union, cast
23
- from uuid import uuid4
21
+ from typing import Any, Callable, List, Optional, Union
24
22
 
25
23
  from pandas import DataFrame
26
- from pandas.io.json._table_schema import build_table_schema
27
- from pydantic import ConfigDict, SerializerFunctionWrapHandler, model_serializer
24
+ from typing_extensions import deprecated
28
25
 
29
26
  from dara.core.base_definitions import (
30
- BaseTask,
31
27
  Cache,
32
28
  CacheArgType,
33
- PendingTask,
34
- PendingValue,
35
- )
36
- from dara.core.interactivity.any_data_variable import (
37
- AnyDataVariable,
38
- DataFrameSchema,
39
- DataVariableRegistryEntry,
40
29
  )
41
30
  from dara.core.interactivity.any_variable import AnyVariable
42
31
  from dara.core.interactivity.derived_variable import (
43
32
  DerivedVariable,
44
- DerivedVariableRegistryEntry,
45
- DerivedVariableResult,
46
- )
47
- from dara.core.interactivity.filtering import (
48
- FilterQuery,
49
- Pagination,
50
- apply_filters,
51
- coerce_to_filter_query,
52
33
  )
53
- from dara.core.internal.cache_store import CacheStore
54
- from dara.core.internal.hashing import hash_object
55
- from dara.core.internal.pandas_utils import append_index, df_convert_to_internal
56
- from dara.core.internal.tasks import MetaTask, Task, TaskManager
57
- from dara.core.logging import eng_logger
58
34
 
59
35
 
60
- class DerivedDataVariable(AnyDataVariable, DerivedVariable):
36
+ @deprecated(
37
+ 'DerivedDataVariable is deprecated and will be removed in a future version. Use dara.core.interactivity.derived_variable.DerivedVariable instead, it can now return DataFrames'
38
+ )
39
+ class DerivedDataVariable(DerivedVariable):
61
40
  """
62
41
  DerivedDataVariable represents a variable designed to hold datasets computed
63
42
  by a resolver function like a normal DerivedVariable.
@@ -65,13 +44,6 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
65
44
  Note: the resolver function must return a DataFrame.
66
45
  """
67
46
 
68
- uid: str
69
- filters: Optional[FilterQuery] = None
70
- variables: List[AnyVariable]
71
- polling_interval: Optional[int] = None
72
- deps: Optional[List[AnyVariable]] = None
73
- model_config = ConfigDict(extra='forbid')
74
-
75
47
  def __init__(
76
48
  self,
77
49
  func: Union[
@@ -107,9 +79,6 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
107
79
  - `deps = [var1, var2]` - `func` is ran whenever one of these vars changes
108
80
  :param uid: the unique identifier for this variable; if not provided a random one is generated
109
81
  """
110
- cache = Cache.Policy.from_arg(cache)
111
-
112
- # Initialize the DV underneath, which puts an entry in the derived variable registry
113
82
  super().__init__(
114
83
  func=func,
115
84
  cache=cache,
@@ -118,257 +87,4 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
118
87
  polling_interval=polling_interval,
119
88
  deps=deps,
120
89
  run_as_task=run_as_task,
121
- _get_value=DerivedDataVariable.get_value,
122
- )
123
-
124
- # Also put an entry in the data variable registry under the same uid; this way we can send a request
125
- # for either the DV (to update the cached value) or the DataVariable (to get the cached value)
126
- from dara.core.internal.registries import data_variable_registry
127
-
128
- data_variable_registry.register(
129
- str(self.uid),
130
- DataVariableRegistryEntry(
131
- type='derived',
132
- cache=cache,
133
- uid=str(self.uid),
134
- get_data=DerivedDataVariable.get_data,
135
- get_total_count=DerivedDataVariable.get_total_count,
136
- get_schema=DerivedDataVariable.get_schema,
137
- ),
138
- )
139
-
140
- @staticmethod
141
- def _get_schema_cache_key(cache_key: str) -> str:
142
- """
143
- Get a unique cache key for the data variable's schema.
144
-
145
- :param cache_key: cache_key of the DerivedDataVariable
146
- """
147
- return f'schema-{cache_key}'
148
-
149
- @staticmethod
150
- async def _filter_data(
151
- data: Union[DataFrame, Any, None],
152
- count_cache_key: str,
153
- var_entry: DataVariableRegistryEntry,
154
- store: CacheStore,
155
- filters: Optional[Union[FilterQuery, dict]] = None,
156
- pagination: Optional[Pagination] = None,
157
- ) -> Optional[DataFrame]:
158
- """
159
- Helper function to apply filters and pagination to a dataframe.
160
- Also verifies if the data is a DataFrame.
161
-
162
- :param data: data to filter
163
- :param count_cache_key: cache key to store the count under
164
- :param var_entry: data variable entry
165
- :param store: store instance
166
- :param filters: filters to use
167
- :param pagination: pagination to use
168
- """
169
- if data is not None and not isinstance(data, DataFrame):
170
- raise ValueError(f'Data returned by DerivedDataVariable resolver must be a DataFrame, found {type(data)}')
171
-
172
- # Right before we filter, append index column to the dataset
173
- data = append_index(data)
174
-
175
- filtered_data, count = apply_filters(data, coerce_to_filter_query(filters), pagination)
176
-
177
- # Cache the count
178
- await store.set(var_entry, key=count_cache_key, value=count, pin=True)
179
-
180
- return filtered_data
181
-
182
- @classmethod
183
- async def get_value(
184
- cls,
185
- var_entry: DerivedVariableRegistryEntry,
186
- store: CacheStore,
187
- task_mgr: TaskManager,
188
- args: List[Any],
189
- force_key: Optional[str] = None,
190
- ) -> DerivedVariableResult:
191
- """
192
- Update the underlying derived variable.
193
- Wrapper around DerivedVariable.get_value which does not return the value (returns `True` instead).
194
-
195
- :param var: the registry entry for the underlying derived variable
196
- :param store: the store instance to check for cached values
197
- :param task_mgr: task manager instance
198
- :param args: the arguments to call the underlying function with
199
- :param force: whether to ignore cache
200
- """
201
- _uid_short = f'{var_entry.uid[:3]}..{var_entry.uid[-3:]}'
202
- eng_logger.info(
203
- f'Derived Data Variable {_uid_short} calling superclass get_value', {'uid': var_entry.uid, 'args': args}
204
- )
205
- value = await super().get_value(var_entry, store, task_mgr, args, force_key)
206
-
207
- # Pin the value in the store until it's read by get data
208
- await asyncio.gather(
209
- store.set(registry_entry=var_entry, key=value['cache_key'], value=value['value'], pin=True),
210
- store.set(
211
- registry_entry=var_entry,
212
- key=cls._get_schema_cache_key(value['cache_key']),
213
- value=build_table_schema(
214
- df_convert_to_internal(cast(DataFrame, value['value'])),
215
- )
216
- if isinstance(value['value'], DataFrame)
217
- else None,
218
- pin=True,
219
- ),
220
- )
221
-
222
- eng_logger.info(
223
- f'Derived Data Variable {_uid_short} received result from superclass',
224
- {'uid': var_entry.uid, 'result': value},
225
90
  )
226
-
227
- # If the value is a task, then we need to return it
228
- if isinstance(value['value'], BaseTask):
229
- return value
230
-
231
- return {'cache_key': value['cache_key'], 'value': True}
232
-
233
- @classmethod
234
- async def get_data(
235
- cls,
236
- dv_entry: DerivedVariableRegistryEntry,
237
- data_entry: DataVariableRegistryEntry,
238
- cache_key: str,
239
- store: CacheStore,
240
- filters: Optional[Union[FilterQuery, dict]] = None,
241
- pagination: Optional[Pagination] = None,
242
- format_for_display: bool = False,
243
- ) -> Union[BaseTask, DataFrame, None]:
244
- """
245
- Get the filtered data from the underlying derived variable stored under the specified cache_key.
246
-
247
- :param var_entry: the registry entry for the data variable
248
- :param cache_key: cache_key of the underlying DerivedVariable
249
- :param store: the store instance to check for cached values
250
- :param filters: the filters to apply to the data
251
- :param pagination: the pagination to apply to the data
252
- """
253
- _uid_short = f'{data_entry.uid[:3]}..{data_entry.uid[-3:]}'
254
- data_cache_key = f'{cache_key}_{hash_object(filters or {})}_{hash_object(pagination or {})}'
255
- count_cache_key = f'{cache_key}_{hash_object(filters or {})}'
256
-
257
- # Check for cached result of the entire data variable
258
- data_store_entry = await store.get(data_entry, key=data_cache_key)
259
-
260
- # if there's a pending task for this exact request, subscribe to the pending task and return it
261
- if isinstance(data_store_entry, PendingTask):
262
- data_store_entry.add_subscriber()
263
- return data_store_entry
264
-
265
- # Found cached result
266
- if isinstance(data_store_entry, DataFrame):
267
- return data_store_entry
268
-
269
- # First retrieve the cached data for underlying DV
270
- data = await store.get(dv_entry, key=cache_key, unpin=True)
271
-
272
- # Value could have been made pending in the meantime
273
- if isinstance(data, PendingValue):
274
- data = await data.wait()
275
-
276
- eng_logger.info(
277
- f'Derived Data Variable {_uid_short} retrieved underlying DV value', {'uid': dv_entry.uid, 'value': data}
278
- )
279
-
280
- # if the DV returned a task (Task/PendingTask), return a MetaTask which will do the filtering on the task result
281
- if isinstance(data, BaseTask):
282
- task_id = f'{dv_entry.uid}_Filter_MetaTask_{str(uuid4())}'
283
-
284
- eng_logger.info(
285
- f'Derived Data Variable {_uid_short} creating filtering metatask',
286
- {'uid': dv_entry.uid, 'task_id': task_id, 'cache_key': data_cache_key},
287
- )
288
-
289
- return MetaTask(
290
- cls._filter_data,
291
- [data, count_cache_key, data_entry, store, filters, pagination],
292
- notify_channels=data.notify_channels,
293
- process_as_task=False,
294
- cache_key=data_cache_key,
295
- reg_entry=data_entry, # task results are set as the variable result
296
- task_id=task_id,
297
- )
298
-
299
- # Run the filtering
300
- data = await cls._filter_data(data, count_cache_key, data_entry, store, filters, pagination)
301
- if format_for_display and data is not None:
302
- data = data.copy()
303
- for col in data.columns:
304
- if data[col].dtype == 'object':
305
- # 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
306
- data.loc[:, col] = data[col].apply(str)
307
-
308
- return data
309
-
310
- @classmethod
311
- async def get_total_count(
312
- cls, data_entry: DataVariableRegistryEntry, store: CacheStore, cache_key: str, filters: Optional[FilterQuery]
313
- ):
314
- """
315
- Get total count of the derived data variable.
316
- """
317
- count_cache_key = f'{cache_key}_{hash_object(filters or {})}'
318
- entry = await store.get(data_entry, key=count_cache_key, unpin=True)
319
-
320
- # No entry means this filter setup has not been done yet, this shouldn't happen
321
- if entry is None:
322
- raise ValueError('Requested count for filter setup which has not been performed yet')
323
-
324
- return entry
325
-
326
- @classmethod
327
- async def get_schema(cls, derived_entry: DerivedVariableRegistryEntry, store: CacheStore, cache_key: str):
328
- """
329
- Get the schema of the derived data variable.
330
- """
331
- return cast(
332
- DataFrameSchema, await store.get(derived_entry, key=cls._get_schema_cache_key(cache_key), unpin=True)
333
- )
334
-
335
- @classmethod
336
- async def resolve_value(
337
- cls,
338
- data_entry: DataVariableRegistryEntry,
339
- dv_entry: DerivedVariableRegistryEntry,
340
- store: CacheStore,
341
- task_mgr: TaskManager,
342
- args: List[Any],
343
- filters: Optional[Union[FilterQuery, dict]] = None,
344
- force_key: Optional[str] = None,
345
- ):
346
- """
347
- Helper method to resolve the filtered value of a derived data variable.
348
- Under the hood runs the underlying DerivedVariable, starts its task if required, and then filters the result.
349
-
350
- :param data_entry: the registry entry for the data variable
351
- :param dv_entry: the registry entry for the underlying derived variable
352
- :param store: the store instance to check for cached values
353
- :param task_mgr: task manager instance
354
- :param args: the arguments to call the underlying function with
355
- :param filters: the filters to apply to the data
356
- :param force_key: unique key for forced execution, if provided forces cache bypass
357
- :param pagination: the pagination to apply to the data
358
- """
359
- dv_result = await cls.get_value(dv_entry, store, task_mgr, args, force_key)
360
-
361
- # If the intermediate result was a task/metatask, we need to run it
362
- # get_data will then pick up the result from the pending task for it
363
- if isinstance(dv_result['value'], (Task, MetaTask)):
364
- await task_mgr.run_task(dv_result['value'], None)
365
-
366
- return await cls.get_data(dv_entry, data_entry, dv_result['cache_key'], store, filters)
367
-
368
- @model_serializer(mode='wrap')
369
- def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
370
- parent_dict = nxt(self)
371
- # nested is not supported for DerivedDataVariable so remove from serialised form
372
- # it's included because we inherit from DV which has the field
373
- parent_dict.pop('nested')
374
- return {**parent_dict, '__typename': 'DerivedDataVariable', 'uid': str(parent_dict['uid'])}