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