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.
- dara/core/__init__.py +1 -0
- dara/core/auth/basic.py +13 -7
- dara/core/auth/definitions.py +2 -2
- dara/core/auth/utils.py +1 -1
- dara/core/base_definitions.py +7 -42
- dara/core/data_utils.py +16 -17
- dara/core/definitions.py +8 -8
- dara/core/interactivity/__init__.py +4 -0
- dara/core/interactivity/actions.py +20 -22
- dara/core/interactivity/any_data_variable.py +7 -135
- dara/core/interactivity/any_variable.py +1 -1
- dara/core/interactivity/client_variable.py +71 -0
- dara/core/interactivity/data_variable.py +8 -266
- dara/core/interactivity/derived_data_variable.py +6 -290
- dara/core/interactivity/derived_variable.py +333 -199
- dara/core/interactivity/filtering.py +29 -2
- dara/core/interactivity/loop_variable.py +2 -2
- dara/core/interactivity/non_data_variable.py +5 -68
- dara/core/interactivity/plain_variable.py +87 -14
- dara/core/interactivity/server_variable.py +325 -0
- dara/core/interactivity/state_variable.py +2 -2
- dara/core/interactivity/switch_variable.py +15 -15
- dara/core/interactivity/tabular_variable.py +94 -0
- dara/core/interactivity/url_variable.py +10 -90
- dara/core/internal/cache_store/cache_store.py +5 -20
- dara/core/internal/dependency_resolution.py +27 -69
- dara/core/internal/devtools.py +10 -3
- dara/core/internal/execute_action.py +9 -3
- dara/core/internal/multi_resource_lock.py +70 -0
- dara/core/internal/normalization.py +0 -5
- dara/core/internal/pandas_utils.py +105 -3
- dara/core/internal/pool/definitions.py +1 -1
- dara/core/internal/pool/task_pool.py +1 -1
- dara/core/internal/registries.py +3 -2
- dara/core/internal/registry.py +1 -1
- dara/core/internal/registry_lookup.py +5 -3
- dara/core/internal/routing.py +52 -121
- dara/core/internal/store.py +2 -29
- dara/core/internal/tasks.py +372 -182
- dara/core/internal/utils.py +25 -3
- dara/core/internal/websocket.py +1 -1
- dara/core/js_tooling/js_utils.py +2 -0
- dara/core/logging.py +10 -6
- dara/core/persistence.py +26 -4
- dara/core/umd/dara.core.umd.js +742 -1381
- dara/core/visual/dynamic_component.py +10 -13
- {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/METADATA +10 -10
- {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/RECORD +51 -47
- {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/LICENSE +0 -0
- {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/WHEEL +0 -0
- {dara_core-1.19.1.dist-info → dara_core-1.20.0a1.dist-info}/entry_points.txt +0 -0
dara/core/__init__.py
CHANGED
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=
|
|
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=
|
|
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 =
|
|
143
|
-
SESSION_ID.set(decoded.
|
|
144
|
-
|
|
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:
|
dara/core/auth/definitions.py
CHANGED
|
@@ -39,7 +39,7 @@ class TokenData(BaseModel):
|
|
|
39
39
|
|
|
40
40
|
session_id: str
|
|
41
41
|
exp: Union[float, int, datetime]
|
|
42
|
-
identity_id:
|
|
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:
|
|
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
dara/core/base_definitions.py
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
293
|
-
cache: Union[CacheType,
|
|
291
|
+
name: Union[str, ClientVariable],
|
|
292
|
+
cache: Union[CacheType, ClientVariable] = CacheType.GLOBAL,
|
|
294
293
|
polling_interval: Optional[int] = None,
|
|
295
|
-
) ->
|
|
294
|
+
) -> DerivedVariable:
|
|
296
295
|
"""
|
|
297
|
-
Create a
|
|
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,
|
|
304
|
-
cache_var = cache if isinstance(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
|
|
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,
|
|
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,
|
|
332
|
-
cache_var = cache if isinstance(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,
|
|
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,
|
|
346
|
-
cache_var = cache if isinstance(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
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
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, (
|
|
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
|
|
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.
|
|
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,
|
|
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,
|
|
137
|
+
if isinstance(self.variable, ServerVariable):
|
|
140
138
|
# Update on the backend
|
|
141
139
|
from dara.core.internal.registries import (
|
|
142
|
-
|
|
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(
|
|
150
|
-
|
|
151
|
-
# Don't notify frontend explicitly, all clients will be notified by
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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:
|
|
827
|
+
async def update(self, variable: ServerVariable, value: Optional[DataFrame]): ...
|
|
831
828
|
|
|
832
829
|
@overload
|
|
833
|
-
async def update(self, variable:
|
|
830
|
+
async def update(self, variable: Variable[VariableT], value: VariableT): ...
|
|
834
831
|
|
|
835
|
-
async def update(self, variable: Union[Variable,
|
|
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,
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
Registry entry for DataVariable.
|
|
67
|
-
"""
|
|
5
|
+
# re-export for backwards compatibility
|
|
6
|
+
from .tabular_variable import * # noqa: F403
|
|
68
7
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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]
|