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.
Files changed (51) hide show
  1. dara/core/__init__.py +16 -27
  2. dara/core/auth/base.py +3 -2
  3. dara/core/auth/definitions.py +0 -3
  4. dara/core/auth/utils.py +1 -1
  5. dara/core/base_definitions.py +122 -65
  6. dara/core/cli.py +12 -0
  7. dara/core/configuration.py +5 -8
  8. dara/core/defaults.py +0 -3
  9. dara/core/definitions.py +95 -231
  10. dara/core/interactivity/__init__.py +12 -18
  11. dara/core/interactivity/actions.py +22 -19
  12. dara/core/interactivity/any_data_variable.py +2 -4
  13. dara/core/interactivity/any_variable.py +10 -2
  14. dara/core/interactivity/condition.py +7 -10
  15. dara/core/interactivity/data_variable.py +11 -12
  16. dara/core/interactivity/derived_data_variable.py +7 -7
  17. dara/core/interactivity/derived_variable.py +20 -17
  18. dara/core/interactivity/filtering.py +1 -1
  19. dara/core/interactivity/plain_variable.py +53 -6
  20. dara/core/interactivity/url_variable.py +7 -6
  21. dara/core/internal/download.py +1 -1
  22. dara/core/internal/encoder_registry.py +14 -0
  23. dara/core/internal/hashing.py +1 -1
  24. dara/core/internal/normalization.py +0 -24
  25. dara/core/internal/routing.py +10 -10
  26. dara/core/internal/scheduler.py +3 -2
  27. dara/core/internal/settings.py +2 -4
  28. dara/core/internal/store.py +0 -3
  29. dara/core/internal/tasks.py +2 -2
  30. dara/core/internal/websocket.py +29 -20
  31. dara/core/js_tooling/js_utils.py +1 -1
  32. dara/core/main.py +2 -2
  33. dara/core/persistence.py +12 -4
  34. dara/core/umd/dara.core.umd.js +13 -277
  35. dara/core/visual/components/__init__.py +0 -3
  36. dara/core/visual/components/fallback.py +3 -3
  37. dara/core/visual/components/invalid_component.py +3 -3
  38. dara/core/visual/components/menu.py +3 -3
  39. dara/core/visual/components/progress_tracker.py +3 -2
  40. dara/core/visual/components/raw_string.py +3 -3
  41. dara/core/visual/components/router_content.py +3 -3
  42. dara/core/visual/components/sidebar_frame.py +3 -3
  43. dara/core/visual/components/topbar_frame.py +3 -3
  44. dara/core/visual/css/__init__.py +2 -6
  45. dara/core/visual/dynamic_component.py +3 -6
  46. {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/METADATA +13 -12
  47. {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/RECORD +50 -51
  48. dara/core/visual/components/for_cmp.py +0 -150
  49. {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/LICENSE +0 -0
  50. {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/WHEEL +0 -0
  51. {dara_core-1.15.6a1.dist-info → dara_core-1.16.0.dist-info}/entry_points.txt +0 -0
@@ -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.dict(
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.dict(),
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.dict(exclude={'func'}) for k, comp in component_registry.get_all().items()}
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.dict())
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')
@@ -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, validator
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
- @validator('weekday', pre=True)
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
@@ -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 pydantic import BaseSettings
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():
@@ -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.
@@ -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
- class Config:
62
- use_enum_values = True
62
+ model_config = ConfigDict(use_enum_values=True)
63
63
 
64
64
  def __init__(
65
65
  self,
@@ -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 BaseModel, Field, parse_obj_as
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'] = Field(default='message', const=True)
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
- def dict(self, *args, **kwargs):
64
- # Force by_alias to True to use __rchan name
65
- result = super().dict(*args, **{**kwargs, 'by_alias': True})
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'] = Field(default='custom', const=True)
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
- class Config:
95
- extra = 'allow'
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'] = Field(default='message', const=True)
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'] = Field(default='custom', const=True)
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
- class Config:
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.parse_obj(payload))
344
+ return CustomServerMessage(message=CustomServerMessagePayload.model_validate(payload))
338
345
  else:
339
- return DaraServerMessage(message=ServerMessagePayload.parse_obj(payload))
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 = parse_obj_as(ClientMessage, 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.dict().items() if k != 'result'}
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
 
@@ -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.json(indent=2))
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.dict(),
268
- 'Difference from last cache': build_diff.dict(),
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 BaseModel, Field, PrivateAttr, validator
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
- @validator('path', check_fields=True)
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
- def dict(self, *args, **kwargs):
155
- parent_dict = super().dict(*args, **kwargs)
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