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
dara/core/internal/routing.py
CHANGED
|
@@ -120,7 +120,7 @@ def create_router(config: Configuration):
|
|
|
120
120
|
values: NormalizedPayload[Mapping[str, Any]]
|
|
121
121
|
"""Dynamic kwarg values"""
|
|
122
122
|
|
|
123
|
-
input: Any
|
|
123
|
+
input: Any = None
|
|
124
124
|
"""Input from the component"""
|
|
125
125
|
|
|
126
126
|
ws_channel: str
|
|
@@ -194,7 +194,7 @@ def create_router(config: Configuration):
|
|
|
194
194
|
@core_api_router.get('/config', dependencies=[Depends(verify_session)])
|
|
195
195
|
async def get_config(): # pylint: disable=unused-variable
|
|
196
196
|
return {
|
|
197
|
-
**config.
|
|
197
|
+
**config.model_dump(
|
|
198
198
|
include={
|
|
199
199
|
'enable_devtools',
|
|
200
200
|
'live_reload',
|
|
@@ -211,7 +211,7 @@ def create_router(config: Configuration):
|
|
|
211
211
|
@core_api_router.get('/auth-config')
|
|
212
212
|
async def get_auth_config(): # pylint: disable=unused-variable
|
|
213
213
|
return {
|
|
214
|
-
'auth_components': config.auth_config.component_config.
|
|
214
|
+
'auth_components': config.auth_config.component_config.model_dump(),
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
@core_api_router.get('/components', dependencies=[Depends(verify_session)])
|
|
@@ -225,7 +225,7 @@ def create_router(config: Configuration):
|
|
|
225
225
|
registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
|
|
226
226
|
await registry_mgr.get(component_registry, name)
|
|
227
227
|
|
|
228
|
-
return {k: comp.
|
|
228
|
+
return {k: comp.model_dump(exclude={'func'}) for k, comp in component_registry.get_all().items()}
|
|
229
229
|
|
|
230
230
|
class ComponentRequestBody(BaseModel):
|
|
231
231
|
# Dynamic kwarg values
|
|
@@ -294,9 +294,9 @@ def create_router(config: Configuration):
|
|
|
294
294
|
)
|
|
295
295
|
|
|
296
296
|
class DataVariableRequestBody(BaseModel):
|
|
297
|
-
filters: Optional[FilterQuery]
|
|
298
|
-
cache_key: Optional[str]
|
|
299
|
-
ws_channel: Optional[str]
|
|
297
|
+
filters: Optional[FilterQuery] = None
|
|
298
|
+
cache_key: Optional[str] = None
|
|
299
|
+
ws_channel: Optional[str] = None
|
|
300
300
|
|
|
301
301
|
@core_api_router.post('/data-variable/{uid}', dependencies=[Depends(verify_session)])
|
|
302
302
|
async def get_data_variable(
|
|
@@ -366,8 +366,8 @@ def create_router(config: Configuration):
|
|
|
366
366
|
raise HTTPException(status_code=400, detail=str(e))
|
|
367
367
|
|
|
368
368
|
class DataVariableCountRequestBody(BaseModel):
|
|
369
|
-
cache_key: Optional[str]
|
|
370
|
-
filters: Optional[FilterQuery]
|
|
369
|
+
cache_key: Optional[str] = None
|
|
370
|
+
filters: Optional[FilterQuery] = None
|
|
371
371
|
|
|
372
372
|
@core_api_router.post('/data-variable/{uid}/count', dependencies=[Depends(verify_session)])
|
|
373
373
|
async def get_data_variable_count(uid: str, body: Optional[DataVariableCountRequestBody] = None):
|
|
@@ -538,7 +538,7 @@ def create_router(config: Configuration):
|
|
|
538
538
|
async def get_template(template: str): # pylint: disable=unused-variable
|
|
539
539
|
try:
|
|
540
540
|
selected_template = template_registry.get(template)
|
|
541
|
-
normalized_template, lookup = normalize(selected_template
|
|
541
|
+
normalized_template, lookup = normalize(jsonable_encoder(selected_template))
|
|
542
542
|
return {'data': normalized_template, 'lookup': lookup}
|
|
543
543
|
except KeyError:
|
|
544
544
|
raise HTTPException(status_code=404, detail=f'Template: {template}, not found in registry')
|
dara/core/internal/scheduler.py
CHANGED
|
@@ -23,7 +23,7 @@ from pickle import PicklingError
|
|
|
23
23
|
from typing import Any, List, Optional, Union
|
|
24
24
|
|
|
25
25
|
from croniter import croniter
|
|
26
|
-
from pydantic import BaseModel,
|
|
26
|
+
from pydantic import BaseModel, field_validator
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class ScheduledJob(BaseModel):
|
|
@@ -174,7 +174,8 @@ class ScheduledJobFactory(BaseModel):
|
|
|
174
174
|
weekday: Optional[datetime] = None
|
|
175
175
|
run_once: bool
|
|
176
176
|
|
|
177
|
-
@
|
|
177
|
+
@field_validator('weekday', mode='before')
|
|
178
|
+
@classmethod
|
|
178
179
|
def validate_weekday(cls, weekday: Any) -> datetime: # pylint: disable=E0213
|
|
179
180
|
if isinstance(weekday, datetime):
|
|
180
181
|
return weekday
|
dara/core/internal/settings.py
CHANGED
|
@@ -21,7 +21,7 @@ from secrets import token_hex
|
|
|
21
21
|
from typing import List, Optional
|
|
22
22
|
|
|
23
23
|
from dotenv import dotenv_values
|
|
24
|
-
from
|
|
24
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
25
25
|
|
|
26
26
|
from dara.core.logging import dev_logger
|
|
27
27
|
|
|
@@ -45,9 +45,7 @@ class Settings(BaseSettings):
|
|
|
45
45
|
sso_jwt_algo: str = 'ES256'
|
|
46
46
|
sso_verify_audience: bool = False
|
|
47
47
|
sso_extra_audience: Optional[List[str]] = None
|
|
48
|
-
|
|
49
|
-
class Config:
|
|
50
|
-
env_file = '.env'
|
|
48
|
+
model_config = SettingsConfigDict(env_file='.env')
|
|
51
49
|
|
|
52
50
|
|
|
53
51
|
def generate_env_content():
|
dara/core/internal/store.py
CHANGED
|
@@ -31,9 +31,6 @@ class Store:
|
|
|
31
31
|
and resolve at the same time when the value is correctly updated by the first one to request the specific key
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
|
-
class Config:
|
|
35
|
-
use_enum_values = True
|
|
36
|
-
|
|
37
34
|
def __init__(self):
|
|
38
35
|
# This dict is the main store of values, the first level of keys is the cache type and the second level are the
|
|
39
36
|
# value keys. The root key is used for any non-session/user dependant keys that are added.
|
dara/core/internal/tasks.py
CHANGED
|
@@ -29,6 +29,7 @@ from anyio import (
|
|
|
29
29
|
from anyio.abc import TaskGroup
|
|
30
30
|
from anyio.streams.memory import MemoryObjectSendStream
|
|
31
31
|
from exceptiongroup import ExceptionGroup
|
|
32
|
+
from pydantic import ConfigDict
|
|
32
33
|
|
|
33
34
|
from dara.core.base_definitions import (
|
|
34
35
|
BaseTask,
|
|
@@ -58,8 +59,7 @@ class Task(BaseTask):
|
|
|
58
59
|
Methods for reading from the subprocess and writing to it are also provided.
|
|
59
60
|
"""
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
use_enum_values = True
|
|
62
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
63
63
|
|
|
64
64
|
def __init__(
|
|
65
65
|
self,
|
dara/core/internal/websocket.py
CHANGED
|
@@ -30,12 +30,19 @@ from exceptiongroup import catch
|
|
|
30
30
|
from fastapi import Query, WebSocketException
|
|
31
31
|
from fastapi.encoders import jsonable_encoder
|
|
32
32
|
from jwt import DecodeError
|
|
33
|
-
from pydantic import
|
|
33
|
+
from pydantic import (
|
|
34
|
+
ConfigDict,
|
|
35
|
+
Field,
|
|
36
|
+
SerializerFunctionWrapHandler,
|
|
37
|
+
TypeAdapter,
|
|
38
|
+
model_serializer,
|
|
39
|
+
)
|
|
34
40
|
from starlette.websockets import WebSocket, WebSocketDisconnect
|
|
35
41
|
|
|
36
42
|
from dara.core.auth.base import BaseAuthConfig
|
|
37
43
|
from dara.core.auth.definitions import AuthError, TokenData
|
|
38
44
|
from dara.core.auth.utils import decode_token
|
|
45
|
+
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
39
46
|
from dara.core.logging import dev_logger, eng_logger
|
|
40
47
|
|
|
41
48
|
|
|
@@ -47,22 +54,24 @@ class DaraClientMessage(BaseModel):
|
|
|
47
54
|
An optional chunk_count field can be used to indicate that a message is chunked and what number of messages to expect
|
|
48
55
|
"""
|
|
49
56
|
|
|
50
|
-
type: Literal['message'] =
|
|
57
|
+
type: Literal['message'] = 'message'
|
|
51
58
|
channel: str
|
|
52
59
|
chunk_count: Optional[int] = None
|
|
53
60
|
message: Any
|
|
54
61
|
|
|
55
62
|
|
|
56
63
|
class CustomClientMessagePayload(BaseModel):
|
|
64
|
+
model_config = ConfigDict(serialize_by_alias=True)
|
|
65
|
+
|
|
57
66
|
rchan: Optional[str] = Field(default=None, alias='__rchan')
|
|
58
67
|
"""Return channel if the message is expected to have a response for"""
|
|
59
68
|
|
|
60
69
|
kind: str
|
|
61
70
|
data: Any
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
result =
|
|
72
|
+
@model_serializer(mode='wrap')
|
|
73
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
74
|
+
result = nxt(self)
|
|
66
75
|
|
|
67
76
|
# remove rchan if None
|
|
68
77
|
if '__rchan' in result and result.get('__rchan') is None:
|
|
@@ -76,7 +85,7 @@ class CustomClientMessage(BaseModel):
|
|
|
76
85
|
Represents a custom message sent by the frontend to the backend.
|
|
77
86
|
"""
|
|
78
87
|
|
|
79
|
-
type: Literal['custom'] =
|
|
88
|
+
type: Literal['custom'] = 'custom'
|
|
80
89
|
message: CustomClientMessagePayload
|
|
81
90
|
|
|
82
91
|
|
|
@@ -85,18 +94,17 @@ ClientMessage = Union[DaraClientMessage, CustomClientMessage]
|
|
|
85
94
|
|
|
86
95
|
# Server message types
|
|
87
96
|
class ServerMessagePayload(BaseModel):
|
|
97
|
+
model_config = ConfigDict(serialize_by_alias=True, extra='allow')
|
|
98
|
+
|
|
88
99
|
rchan: Optional[str] = Field(default=None, alias='__rchan')
|
|
89
100
|
"""Return channel if the message is expected to have a response for"""
|
|
90
101
|
|
|
91
102
|
response_for: Optional[str] = Field(default=None, alias='__response_for')
|
|
92
103
|
"""ID of the __rchan included in the original client message if this message is a response to a client message"""
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def dict(self, *args, **kwargs):
|
|
98
|
-
# Force by_alias to True to use __rchan name
|
|
99
|
-
result = super().dict(*args, **{**kwargs, 'by_alias': True})
|
|
105
|
+
@model_serializer(mode='wrap')
|
|
106
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
107
|
+
result = nxt(self)
|
|
100
108
|
|
|
101
109
|
# remove rchan if None
|
|
102
110
|
if '__rchan' in result and result.get('__rchan') is None:
|
|
@@ -119,7 +127,7 @@ class DaraServerMessage(BaseModel):
|
|
|
119
127
|
Represents a message sent by Dara internals from the backend to the frontend.
|
|
120
128
|
"""
|
|
121
129
|
|
|
122
|
-
type: Literal['message'] =
|
|
130
|
+
type: Literal['message'] = 'message'
|
|
123
131
|
message: ServerMessagePayload # exact messages expected by frontend are defined in js/api/websocket.tsx
|
|
124
132
|
|
|
125
133
|
|
|
@@ -128,7 +136,7 @@ class CustomServerMessage(BaseModel):
|
|
|
128
136
|
Represents a custom message sent by the backend to the frontend.
|
|
129
137
|
"""
|
|
130
138
|
|
|
131
|
-
type: Literal['custom'] =
|
|
139
|
+
type: Literal['custom'] = 'custom'
|
|
132
140
|
message: CustomServerMessagePayload
|
|
133
141
|
|
|
134
142
|
|
|
@@ -165,8 +173,7 @@ class WebSocketHandler:
|
|
|
165
173
|
notify when the response is received and the response data.
|
|
166
174
|
"""
|
|
167
175
|
|
|
168
|
-
|
|
169
|
-
arbitrary_types_allowed = True
|
|
176
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
170
177
|
|
|
171
178
|
def __init__(self, channel_id: str):
|
|
172
179
|
send_stream, receive_stream = create_memory_object_stream[ServerMessage](math.inf)
|
|
@@ -334,9 +341,9 @@ class WebsocketManager:
|
|
|
334
341
|
:param custom: Whether the message is a custom message
|
|
335
342
|
"""
|
|
336
343
|
if custom:
|
|
337
|
-
return CustomServerMessage(message=CustomServerMessagePayload.
|
|
344
|
+
return CustomServerMessage(message=CustomServerMessagePayload.model_validate(payload))
|
|
338
345
|
else:
|
|
339
|
-
return DaraServerMessage(message=ServerMessagePayload.
|
|
346
|
+
return DaraServerMessage(message=ServerMessagePayload.model_validate(payload))
|
|
340
347
|
|
|
341
348
|
def create_handler(self, channel_id: str) -> WebSocketHandler:
|
|
342
349
|
"""
|
|
@@ -534,7 +541,7 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
|
|
|
534
541
|
eng_logger.error('Error updating token data', error=e)
|
|
535
542
|
else:
|
|
536
543
|
try:
|
|
537
|
-
parsed_data =
|
|
544
|
+
parsed_data = TypeAdapter(ClientMessage).validate_python(data)
|
|
538
545
|
result = handler.process_client_message(parsed_data)
|
|
539
546
|
# Process the resulting coroutine before moving on to next message
|
|
540
547
|
if inspect.iscoroutine(result):
|
|
@@ -547,6 +554,8 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
|
|
|
547
554
|
Handle messages sent to the client and pass them via the websocket
|
|
548
555
|
"""
|
|
549
556
|
async for message in handler.receive_stream:
|
|
557
|
+
# TODO: This is hacky, should probably be a model_serializer
|
|
558
|
+
# on a proper payload type
|
|
550
559
|
if (
|
|
551
560
|
message.type == 'message'
|
|
552
561
|
and isinstance(message.message, ServerMessagePayload)
|
|
@@ -556,7 +565,7 @@ async def ws_handler(websocket: WebSocket, token: Optional[str] = Query(default=
|
|
|
556
565
|
data = message.message
|
|
557
566
|
# Reconstruct the payload without the result field
|
|
558
567
|
message.message = ServerMessagePayload(
|
|
559
|
-
**{k: v for k, v in data.
|
|
568
|
+
**{k: v for k, v in data.model_dump().items() if k != 'result'}
|
|
560
569
|
)
|
|
561
570
|
await websocket.send_json(jsonable_encoder(message))
|
|
562
571
|
|
dara/core/js_tooling/js_utils.py
CHANGED
|
@@ -451,7 +451,7 @@ def rebuild_js(build_cache: BuildCache, build_diff: BuildCacheDiff = BuildCacheD
|
|
|
451
451
|
# Store the new build cache
|
|
452
452
|
build_cache_path = os.path.join(build_cache.static_files_dir, BuildCache.FILENAME)
|
|
453
453
|
with open(build_cache_path, 'w+', encoding='utf-8') as f:
|
|
454
|
-
f.write(build_cache.
|
|
454
|
+
f.write(build_cache.model_dump_json(indent=2))
|
|
455
455
|
|
|
456
456
|
|
|
457
457
|
def bundle_js(build_cache: BuildCache, copy_js: bool = False):
|
dara/core/main.py
CHANGED
|
@@ -264,8 +264,8 @@ def _start_application(config: Configuration):
|
|
|
264
264
|
dev_logger.debug(
|
|
265
265
|
'Building JS...',
|
|
266
266
|
extra={
|
|
267
|
-
'New build cache': build_cache.
|
|
268
|
-
'Difference from last cache': build_diff.
|
|
267
|
+
'New build cache': build_cache.model_dump(),
|
|
268
|
+
'Difference from last cache': build_diff.model_dump(),
|
|
269
269
|
},
|
|
270
270
|
)
|
|
271
271
|
rebuild_js(build_cache, build_diff)
|
dara/core/persistence.py
CHANGED
|
@@ -6,7 +6,14 @@ from uuid import uuid4
|
|
|
6
6
|
|
|
7
7
|
import aiorwlock
|
|
8
8
|
import anyio
|
|
9
|
-
from pydantic import
|
|
9
|
+
from pydantic import (
|
|
10
|
+
BaseModel,
|
|
11
|
+
Field,
|
|
12
|
+
PrivateAttr,
|
|
13
|
+
SerializerFunctionWrapHandler,
|
|
14
|
+
field_validator,
|
|
15
|
+
model_serializer,
|
|
16
|
+
)
|
|
10
17
|
|
|
11
18
|
from dara.core.auth.definitions import USER
|
|
12
19
|
from dara.core.internal.utils import run_user_handler
|
|
@@ -88,7 +95,7 @@ class FileBackend(PersistenceBackend):
|
|
|
88
95
|
path: str
|
|
89
96
|
_lock: aiorwlock.RWLock = PrivateAttr(default_factory=aiorwlock.RWLock)
|
|
90
97
|
|
|
91
|
-
@
|
|
98
|
+
@field_validator('path', check_fields=True)
|
|
92
99
|
@classmethod
|
|
93
100
|
def validate_path(cls, value):
|
|
94
101
|
if not os.path.splitext(value)[1] == '.json':
|
|
@@ -151,8 +158,9 @@ class PersistenceStore(BaseModel, abc.ABC):
|
|
|
151
158
|
Initialize the store when connecting to a variable
|
|
152
159
|
"""
|
|
153
160
|
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
@model_serializer(mode='wrap')
|
|
162
|
+
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
|
|
163
|
+
parent_dict = nxt(self)
|
|
156
164
|
parent_dict['__typename'] = self.__class__.__name__
|
|
157
165
|
return parent_dict
|
|
158
166
|
|