dara-core 1.15.6a1__py3-none-any.whl → 1.16.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.
- dara/core/__init__.py +16 -27
- dara/core/auth/base.py +3 -2
- dara/core/auth/definitions.py +0 -3
- dara/core/auth/utils.py +1 -1
- dara/core/base_definitions.py +122 -65
- dara/core/cli.py +12 -0
- dara/core/configuration.py +5 -8
- dara/core/defaults.py +0 -3
- dara/core/definitions.py +95 -231
- dara/core/interactivity/__init__.py +12 -18
- dara/core/interactivity/actions.py +22 -19
- dara/core/interactivity/any_data_variable.py +2 -4
- dara/core/interactivity/any_variable.py +10 -2
- dara/core/interactivity/condition.py +7 -10
- dara/core/interactivity/data_variable.py +11 -12
- dara/core/interactivity/derived_data_variable.py +7 -7
- dara/core/interactivity/derived_variable.py +20 -17
- dara/core/interactivity/filtering.py +1 -1
- dara/core/interactivity/plain_variable.py +53 -6
- dara/core/interactivity/url_variable.py +7 -6
- dara/core/internal/download.py +1 -1
- dara/core/internal/encoder_registry.py +14 -0
- dara/core/internal/hashing.py +1 -1
- dara/core/internal/normalization.py +0 -24
- dara/core/internal/routing.py +10 -10
- dara/core/internal/scheduler.py +3 -2
- dara/core/internal/settings.py +2 -4
- dara/core/internal/store.py +0 -3
- dara/core/internal/tasks.py +2 -2
- dara/core/internal/websocket.py +29 -20
- dara/core/js_tooling/js_utils.py +1 -1
- dara/core/main.py +2 -2
- dara/core/persistence.py +12 -4
- dara/core/umd/dara.core.umd.js +13 -277
- dara/core/visual/components/__init__.py +0 -3
- dara/core/visual/components/fallback.py +3 -3
- dara/core/visual/components/invalid_component.py +3 -3
- dara/core/visual/components/menu.py +3 -3
- dara/core/visual/components/progress_tracker.py +3 -2
- dara/core/visual/components/raw_string.py +3 -3
- dara/core/visual/components/router_content.py +3 -3
- dara/core/visual/components/sidebar_frame.py +3 -3
- dara/core/visual/components/topbar_frame.py +3 -3
- dara/core/visual/css/__init__.py +2 -6
- dara/core/visual/dynamic_component.py +3 -6
- {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/METADATA +13 -12
- {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/RECORD +50 -51
- dara/core/visual/components/for_cmp.py +0 -150
- {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/LICENSE +0 -0
- {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/WHEEL +0 -0
- {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/entry_points.txt +0 -0
|
@@ -35,13 +35,14 @@ from typing import (
|
|
|
35
35
|
Optional,
|
|
36
36
|
TypeVar,
|
|
37
37
|
Union,
|
|
38
|
+
cast,
|
|
38
39
|
overload,
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
import anyio
|
|
42
43
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
43
44
|
from pandas import DataFrame
|
|
44
|
-
from pydantic import BaseModel
|
|
45
|
+
from pydantic import BaseModel, ConfigDict
|
|
45
46
|
from typing_extensions import deprecated
|
|
46
47
|
|
|
47
48
|
from dara.core.base_definitions import (
|
|
@@ -49,7 +50,6 @@ from dara.core.base_definitions import (
|
|
|
49
50
|
ActionImpl,
|
|
50
51
|
ActionResolverDef,
|
|
51
52
|
AnnotatedAction,
|
|
52
|
-
TemplateMarker,
|
|
53
53
|
)
|
|
54
54
|
from dara.core.interactivity.data_variable import DataVariable
|
|
55
55
|
from dara.core.internal.download import generate_download_code
|
|
@@ -72,8 +72,7 @@ class ActionInputs(BaseModel):
|
|
|
72
72
|
Base class for all action inputs
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
extra = 'allow'
|
|
75
|
+
model_config = ConfigDict(extra='allow')
|
|
77
76
|
|
|
78
77
|
|
|
79
78
|
class ActionContext(BaseModel):
|
|
@@ -96,7 +95,7 @@ class ComponentActionContext(ActionContext):
|
|
|
96
95
|
ActionContext for actions that only require component value
|
|
97
96
|
"""
|
|
98
97
|
|
|
99
|
-
inputs: ComponentActionInputs
|
|
98
|
+
inputs: ComponentActionInputs # type: ignore
|
|
100
99
|
|
|
101
100
|
|
|
102
101
|
class UpdateVariableImpl(ActionImpl):
|
|
@@ -161,7 +160,7 @@ class UpdateVariableInputs(ActionInputs):
|
|
|
161
160
|
|
|
162
161
|
|
|
163
162
|
class UpdateVariableContext(ActionContext):
|
|
164
|
-
inputs: UpdateVariableInputs
|
|
163
|
+
inputs: UpdateVariableInputs # type: ignore
|
|
165
164
|
|
|
166
165
|
|
|
167
166
|
@deprecated('Use @action or `UpdateVariableImpl` for simple cases')
|
|
@@ -236,16 +235,16 @@ class UpdateVariable(AnnotatedAction):
|
|
|
236
235
|
```
|
|
237
236
|
"""
|
|
238
237
|
|
|
239
|
-
Ctx = UpdateVariableContext
|
|
238
|
+
Ctx: ClassVar[type[UpdateVariableContext]] = UpdateVariableContext
|
|
240
239
|
|
|
241
240
|
variable: Union[Variable, DataVariable, UrlVariable]
|
|
242
|
-
extras: Optional[List[
|
|
241
|
+
extras: Optional[List[AnyVariable]]
|
|
243
242
|
|
|
244
243
|
def __init__(
|
|
245
244
|
self,
|
|
246
245
|
resolver: Callable[[UpdateVariableContext], Any],
|
|
247
246
|
variable: Union[Variable, DataVariable, UrlVariable],
|
|
248
|
-
extras: Optional[List[
|
|
247
|
+
extras: Optional[List[AnyVariable]] = None,
|
|
249
248
|
):
|
|
250
249
|
"""
|
|
251
250
|
:param resolver: a function to resolve the new value for the variable. Takes one arguments: containing a context of type `Updatevariable.Ctx`
|
|
@@ -253,7 +252,8 @@ class UpdateVariable(AnnotatedAction):
|
|
|
253
252
|
:param extras: any extra variables to resolve and pass to the resolution function context
|
|
254
253
|
"""
|
|
255
254
|
|
|
256
|
-
async def _update(ctx: action.Ctx, **kwargs):
|
|
255
|
+
async def _update(ctx: action.Ctx, **kwargs): # type: ignore
|
|
256
|
+
ctx = cast(ActionCtx, ctx) # type: ignore
|
|
257
257
|
old = kwargs.pop('old')
|
|
258
258
|
extras = [kwargs[f'kwarg_{idx}'] for idx in range(len(kwargs))]
|
|
259
259
|
old_ctx = UpdateVariableContext(inputs=UpdateVariableInputs(old=old, new=ctx.input), extras=extras)
|
|
@@ -371,7 +371,7 @@ class NavigateToImpl(ActionImpl):
|
|
|
371
371
|
|
|
372
372
|
py_name = 'NavigateTo'
|
|
373
373
|
|
|
374
|
-
url: Optional[str]
|
|
374
|
+
url: Optional[str] = None
|
|
375
375
|
new_tab: bool
|
|
376
376
|
|
|
377
377
|
|
|
@@ -426,7 +426,8 @@ def NavigateTo(
|
|
|
426
426
|
return NavigateToImpl(url=url, new_tab=new_tab)
|
|
427
427
|
|
|
428
428
|
# Otherwise create a new @action with the provided resolver
|
|
429
|
-
async def _navigate(ctx: action.Ctx, **kwargs):
|
|
429
|
+
async def _navigate(ctx: action.Ctx, **kwargs): # type: ignore
|
|
430
|
+
ctx = cast(ActionCtx, ctx) # type: ignore
|
|
430
431
|
extras = [kwargs[f'kwarg_{idx}'] for idx in range(len(kwargs))]
|
|
431
432
|
old_ctx = ComponentActionContext(inputs=ComponentActionInputs(value=ctx.input), extras=extras)
|
|
432
433
|
result = await run_user_handler(url, args=(old_ctx,)) # type: ignore
|
|
@@ -573,8 +574,8 @@ class Notify(ActionImpl):
|
|
|
573
574
|
status: NotificationStatus
|
|
574
575
|
title: str
|
|
575
576
|
|
|
576
|
-
Status = NotificationStatus
|
|
577
|
-
Ctx = ComponentActionContext
|
|
577
|
+
Status: ClassVar[type[NotificationStatus]] = NotificationStatus
|
|
578
|
+
Ctx: ClassVar[type[ComponentActionContext]] = ComponentActionContext
|
|
578
579
|
"""@deprecated retained for backwards compatibility, to be removed in 2.0"""
|
|
579
580
|
|
|
580
581
|
|
|
@@ -660,7 +661,8 @@ def DownloadContent(
|
|
|
660
661
|
```
|
|
661
662
|
"""
|
|
662
663
|
|
|
663
|
-
async def _download(ctx: action.Ctx, **kwargs):
|
|
664
|
+
async def _download(ctx: action.Ctx, **kwargs): # type: ignore
|
|
665
|
+
ctx = cast(ActionCtx, ctx) # type: ignore
|
|
664
666
|
extras = [kwargs[f'kwarg_{idx}'] for idx in range(len(kwargs))]
|
|
665
667
|
old_ctx = ComponentActionContext(inputs=ComponentActionInputs(value=ctx.input), extras=extras)
|
|
666
668
|
result = await run_user_handler(resolver, args=(old_ctx,))
|
|
@@ -725,7 +727,7 @@ class DownloadVariable(ActionImpl):
|
|
|
725
727
|
@deprecated('Use @action instead')
|
|
726
728
|
def SideEffect(
|
|
727
729
|
function: Callable[[ComponentActionContext], Any],
|
|
728
|
-
extras: Optional[List[
|
|
730
|
+
extras: Optional[List[AnyVariable]] = None,
|
|
729
731
|
block: bool = False,
|
|
730
732
|
):
|
|
731
733
|
"""
|
|
@@ -765,7 +767,8 @@ def SideEffect(
|
|
|
765
767
|
```
|
|
766
768
|
"""
|
|
767
769
|
|
|
768
|
-
async def _effect(ctx: action.Ctx, **kwargs):
|
|
770
|
+
async def _effect(ctx: action.Ctx, **kwargs): # type: ignore
|
|
771
|
+
ctx = cast(ActionCtx, ctx) # type: ignore
|
|
769
772
|
extras = [kwargs[f'kwarg_{idx}'] for idx in range(len(kwargs))]
|
|
770
773
|
old_ctx = ComponentActionContext(inputs=ComponentActionInputs(value=ctx.input), extras=extras)
|
|
771
774
|
# Simply run the user handler
|
|
@@ -1274,7 +1277,7 @@ class action:
|
|
|
1274
1277
|
```
|
|
1275
1278
|
"""
|
|
1276
1279
|
|
|
1277
|
-
Ctx: ClassVar =
|
|
1280
|
+
Ctx: ClassVar[type[ActionContext]] = ActionContext
|
|
1278
1281
|
|
|
1279
1282
|
def __init__(self, func: Callable[..., Any]):
|
|
1280
1283
|
from dara.core.internal.execute_action import execute_action
|
|
@@ -1337,7 +1340,7 @@ class action:
|
|
|
1337
1340
|
...
|
|
1338
1341
|
|
|
1339
1342
|
@overload
|
|
1340
|
-
def __call__(self, *args: Any, **kwargs: Any) -> AnnotatedAction:
|
|
1343
|
+
def __call__(self, *args: Any, **kwargs: Any) -> AnnotatedAction: # type: ignore
|
|
1341
1344
|
...
|
|
1342
1345
|
|
|
1343
1346
|
def __call__(self, *args, **kwargs) -> Union[AnnotatedAction, Any]:
|
|
@@ -22,6 +22,7 @@ from typing import Any, Awaitable, Callable, Literal, Optional, TypedDict, Union
|
|
|
22
22
|
|
|
23
23
|
import pandas
|
|
24
24
|
from fastapi import UploadFile
|
|
25
|
+
from pydantic import ConfigDict
|
|
25
26
|
|
|
26
27
|
from dara.core.base_definitions import CachedRegistryEntry, UploadResolverDef
|
|
27
28
|
from dara.core.interactivity.any_variable import AnyVariable
|
|
@@ -73,10 +74,7 @@ class DataVariableRegistryEntry(CachedRegistryEntry):
|
|
|
73
74
|
|
|
74
75
|
get_schema: Callable[..., Awaitable[DataFrameSchema]]
|
|
75
76
|
"""Handler to get the schema for data variable. Defaults to DataVariable.get_schema for type=plain, and DerivedDataVariable.get_schema for type=derived"""
|
|
76
|
-
|
|
77
|
-
class Config:
|
|
78
|
-
extra = 'forbid'
|
|
79
|
-
arbitrary_types_allowed = True
|
|
77
|
+
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
|
|
80
78
|
|
|
81
79
|
|
|
82
80
|
async def upload(data: UploadFile, data_uid: Optional[str] = None, resolver_id: Optional[str] = None):
|
|
@@ -27,10 +27,12 @@ from typing import Any, Callable, Dict, Optional, Set
|
|
|
27
27
|
|
|
28
28
|
import anyio
|
|
29
29
|
from fastapi.encoders import jsonable_encoder
|
|
30
|
-
from pydantic import
|
|
30
|
+
from pydantic import ConfigDict
|
|
31
31
|
|
|
32
32
|
from dara.core.auth.definitions import SESSION_ID, USER, UserData
|
|
33
|
-
from dara.core.base_definitions import BaseTask
|
|
33
|
+
from dara.core.base_definitions import BaseTask
|
|
34
|
+
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
35
|
+
from dara.core.base_definitions import PendingTask
|
|
34
36
|
from dara.core.interactivity.condition import Condition, Operator
|
|
35
37
|
from dara.core.internal.cache_store import CacheStore
|
|
36
38
|
from dara.core.internal.tasks import TaskManager
|
|
@@ -275,6 +277,8 @@ class AnyVariable(BaseModel, abc.ABC):
|
|
|
275
277
|
Base class for all variables. Used for typing to specify that any variable can be provided.
|
|
276
278
|
"""
|
|
277
279
|
|
|
280
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
281
|
+
|
|
278
282
|
uid: str
|
|
279
283
|
|
|
280
284
|
def __init__(self, uid: Optional[str] = None, **kwargs) -> None:
|
|
@@ -328,6 +332,10 @@ class AnyVariable(BaseModel, abc.ABC):
|
|
|
328
332
|
assert_no_context('ctx.reset')
|
|
329
333
|
return ResetVariables(variables=[self])
|
|
330
334
|
|
|
335
|
+
@classmethod
|
|
336
|
+
def isinstance(cls, obj: Any) -> bool:
|
|
337
|
+
return isinstance(obj, cls)
|
|
338
|
+
|
|
331
339
|
async def get_current_value(self, timeout: float = 3) -> Any:
|
|
332
340
|
"""
|
|
333
341
|
Retrieve the current value of the variable for the current user
|
|
@@ -18,11 +18,9 @@ limitations under the License.
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
from enum import Enum
|
|
21
|
-
from typing import TYPE_CHECKING, Union
|
|
21
|
+
from typing import TYPE_CHECKING, ClassVar, Union
|
|
22
22
|
|
|
23
|
-
from
|
|
24
|
-
|
|
25
|
-
from dara.core.base_definitions import TemplateMarker
|
|
23
|
+
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
26
24
|
|
|
27
25
|
# Type-only imports
|
|
28
26
|
if TYPE_CHECKING:
|
|
@@ -39,13 +37,12 @@ class Operator(Enum):
|
|
|
39
37
|
TRUTHY = 'truthy'
|
|
40
38
|
|
|
41
39
|
|
|
40
|
+
OperatorType = type[Operator]
|
|
41
|
+
|
|
42
|
+
|
|
42
43
|
class Condition(BaseModel):
|
|
43
44
|
operator: Operator
|
|
44
45
|
other: Union[BaseModel, int, float, str, bool, None, AnyVariable]
|
|
45
|
-
variable:
|
|
46
|
-
|
|
47
|
-
Operator = Operator
|
|
46
|
+
variable: AnyVariable
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
# This makes it properly check the union type rather than coercing to variable type
|
|
51
|
-
smart_union = True
|
|
48
|
+
Operator: ClassVar[OperatorType] = Operator
|
|
@@ -23,7 +23,12 @@ from typing import Optional, Union, cast
|
|
|
23
23
|
from anyio.abc import TaskGroup
|
|
24
24
|
from pandas import DataFrame
|
|
25
25
|
from pandas.io.json._table_schema import build_table_schema
|
|
26
|
-
from pydantic import
|
|
26
|
+
from pydantic import (
|
|
27
|
+
BaseModel,
|
|
28
|
+
ConfigDict,
|
|
29
|
+
SerializerFunctionWrapHandler,
|
|
30
|
+
model_serializer,
|
|
31
|
+
)
|
|
27
32
|
|
|
28
33
|
from dara.core.base_definitions import BaseCachePolicy, Cache, CacheArgType
|
|
29
34
|
from dara.core.interactivity.any_data_variable import (
|
|
@@ -67,11 +72,7 @@ class DataVariable(AnyDataVariable):
|
|
|
67
72
|
uid: str
|
|
68
73
|
filters: Optional[FilterQuery] = None
|
|
69
74
|
cache: Optional[BaseCachePolicy] = None
|
|
70
|
-
|
|
71
|
-
class Config:
|
|
72
|
-
extra = 'forbid'
|
|
73
|
-
arbitrary_types_allowed = True
|
|
74
|
-
use_enum_values = True
|
|
75
|
+
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True, use_enum_values=True)
|
|
75
76
|
|
|
76
77
|
def __init__(
|
|
77
78
|
self,
|
|
@@ -304,8 +305,9 @@ class DataVariable(AnyDataVariable):
|
|
|
304
305
|
|
|
305
306
|
return UpdateVariableImpl(variable=self, value=value)
|
|
306
307
|
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
@model_serializer(mode='wrap')
|
|
309
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
310
|
+
parent_dict = nxt(self)
|
|
309
311
|
if 'data' in parent_dict:
|
|
310
312
|
parent_dict.pop('data') # make sure data is not included in the serialised dict
|
|
311
313
|
return {**parent_dict, '__typename': 'DataVariable', 'uid': str(parent_dict['uid'])}
|
|
@@ -319,7 +321,4 @@ class DataStoreEntry(BaseModel):
|
|
|
319
321
|
|
|
320
322
|
data: Optional[DataFrame] = None
|
|
321
323
|
path: Optional[str] = None
|
|
322
|
-
|
|
323
|
-
class Config:
|
|
324
|
-
extra = 'forbid'
|
|
325
|
-
arbitrary_types_allowed = True
|
|
324
|
+
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
|
|
@@ -23,6 +23,7 @@ from uuid import uuid4
|
|
|
23
23
|
|
|
24
24
|
from pandas import DataFrame
|
|
25
25
|
from pandas.io.json._table_schema import build_table_schema
|
|
26
|
+
from pydantic import ConfigDict, SerializerFunctionWrapHandler, model_serializer
|
|
26
27
|
|
|
27
28
|
from dara.core.base_definitions import (
|
|
28
29
|
BaseTask,
|
|
@@ -66,11 +67,9 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
|
|
|
66
67
|
uid: str
|
|
67
68
|
filters: Optional[FilterQuery] = None
|
|
68
69
|
variables: List[AnyVariable]
|
|
69
|
-
polling_interval: Optional[int]
|
|
70
|
-
deps: Optional[List[AnyVariable]]
|
|
71
|
-
|
|
72
|
-
class Config:
|
|
73
|
-
extra = 'forbid'
|
|
70
|
+
polling_interval: Optional[int] = None
|
|
71
|
+
deps: Optional[List[AnyVariable]] = None
|
|
72
|
+
model_config = ConfigDict(extra='forbid')
|
|
74
73
|
|
|
75
74
|
def __init__(
|
|
76
75
|
self,
|
|
@@ -365,8 +364,9 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
|
|
|
365
364
|
|
|
366
365
|
return await cls.get_data(dv_entry, data_entry, dv_result['cache_key'], store, filters)
|
|
367
366
|
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
@model_serializer(mode='wrap')
|
|
368
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
369
|
+
parent_dict = nxt(self)
|
|
370
370
|
# nested is not supported for DerivedDataVariable so remove from serialised form
|
|
371
371
|
# it's included because we inherit from DV which has the field
|
|
372
372
|
parent_dict.pop('nested')
|
|
@@ -24,15 +24,22 @@ from typing import (
|
|
|
24
24
|
Any,
|
|
25
25
|
Awaitable,
|
|
26
26
|
Callable,
|
|
27
|
-
Dict,
|
|
28
27
|
Generic,
|
|
29
28
|
List,
|
|
30
29
|
Optional,
|
|
31
30
|
TypeVar,
|
|
32
31
|
Union,
|
|
32
|
+
cast,
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
from pydantic import
|
|
35
|
+
from pydantic import (
|
|
36
|
+
ConfigDict,
|
|
37
|
+
Field,
|
|
38
|
+
SerializerFunctionWrapHandler,
|
|
39
|
+
ValidationInfo,
|
|
40
|
+
field_validator,
|
|
41
|
+
model_serializer,
|
|
42
|
+
)
|
|
36
43
|
from typing_extensions import TypedDict
|
|
37
44
|
|
|
38
45
|
from dara.core.base_definitions import (
|
|
@@ -76,13 +83,10 @@ class DerivedVariable(NonDataVariable, Generic[VariableType]):
|
|
|
76
83
|
cache: Optional[BaseCachePolicy]
|
|
77
84
|
variables: List[AnyVariable]
|
|
78
85
|
polling_interval: Optional[int]
|
|
79
|
-
deps: Optional[List[AnyVariable]]
|
|
86
|
+
deps: Optional[List[AnyVariable]] = Field(validate_default=True)
|
|
80
87
|
nested: List[str] = []
|
|
81
88
|
uid: str
|
|
82
|
-
|
|
83
|
-
class Config:
|
|
84
|
-
extra = 'forbid'
|
|
85
|
-
use_enum_values = True
|
|
89
|
+
model_config = ConfigDict(extra='forbid', use_enum_values=True)
|
|
86
90
|
|
|
87
91
|
def __init__(
|
|
88
92
|
self,
|
|
@@ -130,7 +134,7 @@ class DerivedVariable(NonDataVariable, Generic[VariableType]):
|
|
|
130
134
|
# Explicitly disallow run_as_task within a Jupyter environment
|
|
131
135
|
if run_as_task:
|
|
132
136
|
try:
|
|
133
|
-
from IPython import get_ipython
|
|
137
|
+
from IPython import get_ipython # type: ignore
|
|
134
138
|
except ImportError:
|
|
135
139
|
pass
|
|
136
140
|
else:
|
|
@@ -164,20 +168,20 @@ class DerivedVariable(NonDataVariable, Generic[VariableType]):
|
|
|
164
168
|
),
|
|
165
169
|
)
|
|
166
170
|
|
|
167
|
-
@
|
|
171
|
+
@field_validator('deps', mode='before')
|
|
168
172
|
@classmethod
|
|
169
|
-
def validate_deps(cls, deps: Any,
|
|
173
|
+
def validate_deps(cls, deps: Any, info: ValidationInfo) -> List[AnyVariable]:
|
|
170
174
|
"""
|
|
171
175
|
If deps is not specified, set deps to include all variables used
|
|
172
176
|
"""
|
|
173
177
|
if deps is None:
|
|
174
178
|
# This will always be set on the variable with the type verified by pydantic
|
|
175
|
-
return
|
|
179
|
+
return cast(List[AnyVariable], info.data.get('variables'))
|
|
176
180
|
|
|
177
181
|
return deps
|
|
178
182
|
|
|
179
183
|
def get(self, key: str):
|
|
180
|
-
return self.
|
|
184
|
+
return self.model_copy(update={'nested': [*self.nested, key]}, deep=True)
|
|
181
185
|
|
|
182
186
|
def trigger(self, force: bool = True):
|
|
183
187
|
"""
|
|
@@ -479,8 +483,9 @@ class DerivedVariable(NonDataVariable, Generic[VariableType]):
|
|
|
479
483
|
return True
|
|
480
484
|
return False
|
|
481
485
|
|
|
482
|
-
|
|
483
|
-
|
|
486
|
+
@model_serializer(mode='wrap')
|
|
487
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
488
|
+
parent_dict = nxt(self)
|
|
484
489
|
return {**parent_dict, '__typename': 'DerivedVariable', 'uid': str(parent_dict['uid'])}
|
|
485
490
|
|
|
486
491
|
|
|
@@ -492,9 +497,7 @@ class DerivedVariableRegistryEntry(CachedRegistryEntry):
|
|
|
492
497
|
polling_interval: Optional[int]
|
|
493
498
|
get_value: Callable[..., Awaitable[Any]]
|
|
494
499
|
"""Handler to get the value of the derived variable. Defaults to DerivedVariable.get_value, should match the signature"""
|
|
495
|
-
|
|
496
|
-
class Config:
|
|
497
|
-
extra = 'forbid'
|
|
500
|
+
model_config = ConfigDict(extra='forbid')
|
|
498
501
|
|
|
499
502
|
|
|
500
503
|
class LatestValueRegistryEntry(CachedRegistryEntry):
|
|
@@ -86,7 +86,7 @@ FilterQuery = Union[ClauseQuery, ValueQuery]
|
|
|
86
86
|
Filter query to be applied
|
|
87
87
|
"""
|
|
88
88
|
|
|
89
|
-
ClauseQuery.
|
|
89
|
+
ClauseQuery.model_rebuild()
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def coerce_to_filter_query(filters: Union[ClauseQuery, ValueQuery, dict, None]) -> Optional[FilterQuery]:
|
|
@@ -21,12 +21,34 @@ from contextlib import contextmanager
|
|
|
21
21
|
from contextvars import ContextVar
|
|
22
22
|
from typing import Any, Callable, Generic, List, Optional, TypeVar
|
|
23
23
|
|
|
24
|
+
from pydantic import (
|
|
25
|
+
ConfigDict,
|
|
26
|
+
SerializerFunctionWrapHandler,
|
|
27
|
+
field_serializer,
|
|
28
|
+
model_serializer,
|
|
29
|
+
)
|
|
30
|
+
|
|
24
31
|
from dara.core.interactivity.derived_data_variable import DerivedDataVariable
|
|
25
32
|
from dara.core.interactivity.derived_variable import DerivedVariable
|
|
26
33
|
from dara.core.interactivity.non_data_variable import NonDataVariable
|
|
27
34
|
from dara.core.internal.utils import call_async
|
|
35
|
+
from dara.core.logging import dev_logger
|
|
28
36
|
from dara.core.persistence import PersistenceStore
|
|
29
37
|
|
|
38
|
+
|
|
39
|
+
def _is_subclass_safe(value: type, base: type) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
Check if a class is a subclass of another class. Returns False if the value is not a class.
|
|
42
|
+
|
|
43
|
+
:param value: the class to check
|
|
44
|
+
:param base: the class to check against
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
return issubclass(value, base)
|
|
48
|
+
except TypeError:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
30
52
|
VARIABLE_INIT_OVERRIDE = ContextVar[Optional[Callable[[dict], dict]]]('VARIABLE_INIT_OVERRIDE', default=None)
|
|
31
53
|
|
|
32
54
|
VariableType = TypeVar('VariableType')
|
|
@@ -38,14 +60,12 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
38
60
|
A Variable represents a dynamic value in the system that can be read and written to by components and actions
|
|
39
61
|
"""
|
|
40
62
|
|
|
41
|
-
default: Optional[VariableType]
|
|
63
|
+
default: Optional[VariableType] = None
|
|
42
64
|
persist_value: bool = False
|
|
43
65
|
store: Optional[PersistenceStore] = None
|
|
44
66
|
uid: str
|
|
45
67
|
nested: List[str] = []
|
|
46
|
-
|
|
47
|
-
class Config:
|
|
48
|
-
extra = 'forbid'
|
|
68
|
+
model_config = ConfigDict(extra='forbid')
|
|
49
69
|
|
|
50
70
|
def __init__(
|
|
51
71
|
self,
|
|
@@ -53,6 +73,7 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
53
73
|
persist_value: Optional[bool] = False,
|
|
54
74
|
uid: Optional[str] = None,
|
|
55
75
|
store: Optional[PersistenceStoreType_co] = None,
|
|
76
|
+
nested: Optional[List[str]] = None,
|
|
56
77
|
):
|
|
57
78
|
"""
|
|
58
79
|
A Variable represents a dynamic value in the system that can be read and written to by components and actions
|
|
@@ -62,6 +83,8 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
62
83
|
:param uid: the unique identifier for this variable; if not provided a random one is generated
|
|
63
84
|
:param store: a persistence store to attach to the variable; modifies where the source of truth for the variable is
|
|
64
85
|
"""
|
|
86
|
+
if nested is None:
|
|
87
|
+
nested = []
|
|
65
88
|
kwargs = {'default': default, 'persist_value': persist_value, 'uid': uid, 'store': store}
|
|
66
89
|
|
|
67
90
|
# If an override is active, run the kwargs through it
|
|
@@ -78,6 +101,29 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
78
101
|
if self.store:
|
|
79
102
|
call_async(self.store.init, self)
|
|
80
103
|
|
|
104
|
+
@field_serializer('default', mode='wrap')
|
|
105
|
+
def serialize_default(self, default: Any, nxt: SerializerFunctionWrapHandler):
|
|
106
|
+
"""
|
|
107
|
+
Handle serializing the default value of the Variable using the registry of encoders.
|
|
108
|
+
This ensures that users can define a serializer with config.add_encoder and it will be used
|
|
109
|
+
when serializing the Variable.default.
|
|
110
|
+
"""
|
|
111
|
+
from dara.core.internal.encoder_registry import encoder_registry
|
|
112
|
+
|
|
113
|
+
default_type = type(default)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
for encoder_type, encoder in encoder_registry.items():
|
|
117
|
+
if default_type is encoder_type or _is_subclass_safe(default_type, encoder_type):
|
|
118
|
+
return encoder['serialize'](default)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
dev_logger.error(
|
|
121
|
+
f'Error serializing default value of Variable {self.uid}, falling back to default serialization',
|
|
122
|
+
error=e,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return nxt(default)
|
|
126
|
+
|
|
81
127
|
@staticmethod
|
|
82
128
|
@contextmanager
|
|
83
129
|
def init_override(override: Callable[[dict], dict]):
|
|
@@ -229,7 +275,8 @@ class Variable(NonDataVariable, Generic[VariableType]):
|
|
|
229
275
|
|
|
230
276
|
return cls(default=other) # type: ignore
|
|
231
277
|
|
|
232
|
-
|
|
233
|
-
|
|
278
|
+
@model_serializer(mode='wrap')
|
|
279
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
280
|
+
parent_dict = nxt(self)
|
|
234
281
|
|
|
235
282
|
return {**parent_dict, '__typename': 'Variable', 'uid': str(parent_dict['uid'])}
|
|
@@ -19,6 +19,8 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
from typing import Any, Generic, Optional, TypeVar
|
|
21
21
|
|
|
22
|
+
from pydantic import ConfigDict, SerializerFunctionWrapHandler, model_serializer
|
|
23
|
+
|
|
22
24
|
from dara.core.interactivity.non_data_variable import NonDataVariable
|
|
23
25
|
|
|
24
26
|
VariableType = TypeVar('VariableType')
|
|
@@ -31,12 +33,10 @@ class UrlVariable(NonDataVariable, Generic[VariableType]):
|
|
|
31
33
|
pages as you switch from one to the other.
|
|
32
34
|
"""
|
|
33
35
|
|
|
34
|
-
default: Optional[VariableType]
|
|
36
|
+
default: Optional[VariableType] = None
|
|
35
37
|
query: str
|
|
36
38
|
uid: str
|
|
37
|
-
|
|
38
|
-
class Config:
|
|
39
|
-
extra = 'forbid'
|
|
39
|
+
model_config = ConfigDict(extra='forbid')
|
|
40
40
|
|
|
41
41
|
def __init__(self, query: str, default: Optional[VariableType] = None, uid: Optional[str] = None):
|
|
42
42
|
"""
|
|
@@ -130,6 +130,7 @@ class UrlVariable(NonDataVariable, Generic[VariableType]):
|
|
|
130
130
|
assert_no_context('ctx.update')
|
|
131
131
|
return UpdateVariableImpl(variable=self, value=value)
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
@model_serializer(mode='wrap')
|
|
134
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
135
|
+
parent_dict = nxt(self)
|
|
135
136
|
return {**parent_dict, '__typename': 'UrlVariable', 'uid': str(parent_dict['uid'])}
|
dara/core/internal/download.py
CHANGED
|
@@ -36,7 +36,7 @@ class DownloadDataEntry(BaseModel):
|
|
|
36
36
|
uid: str
|
|
37
37
|
file_path: str
|
|
38
38
|
cleanup_file: bool
|
|
39
|
-
identity_name: Optional[str]
|
|
39
|
+
identity_name: Optional[str] = None
|
|
40
40
|
download: Callable[['DownloadDataEntry'], Awaitable[Tuple[anyio.AsyncFile, Callable[..., Awaitable]]]]
|
|
41
41
|
"""Handler for getting the file from the entry"""
|
|
42
42
|
|
|
@@ -193,6 +193,20 @@ encoder_registry: MutableMapping[Type[Any], Encoder] = {
|
|
|
193
193
|
),
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
try:
|
|
197
|
+
# technically you can use dara core without this package
|
|
198
|
+
from cai_causal_graph import CausalGraph, Skeleton
|
|
199
|
+
except ImportError:
|
|
200
|
+
# If the import fails, we don't need to register the encoders for these types
|
|
201
|
+
pass
|
|
202
|
+
else:
|
|
203
|
+
encoder_registry.update(
|
|
204
|
+
{
|
|
205
|
+
CausalGraph: Encoder(serialize=lambda x: x.to_dict(), deserialize=lambda x: CausalGraph.from_dict(x)),
|
|
206
|
+
Skeleton: Encoder(serialize=lambda x: x.to_dict(), deserialize=lambda x: Skeleton.from_dict(x)),
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
|
|
196
210
|
|
|
197
211
|
def deserialize(value: Any, typ: Optional[Type]):
|
|
198
212
|
"""
|
dara/core/internal/hashing.py
CHANGED
|
@@ -68,27 +68,6 @@ class ReferrableWithFilters(Referrable):
|
|
|
68
68
|
filters: dict
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def _has_template_marker(obj: Any) -> bool:
|
|
72
|
-
"""
|
|
73
|
-
Check if an object has a TemplateMarker
|
|
74
|
-
anywhere in its data
|
|
75
|
-
"""
|
|
76
|
-
if isinstance(obj, dict):
|
|
77
|
-
if obj.get('__typename') == 'TemplateMarker':
|
|
78
|
-
return True
|
|
79
|
-
|
|
80
|
-
for val in obj.values():
|
|
81
|
-
if _has_template_marker(val):
|
|
82
|
-
return True
|
|
83
|
-
|
|
84
|
-
if isinstance(obj, list):
|
|
85
|
-
for item in obj:
|
|
86
|
-
if _has_template_marker(item):
|
|
87
|
-
return True
|
|
88
|
-
|
|
89
|
-
return False
|
|
90
|
-
|
|
91
|
-
|
|
92
71
|
def _get_identifier(obj: Referrable) -> str:
|
|
93
72
|
"""
|
|
94
73
|
Get a unique identifier from a 'referrable' object
|
|
@@ -112,15 +91,12 @@ def _get_identifier(obj: Referrable) -> str:
|
|
|
112
91
|
def _is_referrable(obj: Any) -> TypeGuard[Referrable]:
|
|
113
92
|
"""
|
|
114
93
|
Check if a dict is a Referrable type with a '__typename' field and 'uid'
|
|
115
|
-
|
|
116
|
-
Bails out if the object has a TemplateMarker
|
|
117
94
|
"""
|
|
118
95
|
return (
|
|
119
96
|
isinstance(obj, dict)
|
|
120
97
|
and '__typename' in obj
|
|
121
98
|
and 'Variable' in obj['__typename'] # Right now this is meant for Variable objects only
|
|
122
99
|
and 'uid' in obj
|
|
123
|
-
and not _has_template_marker(obj)
|
|
124
100
|
)
|
|
125
101
|
|
|
126
102
|
|