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
dara/core/__init__.py CHANGED
@@ -41,6 +41,7 @@ __all__ = [
41
41
  'DerivedVariable',
42
42
  'DerivedDataVariable',
43
43
  'DataVariable',
44
+ 'ServerVariable',
44
45
  'UrlVariable',
45
46
  'Cache',
46
47
  'CacheType',
dara/core/auth/basic.py CHANGED
@@ -25,7 +25,6 @@ from dara.core.auth.definitions import (
25
25
  EXPIRED_TOKEN_ERROR,
26
26
  INVALID_CREDENTIALS_ERROR,
27
27
  INVALID_TOKEN_ERROR,
28
- JWT_ALGO,
29
28
  SESSION_ID,
30
29
  USER,
31
30
  AuthError,
@@ -35,7 +34,6 @@ from dara.core.auth.definitions import (
35
34
  UserData,
36
35
  )
37
36
  from dara.core.auth.utils import decode_token, sign_jwt
38
- from dara.core.internal.settings import get_settings
39
37
 
40
38
  DefaultAuthLogin = AuthComponent(js_module='@darajs/core', py_module='dara.core', js_name='DefaultAuthLogin')
41
39
 
@@ -69,7 +67,7 @@ class BaseBasicAuthConfig(BaseAuthConfig):
69
67
 
70
68
  return {
71
69
  'token': sign_jwt(
72
- identity_id=None,
70
+ identity_id=body.username,
73
71
  identity_name=body.username,
74
72
  identity_email=None,
75
73
  groups=[],
@@ -87,6 +85,7 @@ class BaseBasicAuthConfig(BaseAuthConfig):
87
85
  SESSION_ID.set(decoded.session_id)
88
86
  USER.set(
89
87
  UserData(
88
+ identity_id=decoded.identity_name,
90
89
  identity_name=decoded.identity_name,
91
90
  )
92
91
  )
@@ -127,7 +126,7 @@ class DefaultAuthConfig(BaseAuthConfig):
127
126
 
128
127
  In default auth a new token is returned every time.
129
128
  """
130
- token = sign_jwt(identity_id=None, identity_name='user', identity_email=None, groups=[])
129
+ token = sign_jwt(identity_id='user', identity_name='user', identity_email=None, groups=[])
131
130
  return {'token': token}
132
131
 
133
132
  def verify_token(self, token: str) -> TokenData:
@@ -139,9 +138,16 @@ class DefaultAuthConfig(BaseAuthConfig):
139
138
  :param token: the token to verify
140
139
  """
141
140
  try:
142
- decoded = jwt.decode(token, get_settings().jwt_secret, algorithms=[JWT_ALGO])
143
- SESSION_ID.set(decoded.get('session_id'))
144
- return TokenData.parse_obj(decoded)
141
+ decoded = decode_token(token)
142
+ SESSION_ID.set(decoded.session_id)
143
+ # Implicit auth assumes used by one user so all users are the same
144
+ USER.set(
145
+ UserData(
146
+ identity_id=decoded.identity_id,
147
+ identity_name=decoded.identity_id,
148
+ )
149
+ )
150
+ return decoded
145
151
  except jwt.ExpiredSignatureError as e:
146
152
  raise AuthError(EXPIRED_TOKEN_ERROR, 401) from e
147
153
  except jwt.DecodeError as e:
@@ -39,7 +39,7 @@ class TokenData(BaseModel):
39
39
 
40
40
  session_id: str
41
41
  exp: Union[float, int, datetime]
42
- identity_id: Optional[str] = None
42
+ identity_id: str
43
43
  identity_name: str
44
44
  identity_email: Optional[str] = None
45
45
  id_token: Optional[str] = None
@@ -56,7 +56,7 @@ class UserData(BaseModel):
56
56
  :param groups: list of groups user belongs to
57
57
  """
58
58
 
59
- identity_id: Optional[str] = None
59
+ identity_id: str
60
60
  identity_name: str
61
61
  identity_email: Optional[str] = None
62
62
  groups: Optional[List[str]] = []
dara/core/auth/utils.py CHANGED
@@ -51,7 +51,7 @@ def decode_token(token: str, **kwargs) -> TokenData:
51
51
 
52
52
 
53
53
  def sign_jwt(
54
- identity_id: Optional[str],
54
+ identity_id: str,
55
55
  identity_name: str,
56
56
  identity_email: Optional[str],
57
57
  groups: List[str],
@@ -304,6 +304,12 @@ class CachedRegistryEntry(BaseModel):
304
304
  return f'{self.__class__.__name__}(cache={self.cache}, uid={self.uid})'
305
305
 
306
306
 
307
+ class NonTabularDataError(Exception):
308
+ """
309
+ Raised when trying to interpret a non-tabular variable as tabular
310
+ """
311
+
312
+
307
313
  class BaseTaskMessage(BaseModel):
308
314
  task_id: str
309
315
 
@@ -407,7 +413,7 @@ class PendingTask(BaseTask):
407
413
  self.cancel_scope.cancel()
408
414
  await self.task_def.cancel()
409
415
 
410
- self.error = Exception('Task was cancelled')
416
+ self.error = anyio.get_cancelled_exc_class()()
411
417
  self.event.set()
412
418
 
413
419
  def add_subscriber(self):
@@ -432,47 +438,6 @@ class PendingTask(BaseTask):
432
438
  return self.result
433
439
 
434
440
 
435
- class PendingValue:
436
- """
437
- An internal class that's used to represent a pending value. Holds a future object that can be awaited by
438
- multiple consumers.
439
- """
440
-
441
- def __init__(self):
442
- self.event = anyio.Event()
443
- self._value = None
444
- self._error = None
445
-
446
- async def wait(self):
447
- """
448
- Wait for the underlying event to be set
449
- """
450
- # Waiting in chunks as otherwise Jupyter blocks the event loop
451
- while not self.event.is_set():
452
- await anyio.sleep(0.01)
453
- if self._error:
454
- raise self._error
455
- return self._value
456
-
457
- def resolve(self, value: Any):
458
- """
459
- Resolve the pending state and send values to the waiting code
460
-
461
- :param value: the value to resolve as the result
462
- """
463
- self._value = value
464
- self.event.set()
465
-
466
- def error(self, exc: Exception):
467
- """
468
- Resolve the pending state with an error and send it to the waiting code
469
-
470
- :param exc: exception to resolve as the result
471
- """
472
- self._error = exc
473
- self.event.set()
474
-
475
-
476
441
  class AnnotatedAction(BaseModel):
477
442
  """
478
443
  Represents a single call to an @action-annotated action.
dara/core/data_utils.py CHANGED
@@ -25,10 +25,9 @@ from pandas import DataFrame
25
25
  from dara.core.base_definitions import CacheType
26
26
  from dara.core.base_definitions import DaraBaseModel as BaseModel
27
27
  from dara.core.interactivity import (
28
- DerivedDataVariable,
28
+ ClientVariable,
29
29
  DerivedVariable,
30
30
  DownloadContent,
31
- NonDataVariable,
32
31
  SideEffect,
33
32
  Variable,
34
33
  )
@@ -172,7 +171,7 @@ class DataFactory(BaseModel):
172
171
 
173
172
  def list_datasets_var(
174
173
  self,
175
- cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL,
174
+ cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL,
176
175
  polling_interval: Optional[int] = None,
177
176
  ) -> DerivedVariable[List[str]]:
178
177
  """
@@ -181,7 +180,7 @@ class DataFactory(BaseModel):
181
180
  :param cache: cache type to get the list of datasets for
182
181
  :param polling_interval: optional polling_interval in seconds for the derived variable
183
182
  """
184
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
183
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
185
184
 
186
185
  return DerivedVariable(
187
186
  lambda cache_val: self.file_store.list_files(cache_val or CacheType.GLOBAL),
@@ -289,21 +288,21 @@ class DataFactory(BaseModel):
289
288
 
290
289
  def read_dataset_var(
291
290
  self,
292
- name: Union[str, NonDataVariable],
293
- cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL,
291
+ name: Union[str, ClientVariable],
292
+ cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL,
294
293
  polling_interval: Optional[int] = None,
295
- ) -> DerivedDataVariable:
294
+ ) -> DerivedVariable:
296
295
  """
297
- Create a DerivedDataVariable which reads a specific dataset from disk
296
+ Create a DerivedVariable which reads a specific dataset from disk
298
297
 
299
298
  :param name: name of the dataset
300
299
  :param cache: cache to get the dataset for
301
300
  :param polling_interval: optional polling interval in seconds for the derived variable
302
301
  """
303
- name_var = name if isinstance(name, NonDataVariable) else Variable(name)
304
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
302
+ name_var = name if isinstance(name, ClientVariable) else Variable(name)
303
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
305
304
 
306
- return DerivedDataVariable(
305
+ return DerivedVariable(
307
306
  self.read_dataset,
308
307
  variables=[name_var, cache_var],
309
308
  cache=CacheType.SESSION,
@@ -320,7 +319,7 @@ class DataFactory(BaseModel):
320
319
  self.file_store.delete_file(cache, name)
321
320
 
322
321
  def delete_dataset_action(
323
- self, name: Union[str, NonDataVariable], cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL
322
+ self, name: Union[str, ClientVariable], cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL
324
323
  ):
325
324
  """
326
325
  Get a SideEffect action which deletes a given dataset
@@ -328,13 +327,13 @@ class DataFactory(BaseModel):
328
327
  :param name: name of the dataset
329
328
  :param cache: cache to remove the daatset for
330
329
  """
331
- name_var = name if isinstance(name, NonDataVariable) else Variable(name)
332
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
330
+ name_var = name if isinstance(name, ClientVariable) else Variable(name)
331
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
333
332
 
334
333
  return SideEffect(lambda ctx: self.delete_dataset(ctx.extras[0], ctx.extras[1]), extras=[name_var, cache_var])
335
334
 
336
335
  def download_dataset_action(
337
- self, name: Union[str, NonDataVariable], cache: Union[CacheType, NonDataVariable] = CacheType.GLOBAL
336
+ self, name: Union[str, ClientVariable], cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL
338
337
  ):
339
338
  """
340
339
  Get a DownloadContent action which downloads a dataset with a given name as a .csv
@@ -342,8 +341,8 @@ class DataFactory(BaseModel):
342
341
  :param name: name of the dataset to download
343
342
  :param cache: cache to download dataset for
344
343
  """
345
- name_var = name if isinstance(name, NonDataVariable) else Variable(name)
346
- cache_var = cache if isinstance(cache, NonDataVariable) else Variable(cache)
344
+ name_var = name if isinstance(name, ClientVariable) else Variable(name)
345
+ cache_var = cache if isinstance(cache, ClientVariable) else Variable(cache)
347
346
 
348
347
  def _resolver(ctx: DownloadContent.Ctx): # type: ignore
349
348
  ds_name, sel_cache = ctx.extras # type: ignore
dara/core/definitions.py CHANGED
@@ -84,22 +84,22 @@ class ErrorHandlingConfig(BaseModel):
84
84
  raw_css: Optional[Any] = None
85
85
  """
86
86
  Raw styling to apply to the displayed error boundary.
87
- Accepts a CSSProperties, dict, str, or NonDataVariable.
87
+ Accepts a CSSProperties, dict, str, or ClientVariable.
88
88
  """
89
89
 
90
90
  @field_validator('raw_css', mode='before')
91
91
  @classmethod
92
92
  def validate_raw_css(cls, value):
93
- from dara.core.interactivity.non_data_variable import NonDataVariable
93
+ from dara.core.interactivity.client_variable import ClientVariable
94
94
 
95
95
  if value is None:
96
96
  return None
97
- if isinstance(value, (str, NonDataVariable, CSSProperties)):
97
+ if isinstance(value, (str, ClientVariable, CSSProperties)):
98
98
  return value
99
99
  if isinstance(value, dict):
100
100
  return {_kebab_to_camel(k): v for k, v in value.items()}
101
101
 
102
- raise ValueError(f'raw_css must be a CSSProperties, dict, str, None or NonDataVariable, got {type(value)}')
102
+ raise ValueError(f'raw_css must be a CSSProperties, dict, str, None or ClientVariable, got {type(value)}')
103
103
 
104
104
  def model_dump(self, *args, **kwargs):
105
105
  result = super().model_dump(*args, **kwargs)
@@ -145,7 +145,7 @@ class ComponentInstance(BaseModel):
145
145
  """
146
146
  Raw styling to apply to the component.
147
147
  Can be an dict/CSSProperties instance representing the `styles` tag, a string injected directly into the CSS of the wrapping component,
148
- or a NonDataVariable resoling to either of the above.
148
+ or a ClientVariable resoling to either of the above.
149
149
 
150
150
  ```python
151
151
 
@@ -216,7 +216,7 @@ class ComponentInstance(BaseModel):
216
216
  @field_validator('raw_css', mode='before')
217
217
  @classmethod
218
218
  def parse_css(cls, css: Optional[Any]):
219
- from dara.core.interactivity.non_data_variable import NonDataVariable
219
+ from dara.core.interactivity.client_variable import ClientVariable
220
220
 
221
221
  if css is None:
222
222
  return None
@@ -225,10 +225,10 @@ class ComponentInstance(BaseModel):
225
225
  if isinstance(css, dict):
226
226
  return {_kebab_to_camel(k): v for k, v in css.items()}
227
227
 
228
- if isinstance(css, (NonDataVariable, CSSProperties, str)):
228
+ if isinstance(css, (ClientVariable, CSSProperties, str)):
229
229
  return css
230
230
 
231
- raise ValueError(f'raw_css must be a CSSProperties, dict, str, None or NonDataVariable, got {type(css)}')
231
+ raise ValueError(f'raw_css must be a CSSProperties, dict, str, None or ClientVariable, got {type(css)}')
232
232
 
233
233
  @classmethod
234
234
  def isinstance(cls, obj: Any) -> bool:
@@ -36,12 +36,14 @@ from dara.core.interactivity.actions import (
36
36
  )
37
37
  from dara.core.interactivity.any_data_variable import AnyDataVariable
38
38
  from dara.core.interactivity.any_variable import AnyVariable
39
+ from dara.core.interactivity.client_variable import ClientVariable
39
40
  from dara.core.interactivity.condition import Condition, Operator
40
41
  from dara.core.interactivity.data_variable import DataVariable
41
42
  from dara.core.interactivity.derived_data_variable import DerivedDataVariable
42
43
  from dara.core.interactivity.derived_variable import DerivedVariable
43
44
  from dara.core.interactivity.non_data_variable import NonDataVariable
44
45
  from dara.core.interactivity.plain_variable import Variable
46
+ from dara.core.interactivity.server_variable import ServerVariable
45
47
  from dara.core.interactivity.state_variable import StateVariable
46
48
  from dara.core.interactivity.switch_variable import SwitchVariable
47
49
  from dara.core.interactivity.url_variable import UrlVariable
@@ -51,6 +53,7 @@ __all__ = [
51
53
  'ActionCtx',
52
54
  'AnyVariable',
53
55
  'AnyDataVariable',
56
+ 'ClientVariable',
54
57
  'DataVariable',
55
58
  'NonDataVariable',
56
59
  'Variable',
@@ -69,6 +72,7 @@ __all__ = [
69
72
  'TriggerVariable',
70
73
  'UpdateVariable',
71
74
  'UpdateVariableImpl',
75
+ 'ServerVariable',
72
76
  'SideEffect',
73
77
  'Condition',
74
78
  'Operator',
@@ -44,7 +44,7 @@ import anyio
44
44
  from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
45
45
  from pandas import DataFrame
46
46
  from pydantic import ConfigDict
47
- from typing_extensions import deprecated
47
+ from typing_extensions import TypeAlias, deprecated
48
48
 
49
49
  from dara.core.base_definitions import (
50
50
  ActionDef,
@@ -54,7 +54,7 @@ from dara.core.base_definitions import (
54
54
  TaskProgressUpdate,
55
55
  )
56
56
  from dara.core.base_definitions import DaraBaseModel as BaseModel
57
- from dara.core.interactivity.data_variable import DataVariable
57
+ from dara.core.interactivity.server_variable import ServerVariable
58
58
  from dara.core.interactivity.state_variable import StateVariable
59
59
  from dara.core.internal.download import generate_download_code
60
60
  from dara.core.internal.registry_lookup import RegistryLookup
@@ -65,10 +65,8 @@ if TYPE_CHECKING:
65
65
  from dara.core.interactivity import (
66
66
  AnyVariable,
67
67
  DerivedVariable,
68
- UrlVariable,
69
68
  Variable,
70
69
  )
71
- from dara.core.internal.cache_store import CacheStore
72
70
 
73
71
 
74
72
  class ActionInputs(BaseModel):
@@ -126,7 +124,7 @@ class UpdateVariableImpl(ActionImpl):
126
124
 
127
125
  py_name = 'UpdateVariable'
128
126
 
129
- variable: Union[Variable, UrlVariable, DataVariable]
127
+ variable: Union[Variable, ServerVariable]
130
128
  value: Any
131
129
 
132
130
  INPUT: ClassVar[str] = '__dara_input__'
@@ -136,19 +134,18 @@ class UpdateVariableImpl(ActionImpl):
136
134
  """Special value for `value` that will toggle the variable value"""
137
135
 
138
136
  async def execute(self, ctx: ActionCtx) -> Any:
139
- if isinstance(self.variable, DataVariable):
137
+ if isinstance(self.variable, ServerVariable):
140
138
  # Update on the backend
141
139
  from dara.core.internal.registries import (
142
- data_variable_registry,
140
+ server_variable_registry,
143
141
  utils_registry,
144
142
  )
145
143
 
146
- store: CacheStore = utils_registry.get('Store')
147
144
  registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
148
145
 
149
- var_entry = await registry_mgr.get(data_variable_registry, self.variable.uid)
150
- DataVariable.update_value(var_entry, store, self.value)
151
- # Don't notify frontend explicitly, all clients will be notified by update_value above
146
+ var_entry = await registry_mgr.get(server_variable_registry, self.variable.uid)
147
+ await ServerVariable.write_value(var_entry, self.value)
148
+ # Don't notify frontend explicitly, all clients will be notified by write_value above
152
149
  return None
153
150
 
154
151
  # for non-data variables just ping frontend with the new value
@@ -173,7 +170,7 @@ class UpdateVariable(AnnotatedAction):
173
170
  @deprecated: Passing in resolvers is deprecated, use `ctx.update` in an `@action` or `UpdateVariableImpl` instead.
174
171
  `UpdateVariableImpl` will be renamed to `UpdateVariable` in Dara 2.0.
175
172
 
176
- The UpdateVariable action can be passed to any `ComponentInstance` prop accepting an action and trigger the update of a Variable, UrlVariable or DataVariable.
173
+ The UpdateVariable action can be passed to any `ComponentInstance` prop accepting an action and trigger the update of a Variable or ServerVariable.
177
174
  The resolver function takes a Context param which will feed the `inputs`: `old` and `new` as well as any `extras` passed through.
178
175
 
179
176
  Below an example of how a resolver might look:
@@ -241,13 +238,13 @@ class UpdateVariable(AnnotatedAction):
241
238
 
242
239
  Ctx: ClassVar[type[UpdateVariableContext]] = UpdateVariableContext
243
240
 
244
- variable: Union[Variable, DataVariable, UrlVariable]
241
+ variable: Union[Variable, ServerVariable]
245
242
  extras: Optional[List[AnyVariable]]
246
243
 
247
244
  def __init__(
248
245
  self,
249
246
  resolver: Callable[[UpdateVariableContext], Any],
250
- variable: Union[Variable, DataVariable, UrlVariable],
247
+ variable: Union[Variable, ServerVariable],
251
248
  extras: Optional[List[AnyVariable]] = None,
252
249
  ):
253
250
  """
@@ -629,13 +626,13 @@ def DownloadContent(
629
626
 
630
627
  ```python
631
628
 
632
- from dara.core import action, ConfigurationBuilder, DataVariable, DownloadContent
629
+ from dara.core import action, ConfigurationBuilder, ServerVariable, DownloadContent
633
630
  from dara.components.components import Button, Stack
634
631
 
635
632
 
636
633
  # generate data, alternatively you could load it from a file
637
634
  df = pandas.DataFrame(data={'x': [1, 2, 3], 'y':[4, 5, 6]})
638
- my_var = DataVariable(df)
635
+ my_var = ServerVariable(df)
639
636
 
640
637
  config = ConfigurationBuilder()
641
638
 
@@ -827,12 +824,12 @@ class ActionCtx:
827
824
  self._on_action = _on_action
828
825
 
829
826
  @overload
830
- async def update(self, variable: DataVariable, value: Optional[DataFrame]): ...
827
+ async def update(self, variable: ServerVariable, value: Optional[DataFrame]): ...
831
828
 
832
829
  @overload
833
- async def update(self, variable: Union[Variable[VariableT], UrlVariable[VariableT]], value: VariableT): ...
830
+ async def update(self, variable: Variable[VariableT], value: VariableT): ...
834
831
 
835
- async def update(self, variable: Union[Variable, UrlVariable, DataVariable], value: Any):
832
+ async def update(self, variable: Union[Variable, ServerVariable], value: Any):
836
833
  """
837
834
  Update a given variable to provided value.
838
835
 
@@ -1115,12 +1112,12 @@ class ActionCtx:
1115
1112
 
1116
1113
  ```python
1117
1114
 
1118
- from dara.core import action, ConfigurationBuilder, DataVariable
1115
+ from dara.core import action, ConfigurationBuilder, ServerVariable
1119
1116
  from dara.components.components import Button, Stack
1120
1117
 
1121
1118
  # generate data, alternatively you could load it from a file
1122
1119
  df = pandas.DataFrame(data={'x': [1, 2, 3], 'y':[4, 5, 6]})
1123
- my_var = DataVariable(df)
1120
+ my_var = ServerVariable(df)
1124
1121
 
1125
1122
  config = ConfigurationBuilder()
1126
1123
 
@@ -1246,6 +1243,7 @@ class ActionCtx:
1246
1243
  task_mgr: TaskManager = utils_registry.get('TaskManager')
1247
1244
 
1248
1245
  task = Task(func=func, args=args, kwargs=kwargs, on_progress=on_progress)
1246
+ task_mgr.register_task(task)
1249
1247
  pending_task = await task_mgr.run_task(task)
1250
1248
  return await pending_task.value()
1251
1249
 
@@ -1330,7 +1328,7 @@ class action:
1330
1328
  ```
1331
1329
  """
1332
1330
 
1333
- Ctx: ClassVar[type[ActionCtx]] = ActionCtx
1331
+ Ctx: TypeAlias = ActionCtx
1334
1332
 
1335
1333
  def __init__(self, func: Callable[..., Any]):
1336
1334
  from dara.core.internal.execute_action import execute_action
@@ -1,139 +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 collections.abc import Awaitable
22
- from typing import Any, Callable, Literal, Optional, TypedDict, Union, cast
23
-
24
- import pandas
25
- from fastapi import UploadFile
26
- from pydantic import ConfigDict
1
+ from typing_extensions import TypeAlias
27
2
 
28
- from dara.core.base_definitions import CachedRegistryEntry, UploadResolverDef
29
3
  from dara.core.interactivity.any_variable import AnyVariable
30
- from dara.core.interactivity.filtering import FilterQuery
31
- from dara.core.internal.cache_store.cache_store import CacheStore
32
- from dara.core.internal.registry_lookup import RegistryLookup
33
- from dara.core.internal.utils import run_user_handler
34
-
35
-
36
- class AnyDataVariable(AnyVariable, abc.ABC):
37
- """
38
- AnyDataVariable represents any variable that is specifically designed to hold datasets (i.e. DataVariable, DerivedDataVariable)
39
-
40
- :param uid: the unique identifier for this variable; if not provided a random one is generated
41
- :param filters: a dictionary of filters to apply to the data
42
- """
43
-
44
- uid: str
45
- filters: Optional[FilterQuery] = None
46
-
47
- def __init__(self, uid: Optional[str] = None, **kwargs) -> None:
48
- super().__init__(uid=uid, **kwargs)
49
-
50
- def filter(self, filters: FilterQuery):
51
- return self.copy(update={'filters': filters}, deep=True)
52
-
53
-
54
- class FieldType(TypedDict):
55
- name: Union[str, tuple[str, ...]]
56
- type: Literal['integer', 'number', 'boolean', 'datetime', 'duration', 'any', 'str']
57
-
58
-
59
- class DataFrameSchema(TypedDict):
60
- fields: list[FieldType]
61
- primaryKey: list[str]
62
-
63
4
 
64
- class DataVariableRegistryEntry(CachedRegistryEntry):
65
- """
66
- Registry entry for DataVariable.
67
- """
5
+ # re-export for backwards compatibility
6
+ from .tabular_variable import * # noqa: F403
68
7
 
69
- type: Literal['plain', 'derived']
70
- get_data: Callable[..., Awaitable[Any]]
71
- """Handler to get the data from the data variable. Defaults to DataVariable.get_value for type=plain, and DerivedDataVariable.get_data for type=derived"""
72
-
73
- get_total_count: Callable[..., Awaitable[int]]
74
- """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"""
75
-
76
- get_schema: Callable[..., Awaitable[DataFrameSchema]]
77
- """Handler to get the schema for data variable. Defaults to DataVariable.get_schema for type=plain, and DerivedDataVariable.get_schema for type=derived"""
78
- model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
79
-
80
-
81
- async def upload(data: UploadFile, data_uid: Optional[str] = None, resolver_id: Optional[str] = None):
82
- """
83
- Handler for uploading data.
84
-
85
- :param data: the file to upload
86
- :param data_uid: optional uid of the data variable to upload to
87
- :param resolver_id: optional id of the upload resolver to use, falls back to default handlers for csv/xlsx
88
- """
89
- from dara.core.interactivity.data_variable import DataVariable
90
- from dara.core.internal.registries import (
91
- data_variable_registry,
92
- upload_resolver_registry,
93
- utils_registry,
94
- )
95
-
96
- store: CacheStore = utils_registry.get('Store')
97
- registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
98
-
99
- if data.filename is None:
100
- raise ValueError('Filename not provided')
101
-
102
- variable = None
103
-
104
- _name, file_type = os.path.splitext(data.filename)
105
-
106
- if data_uid is not None:
107
- try:
108
- variable = await registry_mgr.get(data_variable_registry, data_uid)
109
- except KeyError as e:
110
- raise ValueError(f'Data Variable {data_uid} does not exist') from e
111
-
112
- if variable.type == 'derived':
113
- raise ValueError('Cannot upload data to DerivedDataVariable')
114
-
115
- content = cast(bytes, await data.read())
116
-
117
- resolver = None
118
-
119
- # If Id is provided, lookup the definition from registry
120
- if resolver_id is not None:
121
- resolver_def: UploadResolverDef = await registry_mgr.get(upload_resolver_registry, resolver_id)
122
- resolver = resolver_def.resolver
123
-
124
- if resolver:
125
- content = await run_user_handler(handler=resolver, args=(content, data.filename))
126
- # If resolver is not provided, follow roughly the cl_dataset_parser logic
127
- elif file_type == '.xlsx':
128
- file_object_xlsx = io.BytesIO(content)
129
- content = pandas.read_excel(file_object_xlsx, index_col=None)
130
- content.columns = content.columns.str.replace('Unnamed: *', 'column_', regex=True) # type: ignore
131
- else:
132
- # default to csv
133
- file_object_csv = io.StringIO(content.decode('utf-8'))
134
- content = pandas.read_csv(file_object_csv, index_col=0)
135
- content.columns = content.columns.str.replace('Unnamed: *', 'column_', regex=True) # type: ignore
136
-
137
- # If a data variable is provided, update it with the new content
138
- if variable:
139
- DataVariable.update_value(variable, store, content)
8
+ AnyDataVariable: TypeAlias = AnyVariable
9
+ """
10
+ Deprecated alias. Tabular variables are now DerivedVariable or ServerVariable
11
+ """
@@ -111,7 +111,7 @@ async def get_current_value(variable: dict, timeout: float = 3, raw: bool = Fals
111
111
  user_identity = None
112
112
 
113
113
  if current_user is not None:
114
- user_identity = current_user.identity_id or current_user.identity_name
114
+ user_identity = current_user.identity_id
115
115
  elif isinstance(auth_config, BasicAuthConfig):
116
116
  # basic auth - assume it's the single existing user
117
117
  user_identity = list(auth_config.users.keys())[0]