dara-core 1.15.7__py3-none-any.whl → 1.16.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.
Files changed (49) 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/configuration.py +5 -8
  7. dara/core/defaults.py +0 -3
  8. dara/core/definitions.py +95 -231
  9. dara/core/interactivity/__init__.py +12 -18
  10. dara/core/interactivity/actions.py +22 -19
  11. dara/core/interactivity/any_data_variable.py +2 -4
  12. dara/core/interactivity/any_variable.py +10 -2
  13. dara/core/interactivity/condition.py +7 -10
  14. dara/core/interactivity/data_variable.py +11 -12
  15. dara/core/interactivity/derived_data_variable.py +7 -7
  16. dara/core/interactivity/derived_variable.py +20 -17
  17. dara/core/interactivity/filtering.py +1 -1
  18. dara/core/interactivity/plain_variable.py +10 -6
  19. dara/core/interactivity/url_variable.py +7 -6
  20. dara/core/internal/download.py +1 -1
  21. dara/core/internal/hashing.py +1 -1
  22. dara/core/internal/normalization.py +0 -24
  23. dara/core/internal/routing.py +10 -10
  24. dara/core/internal/scheduler.py +3 -2
  25. dara/core/internal/settings.py +2 -4
  26. dara/core/internal/store.py +0 -3
  27. dara/core/internal/tasks.py +2 -2
  28. dara/core/internal/websocket.py +29 -20
  29. dara/core/js_tooling/js_utils.py +1 -1
  30. dara/core/main.py +2 -2
  31. dara/core/persistence.py +12 -4
  32. dara/core/umd/dara.core.umd.js +13 -277
  33. dara/core/visual/components/__init__.py +0 -3
  34. dara/core/visual/components/fallback.py +3 -3
  35. dara/core/visual/components/invalid_component.py +3 -3
  36. dara/core/visual/components/menu.py +3 -3
  37. dara/core/visual/components/progress_tracker.py +3 -2
  38. dara/core/visual/components/raw_string.py +3 -3
  39. dara/core/visual/components/router_content.py +3 -3
  40. dara/core/visual/components/sidebar_frame.py +3 -3
  41. dara/core/visual/components/topbar_frame.py +3 -3
  42. dara/core/visual/css/__init__.py +2 -6
  43. dara/core/visual/dynamic_component.py +3 -6
  44. {dara_core-1.15.7.dist-info → dara_core-1.16.0a1.dist-info}/METADATA +13 -12
  45. {dara_core-1.15.7.dist-info → dara_core-1.16.0a1.dist-info}/RECORD +48 -49
  46. dara/core/visual/components/for_cmp.py +0 -150
  47. {dara_core-1.15.7.dist-info → dara_core-1.16.0a1.dist-info}/LICENSE +0 -0
  48. {dara_core-1.15.7.dist-info → dara_core-1.16.0a1.dist-info}/WHEEL +0 -0
  49. {dara_core-1.15.7.dist-info → dara_core-1.16.0a1.dist-info}/entry_points.txt +0 -0
dara/core/definitions.py CHANGED
@@ -25,8 +25,8 @@ from typing import (
25
25
  Awaitable,
26
26
  Callable,
27
27
  ClassVar,
28
- Generic,
29
28
  List,
29
+ Literal,
30
30
  Mapping,
31
31
  Optional,
32
32
  Protocol,
@@ -38,15 +38,16 @@ from typing import (
38
38
 
39
39
  from fastapi.encoders import jsonable_encoder
40
40
  from fastapi.params import Depends
41
- from pydantic import BaseModel, Field, validator
42
- from pydantic.generics import GenericModel
43
-
44
- from dara.core.base_definitions import (
45
- Action,
46
- ComponentType,
47
- DaraBaseModel,
48
- TemplateMarker,
41
+ from pydantic import (
42
+ ConfigDict,
43
+ Field,
44
+ SerializerFunctionWrapHandler,
45
+ field_validator,
46
+ model_serializer,
49
47
  )
48
+
49
+ from dara.core.base_definitions import Action, ComponentType
50
+ from dara.core.base_definitions import DaraBaseModel as BaseModel
50
51
  from dara.core.css import CSSProperties
51
52
  from dara.core.interactivity import AnyVariable
52
53
 
@@ -62,7 +63,7 @@ class HttpMethod(Enum):
62
63
 
63
64
 
64
65
  class Session(BaseModel):
65
- session_id: Optional[str]
66
+ session_id: Optional[str] = None
66
67
 
67
68
 
68
69
  DEFAULT_ERROR_TITLE = 'Unexpected error occurred'
@@ -79,74 +80,25 @@ class ErrorHandlingConfig(BaseModel):
79
80
  raw_css: Optional[Union[CSSProperties, dict, str]] = None
80
81
  """Raw styling to apply to the displayed error boundary"""
81
82
 
82
- def dict(self, *args, **kwargs):
83
- result = super().dict(*args, **kwargs)
83
+ def model_dump(self, *args, **kwargs):
84
+ result = super().model_dump(*args, **kwargs)
84
85
 
85
86
  # Exclude raw_css if not set
86
87
  if 'raw_css' in result and result.get('raw_css') is None:
87
88
  result.pop('raw_css')
88
89
  elif isinstance(self.raw_css, CSSProperties):
89
90
  # If it's an instance of CSSProperties, serialize but exclude none
90
- result['raw_css'] = self.raw_css.dict(exclude_none=True)
91
+ result['raw_css'] = self.raw_css.model_dump(exclude_none=True)
91
92
 
92
93
  return result
93
94
 
94
95
 
95
- class TemplateMarkerCreator:
96
- """
97
- Creates a TemplateMarker instance for a given field name when accessing
98
- any attribute on the instance. Should not be used standalone, it is injected
99
- into function parameters when using the @template decorator.
100
-
101
- Example
102
-
103
- ```python
104
- from dara.core.definitions import TemplateMarkerCreator
105
-
106
- creator = TemplateMarkerCreator()
107
- creator.foo
108
- # <TemplateMarker field_name='foo'>
109
- ```
110
- """
111
-
112
- def __getattribute__(self, __name: str) -> Any:
113
- return TemplateMarker(field_name=__name)
114
-
115
-
116
- class BaseFallback(DaraBaseModel):
117
- suspend_render: Union[bool, int] = 200
118
- """
119
- :param suspend_render: bool or int, optional
120
- Determines the suspense behavior of the component during state updates.
121
-
122
- - If True, the component will always use suspense during state updates.
123
- This means the component will suspend rendering and show a fallback UI until the new state is ready.
124
-
125
- - If False, the component will always show the previous state while loading the new state.
126
- This means the component will never suspend during state updates. The fallback UI will only
127
- be shown on the first render.
128
-
129
- - If a positive integer (default is 200), this denotes the threshold in milliseconds.
130
- The component will show the previous state while loading the new state,
131
- but will suspend and show a fallback UI after the given timeout if the new state is not ready.
132
- """
133
-
134
- @validator('suspend_render')
135
- @classmethod
136
- def validate_suspend_render(cls, value):
137
- if isinstance(value, int):
138
- if value < 0:
139
- raise ValueError('suspend_render must be a positive integer')
140
-
141
- return value
142
-
143
-
144
- class ComponentInstance(DaraBaseModel):
96
+ class ComponentInstance(BaseModel):
145
97
  """
146
98
  Definition of a Component Instance
147
99
  """
148
100
 
149
- uid: Optional[str] = None
101
+ uid: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4()))
150
102
 
151
103
  js_module: ClassVar[Optional[str]] = None
152
104
  """
@@ -191,9 +143,6 @@ class ComponentInstance(DaraBaseModel):
191
143
  ```
192
144
  """
193
145
 
194
- templated: bool = False
195
- """Whether the component is templated, created by the @template decorator"""
196
-
197
146
  track_progress: Optional[bool] = False
198
147
  """Whether to use ProgressTracker to display progress updates from a task the component is subscribed to"""
199
148
 
@@ -219,18 +168,10 @@ class ComponentInstance(DaraBaseModel):
219
168
  This has no runtime effect and are intended to help identify components with human-readable names in the serialized trees, not in the DOM
220
169
  """
221
170
 
222
- def __init__(self, *args, **kwargs):
223
- uid = kwargs.get('uid', None)
224
- if uid is None:
225
- uid = str(uuid.uuid4())
226
- kwargs = {**kwargs, 'uid': uid}
227
-
228
- super().__init__(*args, **kwargs)
229
-
230
171
  def __repr__(self):
231
172
  return '__dara__' + json.dumps(jsonable_encoder(self))
232
173
 
233
- @validator('raw_css', pre=True)
174
+ @field_validator('raw_css', mode='before')
234
175
  @classmethod
235
176
  def parse_css(cls, css: Optional[Union[CSSProperties, dict, str]]):
236
177
  # If it's a plain dict, change kebab case to camel case
@@ -244,8 +185,14 @@ class ComponentInstance(DaraBaseModel):
244
185
 
245
186
  return css
246
187
 
247
- def dict(self, *args, **kwargs):
248
- props = super().dict(*args, **kwargs)
188
+ @classmethod
189
+ def isinstance(cls, obj: Any) -> bool:
190
+ return isinstance(obj, cls)
191
+
192
+ @model_serializer(mode='wrap')
193
+ def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
194
+ props = nxt(self)
195
+
249
196
  props.pop('uid')
250
197
 
251
198
  # Exclude raw_css if not set
@@ -253,7 +200,7 @@ class ComponentInstance(DaraBaseModel):
253
200
  props.pop('raw_css')
254
201
  elif isinstance(self.raw_css, CSSProperties):
255
202
  # If it's an instance of CSSProperties, serialize but exclude none
256
- props['raw_css'] = self.raw_css.dict(exclude_none=True)
203
+ props['raw_css'] = self.raw_css.model_dump(exclude_none=True)
257
204
 
258
205
  # Exclude track_progress if not set
259
206
  if 'track_progress' in props and props.get('track_progress') is False:
@@ -263,10 +210,6 @@ class ComponentInstance(DaraBaseModel):
263
210
  if 'error_handler' in props and props.get('error_handler') is None:
264
211
  props.pop('error_handler')
265
212
 
266
- # Exclude template if not set
267
- if 'templated' in props and props.get('templated') is False:
268
- props.pop('templated')
269
-
270
213
  # Exclude fallback if not set
271
214
  if 'fallback' in props and props.get('fallback') is None:
272
215
  props.pop('fallback')
@@ -311,6 +254,26 @@ def discover(outer_obj: DiscoverT) -> DiscoverT:
311
254
  return outer_obj
312
255
 
313
256
 
257
+ AlignItems = Literal[
258
+ '-moz-initial',
259
+ 'baseline',
260
+ 'center',
261
+ 'end',
262
+ 'flex-end',
263
+ 'flex-start',
264
+ 'inherit',
265
+ 'initial',
266
+ 'normal',
267
+ 'revert',
268
+ 'self-end',
269
+ 'self-start',
270
+ 'start',
271
+ 'stretch',
272
+ 'unset',
273
+ None,
274
+ ]
275
+
276
+
314
277
  class StyledComponentInstance(ComponentInstance):
315
278
  """
316
279
  Base class for a component implementing the common styling props
@@ -341,13 +304,13 @@ class StyledComponentInstance(ComponentInstance):
341
304
  :param width: the width of the component, can be an number, which will be converted to pixels, or a string
342
305
  """
343
306
 
344
- align: Optional[str] = None
307
+ align: Optional[AlignItems] = None
345
308
  background: Optional[str] = None
346
309
  basis: Optional[Union[int, str, bool]] = None
347
310
  bold: bool = False
348
311
  border: Optional[str] = None
349
312
  border_radius: Optional[Union[float, int, str]] = None
350
- children: Optional[List[Union[ComponentInstance, TemplateMarker]]] = None
313
+ children: Optional[List[ComponentInstance]] = None
351
314
  color: Optional[str] = None
352
315
  font: Optional[str] = None
353
316
  font_size: Optional[str] = None
@@ -368,10 +331,7 @@ class StyledComponentInstance(ComponentInstance):
368
331
  underline: bool = False
369
332
  width: Optional[Union[float, int, str]] = None
370
333
 
371
- class Config:
372
- smart_union = True
373
-
374
- @validator(
334
+ @field_validator(
375
335
  'height',
376
336
  'basis',
377
337
  'border_radius',
@@ -383,7 +343,7 @@ class StyledComponentInstance(ComponentInstance):
383
343
  'min_height',
384
344
  'padding',
385
345
  'width',
386
- pre=True,
346
+ mode='before',
387
347
  )
388
348
  @classmethod
389
349
  def validate_dimension(cls, value):
@@ -392,7 +352,7 @@ class StyledComponentInstance(ComponentInstance):
392
352
 
393
353
  return value
394
354
 
395
- @validator('grow', 'shrink', pre=True)
355
+ @field_validator('grow', 'shrink', mode='before')
396
356
  @classmethod
397
357
  def validate_flex(cls, value):
398
358
  if isinstance(value, (bool)):
@@ -400,7 +360,7 @@ class StyledComponentInstance(ComponentInstance):
400
360
 
401
361
  return value
402
362
 
403
- @validator('children', pre=True)
363
+ @field_validator('children', mode='before')
404
364
  @classmethod
405
365
  def validate_children(cls, children):
406
366
  # Filter out None children
@@ -409,13 +369,44 @@ class StyledComponentInstance(ComponentInstance):
409
369
  return children
410
370
 
411
371
 
372
+ class BaseFallback(StyledComponentInstance):
373
+ suspend_render: Union[bool, int] = 200
374
+ """
375
+ :param suspend_render: bool or int, optional
376
+ Determines the suspense behavior of the component during state updates.
377
+
378
+ - If True, the component will always use suspense during state updates.
379
+ This means the component will suspend rendering and show a fallback UI until the new state is ready.
380
+
381
+ - If False, the component will always show the previous state while loading the new state.
382
+ This means the component will never suspend during state updates. The fallback UI will only
383
+ be shown on the first render.
384
+
385
+ - If a positive integer (default is 200), this denotes the threshold in milliseconds.
386
+ The component will show the previous state while loading the new state,
387
+ but will suspend and show a fallback UI after the given timeout if the new state is not ready.
388
+ """
389
+
390
+ @field_validator('suspend_render')
391
+ @classmethod
392
+ def validate_suspend_render(cls, value):
393
+ if isinstance(value, int):
394
+ if value < 0:
395
+ raise ValueError('suspend_render must be a positive integer')
396
+
397
+ return value
398
+
399
+
400
+ ComponentInstance.model_rebuild()
401
+
402
+
412
403
  ComponentInstanceType = Union[ComponentInstance, Callable[..., ComponentInstance]]
413
404
 
414
405
 
415
406
  class JsComponentDef(BaseModel):
416
407
  """Definition of a JS Component"""
417
408
 
418
- js_module: Optional[str]
409
+ js_module: Optional[str] = None
419
410
  """
420
411
  JS module where the component implementation lives.
421
412
 
@@ -433,13 +424,13 @@ class JsComponentDef(BaseModel):
433
424
  name: str
434
425
  """Name of the component, must match the Python definition and JS implementation"""
435
426
 
436
- type: str = Field(default=ComponentType.JS, const=True)
427
+ type: Literal[ComponentType.JS] = ComponentType.JS
437
428
 
438
429
 
439
430
  class PyComponentDef(BaseModel):
440
431
  """Definition of a Python Component"""
441
432
 
442
- func: Optional[Callable[..., Any]]
433
+ func: Optional[Callable[..., Any]] = None
443
434
  name: str
444
435
  dynamic_kwargs: Optional[Mapping[str, AnyVariable]] = None
445
436
  fallback: Optional[BaseFallback] = None
@@ -447,126 +438,7 @@ class PyComponentDef(BaseModel):
447
438
  render_component: Callable[..., Awaitable[Any]]
448
439
  """Handler to render the component. Defaults to dara.core.visual.dynamic_component.render_component"""
449
440
 
450
- type: str = Field(default=ComponentType.PY, const=True)
451
-
452
-
453
- ComponentT = TypeVar('ComponentT', bound=ComponentInstance)
454
-
455
-
456
- class TemplateComponentDef(GenericModel, Generic[ComponentT]):
457
- """
458
- Definition of a Template Component, as returned by the @template decorator.
459
- When called, executes the decorated function with the marker creator injected and returns a component instance,
460
- annotated with `templated=True`.
461
- """
462
-
463
- func: Callable[..., ComponentT]
464
- name: str
465
-
466
- def __call__(self, *args, **kwds) -> ComponentT:
467
- creator = TemplateMarkerCreator()
468
- result = self.func(creator, *args, **kwds)
469
- result.templated = True
470
- return result
471
-
472
-
473
- class template(Generic[ComponentT]):
474
- """
475
- The @template decorator is used to define reusable components that can be
476
- dynamically rendered based on input data. It allows you to create template
477
- components with customizable properties that can be injected into certain
478
- components equipped to handle such templates.
479
-
480
- Usage:
481
- ------
482
-
483
- 1. Define a template component using the `@template` decorator and provide a
484
- function that accepts a first argument of type `template.Value`.
485
- This argument is automatically injected by the decorator, and its name can be
486
- chosen by the user. Inside the function, use the first argument to access
487
- properties that will be injected into the component when rendered.
488
-
489
- Example:
490
-
491
- ```
492
- from dara.core import template
493
- from dara_dashboarding_extension import Card, Heading, Text, Button
494
-
495
- @template
496
- def ExperimentCard(data: template.Value):
497
- return Card(
498
- Heading(data.title, level=2),
499
- Text(data.description),
500
- Button(data.button_text, raw_css={'backgroundColor': data.button_color}),
501
- ...
502
- )
503
- ```
504
-
505
- 2. Use the template component in certain components that can handle such
506
- templates, like the `For` component.
507
-
508
- Example:
509
-
510
- ```
511
- from dara.core import Variable, For
512
-
513
- var = Variable([
514
- {
515
- 'title': 'Experiment 1',
516
- 'description': 'This is a description of experiment 1',
517
- 'button_text': 'Run Experiment 1',
518
- 'button_color': 'red',
519
- },
520
- ...
521
- ])
522
-
523
- For(
524
- key_accessor='title',
525
- data=var,
526
- template=ExperimentCard(),
527
- )
528
- ```
529
-
530
- The `For` component will dynamically render elements according to the
531
- template, using the properties specified by the `template.Value`. As opposed
532
- to `py_component`, the decorated component only executes once, when called.
533
-
534
- Note that only certain components are equipped to handle template components,
535
- so refer to the component documentation for compatibility information.
536
- """
537
-
538
- Value = TemplateMarkerCreator
539
- """
540
- The `template.Value` is a special class used with the @template decorator to
541
- define dynamic properties that will be injected into a template component
542
- when it is rendered.
543
-
544
- When defining a template component, use the `template.Value` as the type
545
- for the first argument of the function. This argument is automatically
546
- injected by the `@template` decorator, and its name can be chosen by the user.
547
- Access the properties of the Value within the function to create
548
- dynamic components based on the injected data.
549
-
550
- Example usage:
551
-
552
- ```
553
- from dara.core import template
554
- from dara_dashboarding_extension import Card, Heading, Text, Button
555
-
556
- @template
557
- def ExperimentCard(data: template.Value):
558
- return Card(
559
- Heading(data.title, level=2),
560
- Text(data.description),
561
- Button(data.button_text, raw_css={'backgroundColor': data.button_color}),
562
- ...
563
- )
564
- ```
565
-
566
- """
567
-
568
- def __new__(cls, func: Callable[..., ComponentT]) -> TemplateComponentDef[ComponentT]: # type: ignore
569
- return TemplateComponentDef(func=func, name=func.__name__)
441
+ type: Literal[ComponentType.PY] = ComponentType.PY
570
442
 
571
443
 
572
444
  # Helper type annotation for working with components
@@ -593,9 +465,7 @@ class ApiRoute(BaseModel):
593
465
  handler: Callable
594
466
  method: HttpMethod
595
467
  url: str
596
-
597
- class Config:
598
- arbitrary_types_allowed = True
468
+ model_config = ConfigDict(arbitrary_types_allowed=True)
599
469
 
600
470
  def __hash__(self):
601
471
  return hash((self.handler, self.method, self.url, tuple(self.dependencies)))
@@ -604,37 +474,31 @@ class ApiRoute(BaseModel):
604
474
  class Page(BaseModel):
605
475
  """Definition of a Page"""
606
476
 
607
- icon: Optional[str]
477
+ icon: Optional[str] = None
608
478
  content: ComponentInstanceType
609
479
  name: str
610
480
  sub_pages: Optional[List['Page']] = []
611
481
  url_safe_name: str
612
- include_in_menu: Optional[bool]
482
+ include_in_menu: Optional[bool] = None
613
483
  on_load: Optional[Action] = None
614
-
615
- class Config:
616
- extra = 'forbid'
617
-
618
-
619
- # This is required by pydantic to support the self referential type subpages.
620
- Page.update_forward_refs()
484
+ model_config = ConfigDict(extra='forbid')
621
485
 
622
486
 
623
487
  class TemplateRoute(BaseModel):
624
488
  """Definition of a route for the TemplateRouter"""
625
489
 
626
490
  content: ComponentInstance
627
- icon: Optional[str]
491
+ icon: Optional[str] = None
628
492
  name: str
629
493
  route: str
630
- include_in_menu: Optional[bool]
494
+ include_in_menu: Optional[bool] = None
631
495
  on_load: Optional[Action] = None
632
496
 
633
497
 
634
498
  class TemplateRouterLink(BaseModel):
635
499
  """Definition of a link for the TemplateRouter"""
636
500
 
637
- icon: Optional[str]
501
+ icon: Optional[str] = None
638
502
  name: str
639
503
  route: str
640
504
 
@@ -645,7 +509,7 @@ class TemplateRouterContent(BaseModel):
645
509
  content: ComponentInstance
646
510
  route: str
647
511
  on_load: Optional[Action] = None
648
- name: Optional[str]
512
+ name: Optional[str] = None
649
513
 
650
514
 
651
515
  class Template(BaseModel):
@@ -17,6 +17,8 @@ limitations under the License.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ from pydantic import BaseModel
21
+
20
22
  from dara.core.interactivity.actions import (
21
23
  DownloadContent,
22
24
  DownloadContentImpl,
@@ -41,24 +43,6 @@ from dara.core.interactivity.non_data_variable import NonDataVariable
41
43
  from dara.core.interactivity.plain_variable import Variable
42
44
  from dara.core.interactivity.url_variable import UrlVariable
43
45
 
44
- # Update references to variable types in these actions
45
- refs = {
46
- 'AnyDataVariable': AnyDataVariable,
47
- 'DerivedVariable': DerivedVariable,
48
- 'Variable': Variable,
49
- 'AnyVariable': AnyVariable,
50
- 'DataVariable': DataVariable,
51
- 'UrlVariable': UrlVariable,
52
- }
53
- DownloadVariable.update_forward_refs(**refs)
54
- ResetVariables.update_forward_refs(**refs)
55
- TriggerVariable.update_forward_refs(**refs)
56
- UpdateVariable.update_forward_refs(**refs)
57
- UpdateVariableImpl.update_forward_refs(**refs)
58
- Condition.update_forward_refs(**refs)
59
- Notify.update_forward_refs(**refs)
60
-
61
-
62
46
  __all__ = [
63
47
  'action',
64
48
  'AnyVariable',
@@ -78,7 +62,17 @@ __all__ = [
78
62
  'ResetVariables',
79
63
  'TriggerVariable',
80
64
  'UpdateVariable',
65
+ 'UpdateVariableImpl',
81
66
  'SideEffect',
82
67
  'Condition',
83
68
  'Operator',
84
69
  ]
70
+
71
+ for symbol in list(globals().values()):
72
+ try:
73
+ if issubclass(symbol, BaseModel) and symbol is not BaseModel:
74
+ symbol.model_rebuild()
75
+ except Exception as e:
76
+ from dara.core.logging import dev_logger
77
+
78
+ dev_logger.warning(f'Error rebuilding model "{symbol}": {e}')