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
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,15 @@ 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
47
+ from dara.core.interactivity.state_variable import StateVariable
45
48
  from dara.core.interactivity.switch_variable import SwitchVariable
46
49
  from dara.core.interactivity.url_variable import UrlVariable
47
50
 
@@ -50,9 +53,11 @@ __all__ = [
50
53
  'ActionCtx',
51
54
  'AnyVariable',
52
55
  'AnyDataVariable',
56
+ 'ClientVariable',
53
57
  'DataVariable',
54
58
  'NonDataVariable',
55
59
  'Variable',
60
+ 'StateVariable',
56
61
  'SwitchVariable',
57
62
  'DerivedVariable',
58
63
  'DerivedDataVariable',
@@ -67,6 +72,7 @@ __all__ = [
67
72
  'TriggerVariable',
68
73
  'UpdateVariable',
69
74
  'UpdateVariableImpl',
75
+ 'ServerVariable',
70
76
  'SideEffect',
71
77
  'Condition',
72
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,8 @@ 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
+ from dara.core.interactivity.state_variable import StateVariable
58
59
  from dara.core.internal.download import generate_download_code
59
60
  from dara.core.internal.registry_lookup import RegistryLookup
60
61
  from dara.core.internal.utils import run_user_handler
@@ -64,10 +65,8 @@ if TYPE_CHECKING:
64
65
  from dara.core.interactivity import (
65
66
  AnyVariable,
66
67
  DerivedVariable,
67
- UrlVariable,
68
68
  Variable,
69
69
  )
70
- from dara.core.internal.cache_store import CacheStore
71
70
 
72
71
 
73
72
  class ActionInputs(BaseModel):
@@ -125,7 +124,7 @@ class UpdateVariableImpl(ActionImpl):
125
124
 
126
125
  py_name = 'UpdateVariable'
127
126
 
128
- variable: Union[Variable, UrlVariable, DataVariable]
127
+ variable: Union[Variable, ServerVariable]
129
128
  value: Any
130
129
 
131
130
  INPUT: ClassVar[str] = '__dara_input__'
@@ -135,19 +134,18 @@ class UpdateVariableImpl(ActionImpl):
135
134
  """Special value for `value` that will toggle the variable value"""
136
135
 
137
136
  async def execute(self, ctx: ActionCtx) -> Any:
138
- if isinstance(self.variable, DataVariable):
137
+ if isinstance(self.variable, ServerVariable):
139
138
  # Update on the backend
140
139
  from dara.core.internal.registries import (
141
- data_variable_registry,
140
+ server_variable_registry,
142
141
  utils_registry,
143
142
  )
144
143
 
145
- store: CacheStore = utils_registry.get('Store')
146
144
  registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
147
145
 
148
- var_entry = await registry_mgr.get(data_variable_registry, self.variable.uid)
149
- DataVariable.update_value(var_entry, store, self.value)
150
- # 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
151
149
  return None
152
150
 
153
151
  # for non-data variables just ping frontend with the new value
@@ -172,7 +170,7 @@ class UpdateVariable(AnnotatedAction):
172
170
  @deprecated: Passing in resolvers is deprecated, use `ctx.update` in an `@action` or `UpdateVariableImpl` instead.
173
171
  `UpdateVariableImpl` will be renamed to `UpdateVariable` in Dara 2.0.
174
172
 
175
- 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.
176
174
  The resolver function takes a Context param which will feed the `inputs`: `old` and `new` as well as any `extras` passed through.
177
175
 
178
176
  Below an example of how a resolver might look:
@@ -240,13 +238,13 @@ class UpdateVariable(AnnotatedAction):
240
238
 
241
239
  Ctx: ClassVar[type[UpdateVariableContext]] = UpdateVariableContext
242
240
 
243
- variable: Union[Variable, DataVariable, UrlVariable]
241
+ variable: Union[Variable, ServerVariable]
244
242
  extras: Optional[List[AnyVariable]]
245
243
 
246
244
  def __init__(
247
245
  self,
248
246
  resolver: Callable[[UpdateVariableContext], Any],
249
- variable: Union[Variable, DataVariable, UrlVariable],
247
+ variable: Union[Variable, ServerVariable],
250
248
  extras: Optional[List[AnyVariable]] = None,
251
249
  ):
252
250
  """
@@ -628,13 +626,13 @@ def DownloadContent(
628
626
 
629
627
  ```python
630
628
 
631
- from dara.core import action, ConfigurationBuilder, DataVariable, DownloadContent
629
+ from dara.core import action, ConfigurationBuilder, ServerVariable, DownloadContent
632
630
  from dara.components.components import Button, Stack
633
631
 
634
632
 
635
633
  # generate data, alternatively you could load it from a file
636
634
  df = pandas.DataFrame(data={'x': [1, 2, 3], 'y':[4, 5, 6]})
637
- my_var = DataVariable(df)
635
+ my_var = ServerVariable(df)
638
636
 
639
637
  config = ConfigurationBuilder()
640
638
 
@@ -826,12 +824,12 @@ class ActionCtx:
826
824
  self._on_action = _on_action
827
825
 
828
826
  @overload
829
- async def update(self, variable: DataVariable, value: Optional[DataFrame]): ...
827
+ async def update(self, variable: ServerVariable, value: Optional[DataFrame]): ...
830
828
 
831
829
  @overload
832
- async def update(self, variable: Union[Variable[VariableT], UrlVariable[VariableT]], value: VariableT): ...
830
+ async def update(self, variable: Variable[VariableT], value: VariableT): ...
833
831
 
834
- async def update(self, variable: Union[Variable, UrlVariable, DataVariable], value: Any):
832
+ async def update(self, variable: Union[Variable, ServerVariable], value: Any):
835
833
  """
836
834
  Update a given variable to provided value.
837
835
 
@@ -1114,12 +1112,12 @@ class ActionCtx:
1114
1112
 
1115
1113
  ```python
1116
1114
 
1117
- from dara.core import action, ConfigurationBuilder, DataVariable
1115
+ from dara.core import action, ConfigurationBuilder, ServerVariable
1118
1116
  from dara.components.components import Button, Stack
1119
1117
 
1120
1118
  # generate data, alternatively you could load it from a file
1121
1119
  df = pandas.DataFrame(data={'x': [1, 2, 3], 'y':[4, 5, 6]})
1122
- my_var = DataVariable(df)
1120
+ my_var = ServerVariable(df)
1123
1121
 
1124
1122
  config = ConfigurationBuilder()
1125
1123
 
@@ -1245,6 +1243,7 @@ class ActionCtx:
1245
1243
  task_mgr: TaskManager = utils_registry.get('TaskManager')
1246
1244
 
1247
1245
  task = Task(func=func, args=args, kwargs=kwargs, on_progress=on_progress)
1246
+ task_mgr.register_task(task)
1248
1247
  pending_task = await task_mgr.run_task(task)
1249
1248
  return await pending_task.value()
1250
1249
 
@@ -1329,7 +1328,7 @@ class action:
1329
1328
  ```
1330
1329
  """
1331
1330
 
1332
- Ctx: ClassVar[type[ActionCtx]] = ActionCtx
1331
+ Ctx: TypeAlias = ActionCtx
1333
1332
 
1334
1333
  def __init__(self, func: Callable[..., Any]):
1335
1334
  from dara.core.internal.execute_action import execute_action
@@ -1456,6 +1455,11 @@ class action:
1456
1455
  dynamic_kwargs: Dict[str, AnyVariable] = {}
1457
1456
  static_kwargs: Dict[str, Any] = {}
1458
1457
  for key, kwarg in all_kwargs.items():
1458
+ if isinstance(kwarg, StateVariable):
1459
+ raise ValueError(
1460
+ 'StateVariable cannot be used as input to actions. '
1461
+ "StateVariables are internal variables for tracking DerivedVariable ephemeral client state shouldn't be used as action payloads."
1462
+ )
1459
1463
  if isinstance(kwarg, AnyVariable):
1460
1464
  dynamic_kwargs[key] = kwarg
1461
1465
  else:
@@ -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]