dara-components 1.8.5__py3-none-any.whl → 1.22.1__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 (92) hide show
  1. dara/components/__init__.py +18 -0
  2. dara/components/_assets/__init__.py +30 -0
  3. dara/components/_assets/auto_js/.gitkeep +0 -0
  4. dara/components/_assets/auto_js/dara.components.css +1494 -0
  5. dara/components/_assets/auto_js/dara.components.umd.js +182837 -0
  6. dara/components/_assets/common/bokeh-3.1.1.min.js +690 -0
  7. dara/components/_assets/common/bokeh-api-3.1.1.min.js +60 -0
  8. dara/components/_assets/common/bokeh-gl-3.1.1.min.js +67 -0
  9. dara/components/_assets/common/bokeh-mathjax-3.1.1.min.js +329 -0
  10. dara/components/_assets/common/bokeh-tables-3.1.1.min.js +132 -0
  11. dara/components/_assets/common/bokeh-widgets-3.1.1.min.js +129 -0
  12. dara/components/_assets/common/pixi-filters.min.js +17 -0
  13. dara/components/_assets/common/pixi.min.js +2214 -0
  14. dara/components/_assets/common/pixi_viewport.js +1 -0
  15. dara/components/_assets/common/plotly.min.js +8 -0
  16. dara/components/common/__init__.py +11 -2
  17. dara/components/common/accordion.py +20 -26
  18. dara/components/common/anchor.py +9 -10
  19. dara/components/common/base_component.py +23 -36
  20. dara/components/common/bullet_list.py +1 -3
  21. dara/components/common/button.py +35 -26
  22. dara/components/common/button_bar.py +25 -20
  23. dara/components/common/card.py +4 -5
  24. dara/components/common/carousel.py +9 -9
  25. dara/components/common/checkbox_group.py +26 -19
  26. dara/components/common/code.py +8 -5
  27. dara/components/common/component_select_list.py +9 -13
  28. dara/components/common/datepicker.py +16 -16
  29. dara/components/common/dropdown_menu.py +161 -0
  30. dara/components/common/dropzone.py +42 -33
  31. dara/components/common/form.py +5 -7
  32. dara/components/common/form_page.py +4 -6
  33. dara/components/common/grid.py +21 -18
  34. dara/components/common/heading.py +5 -4
  35. dara/components/common/icon.py +1 -3
  36. dara/components/common/if_cmp.py +23 -17
  37. dara/components/common/image.py +2 -2
  38. dara/components/common/input.py +9 -11
  39. dara/components/common/label.py +13 -14
  40. dara/components/common/markdown.py +3 -5
  41. dara/components/common/modal.py +2 -2
  42. dara/components/common/overlay.py +8 -14
  43. dara/components/common/paragraph.py +2 -2
  44. dara/components/common/progress_bar.py +6 -8
  45. dara/components/common/radio_group.py +38 -21
  46. dara/components/common/select.py +33 -30
  47. dara/components/common/slider.py +74 -29
  48. dara/components/common/spacer.py +4 -6
  49. dara/components/common/stack.py +7 -4
  50. dara/components/common/switch.py +6 -8
  51. dara/components/common/tabbed_card.py +8 -11
  52. dara/components/common/table.py +224 -73
  53. dara/components/common/text.py +7 -9
  54. dara/components/common/textarea.py +7 -7
  55. dara/components/common/time_utils.py +2 -5
  56. dara/components/common/tooltip.py +4 -6
  57. dara/components/common/utils.py +29 -35
  58. dara/components/graphs/__init__.py +1 -0
  59. dara/components/graphs/components/base_graph_component.py +34 -22
  60. dara/components/graphs/components/causal_graph_viewer.py +13 -15
  61. dara/components/graphs/components/edge_encoder.py +49 -26
  62. dara/components/graphs/components/node_hierarchy_builder.py +17 -16
  63. dara/components/graphs/definitions.py +27 -20
  64. dara/components/graphs/graph_layout.py +90 -53
  65. dara/components/plotting/__init__.py +2 -1
  66. dara/components/plotting/bokeh/bokeh.py +7 -10
  67. dara/components/plotting/bokeh/utils.py +5 -3
  68. dara/components/plotting/plotly/plotly.py +24 -19
  69. dara/components/plotting/plotly/themes.py +7 -5
  70. dara/components/smart/__init__.py +7 -1
  71. dara/components/smart/chat/chat.py +7 -8
  72. dara/components/smart/chat/config.py +1 -1
  73. dara/components/smart/chat/types.py +4 -6
  74. dara/components/smart/code_editor/code_editor.py +18 -4
  75. dara/components/smart/code_editor/util.py +11 -11
  76. dara/components/smart/data_slicer/__init__.py +4 -0
  77. dara/components/smart/data_slicer/data_slicer.py +14 -18
  78. dara/components/smart/data_slicer/data_slicer_modal.py +4 -6
  79. dara/components/smart/data_slicer/extension/data_slicer_filter.py +3 -4
  80. dara/components/smart/data_slicer/extension/filter_status_button.py +1 -3
  81. dara/components/smart/data_slicer/utils/core.py +23 -23
  82. dara/components/smart/data_slicer/utils/data_preview.py +1 -3
  83. dara/components/smart/data_slicer/utils/plotting.py +8 -6
  84. dara/components/smart/hierarchy.py +9 -10
  85. {dara_components-1.8.5.dist-info → dara_components-1.22.1.dist-info}/METADATA +7 -7
  86. dara_components-1.22.1.dist-info/RECORD +100 -0
  87. {dara_components-1.8.5.dist-info → dara_components-1.22.1.dist-info}/WHEEL +1 -1
  88. dara_components-1.22.1.dist-info/entry_points.txt +3 -0
  89. dara/components/umd/dara.components.umd.js +0 -396288
  90. dara/components/umd/style.css +0 -745
  91. dara_components-1.8.5.dist-info/RECORD +0 -86
  92. {dara_components-1.8.5.dist-info → dara_components-1.22.1.dist-info}/LICENSE +0 -0
@@ -15,22 +15,23 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from datetime import datetime
18
+ from collections.abc import Sequence
19
19
  from enum import Enum
20
- from typing import List, Literal, Optional, Union
20
+ from typing import Any, ClassVar, Literal
21
21
 
22
- from pydantic import BaseModel, validator
22
+ from fastapi.encoders import jsonable_encoder
23
+ from pandas import DataFrame
24
+ from pydantic import ConfigDict, Field, SerializerFunctionWrapHandler, ValidationInfo, field_serializer, field_validator
23
25
 
24
26
  from dara.components.common.base_component import ContentComponent
25
- from dara.components.common.time_utils import coerce_to_timemilli
26
27
  from dara.core.base_definitions import Action
27
- from dara.core.definitions import ComponentInstance
28
+ from dara.core.base_definitions import DaraBaseModel as BaseModel
28
29
  from dara.core.interactivity import (
29
- AnyDataVariable,
30
- NonDataVariable,
31
- UrlVariable,
30
+ AnyVariable,
31
+ ClientVariable,
32
32
  Variable,
33
33
  )
34
+ from dara.core.logging import dev_logger
34
35
 
35
36
 
36
37
  class TableFormatterType(Enum):
@@ -47,6 +48,9 @@ class TableFormatterType(Enum):
47
48
  THRESHOLD = 'threshold'
48
49
 
49
50
 
51
+ TFormatterType = type[TableFormatterType]
52
+
53
+
50
54
  class TableFormatterCompareCondition(Enum):
51
55
  EQUAL = 'equal'
52
56
 
@@ -58,6 +62,9 @@ class TableFilter(Enum):
58
62
  DATETIME = 'datetime'
59
63
 
60
64
 
65
+ TFilterType = type[TableFilter]
66
+
67
+
61
68
  class Column(BaseModel):
62
69
  """
63
70
  Internal representation of a Table column, required for serialization to work correctly
@@ -261,7 +268,7 @@ class Column(BaseModel):
261
268
 
262
269
  from pandas import DataFrame
263
270
  from dara.components.common import Table
264
- from dara.core import DataVariable
271
+ from dara.core import ServerVariable
265
272
 
266
273
  Table.column(
267
274
  col_id='col1',
@@ -285,7 +292,7 @@ class Column(BaseModel):
285
292
  .
286
293
  .
287
294
  ])
288
- data_var = DataVariable(data)
295
+ data_var = ServerVariable(data)
289
296
 
290
297
  ```
291
298
 
@@ -373,28 +380,35 @@ class Column(BaseModel):
373
380
  :param type: Optional prop to specify type of column, used to determine e.g. filter type. If not specified, inferred from formatters
374
381
  """
375
382
 
376
- align: Optional[str] = None
383
+ align: str | None = None
377
384
  col_id: str
378
- unique_items: Optional[List[str]] = None
379
- filter: Optional[TableFilter] = None
380
- formatter: Optional[dict] = None
381
- label: Optional[str] = None
382
- sticky: Optional[str] = None
383
- tooltip: Optional[str] = None
384
- width: Optional[Union[int, str]] = None
385
- type: Optional[Union[Literal['number'], Literal['string'], Literal['datetime']]] = None
386
-
387
- class Config:
388
- use_enum_values = True
389
-
390
- @validator('label', always=True)
385
+ unique_items: list[str] | None = None
386
+ filter: TableFilter | None = None
387
+ formatter: dict | None = None
388
+ label: str | None = Field(default=None, validate_default=True) # mimics always=True in pydantic v1
389
+ sticky: str | None = None
390
+ tooltip: str | None = None
391
+ width: int | str | None = None
392
+ type: (
393
+ Literal['number']
394
+ | Literal['string']
395
+ | Literal['datetime']
396
+ | Literal['datetime64[ns]']
397
+ | Literal['datetime64[ms]']
398
+ | Literal['datetime64[s]']
399
+ | None
400
+ ) = None
401
+
402
+ model_config = ConfigDict(use_enum_values=True)
403
+
404
+ @field_validator('label')
391
405
  @classmethod
392
- def validate_label(cls, value, values):
406
+ def validate_label(cls, value, info: ValidationInfo):
393
407
  if value is None:
394
- return values.get('col_id')
408
+ return info.data.get('col_id')
395
409
  return value
396
410
 
397
- @validator('formatter')
411
+ @field_validator('formatter')
398
412
  @classmethod
399
413
  def vaildate_formatter_dict(cls, formatter):
400
414
  """
@@ -405,13 +419,12 @@ class Column(BaseModel):
405
419
  formatter_type = formatter.get('type')
406
420
  if formatter_type not in TableFormatterType:
407
421
  raise ValueError(
408
- f"Invalid formatter type: {formatter.get('type')}, accepted values {list(TableFormatterType)}"
422
+ f'Invalid formatter type: {formatter.get("type")}, accepted values {list(TableFormatterType)}'
409
423
  )
410
424
  if formatter_type in (TableFormatterType.NUMBER, TableFormatterType.PERCENT):
411
425
  precision = formatter.get('precision')
412
- if precision is not None:
413
- if not isinstance(precision, int) or precision <= 0:
414
- raise ValueError(f'Invalid precision value: {precision}, must be positive integer')
426
+ if precision is not None and (not isinstance(precision, int) or precision <= 0):
427
+ raise ValueError(f'Invalid precision value: {precision}, must be positive integer')
415
428
  elif formatter_type == TableFormatterType.NUMBER_INTL:
416
429
  locales = formatter.get('locales')
417
430
  if not isinstance(locales, str) or not (
@@ -445,9 +458,9 @@ class Column(BaseModel):
445
458
  )
446
459
  lower_bound = threshold.get('bounds')[0]
447
460
  upper_bound = threshold.get('bounds')[1]
448
- if not (isinstance(lower_bound, int) or isinstance(lower_bound, float)):
461
+ if not (isinstance(lower_bound, (int, float))):
449
462
  raise ValueError(f'Invalid bound type: {lower_bound}, must be int or float')
450
- if not (isinstance(upper_bound, int) or isinstance(upper_bound, float)):
463
+ if not (isinstance(upper_bound, (int, float))):
451
464
  raise ValueError(f'Invalid bound type: {upper_bound}, must be int or float')
452
465
  elif formatter_type == TableFormatterType.BADGE:
453
466
  badges = formatter.get('badges')
@@ -464,11 +477,11 @@ class Column(BaseModel):
464
477
  )
465
478
  if not isinstance(badges[badge].get('color'), str):
466
479
  raise ValueError(
467
- f"Invalid color: {badges[badge].get('color')} for badge: {badges[badge]}, must be a string"
480
+ f'Invalid color: {badges[badge].get("color")} for badge: {badges[badge]}, must be a string'
468
481
  )
469
482
  return formatter
470
483
 
471
- @validator('sticky')
484
+ @field_validator('sticky')
472
485
  @classmethod
473
486
  def validate_sticky(cls, sticky):
474
487
  """
@@ -478,15 +491,15 @@ class Column(BaseModel):
478
491
  raise ValueError(f'Invalid sticky value: {sticky}, accepted values: left, right')
479
492
  return sticky
480
493
 
481
- @validator('filter')
494
+ @field_validator('filter')
482
495
  @classmethod
483
- def validate_unique_items(cls, filter, values):
496
+ def validate_unique_items(cls, filter, info: ValidationInfo):
484
497
  """
485
498
  Validate that for categorical filters unique_items is provided
486
499
  """
487
500
  if filter == 'categorical':
488
- unique_items = values.get('unique_items')
489
- col_id = values.get('col_id')
501
+ unique_items = info.data.get('unique_items')
502
+ col_id = info.data.get('col_id')
490
503
  if unique_items is None:
491
504
  raise ValueError(
492
505
  f'Invalid unique_items: {col_id} has categorical filter, it must have unique_items defined'
@@ -499,10 +512,113 @@ class Table(ContentComponent):
499
512
  ![Table](../../../../docs/packages/dara-components/common/assets/Table.png)
500
513
 
501
514
  A Table Component for drawing a simple table into a document. Table's can be created by passing data
502
- as a DataVariable or a DerivedDataVariable. Columns can be passed as lists of dicts, where the col_id
503
- of the cols must match the column name in the dataframe. Alternatively they can be Variables/DerivedVariables,
504
- and if left undefined it will be inferred from the data passed.
515
+ as a ServerVariable, DerivedVariable, DataFrame or correctly formatted raw tabular data.
516
+
517
+ For simple client-side tables, you can directly pass data as list of records, where each record is a dict with string keys.
518
+
519
+ ```python
520
+ from dara.components.common import Table
521
+
522
+ Table(
523
+ data=[
524
+ {
525
+ 'col1': 'a',
526
+ 'col2': 1,
527
+ 'col3': 'F',
528
+ 'col4': '1990-02-12T00:00:00.000Z',
529
+ },
530
+ {
531
+ 'col1': 'b',
532
+ 'col2': 2,
533
+ 'col3': 'M',
534
+ 'col4': '1991-02-12T00:00:00.000Z',
535
+ },
536
+ ]
537
+ )
538
+ ```
539
+
540
+ You can also pass a DataFrame directly:
541
+
542
+ ```python
543
+ import pandas
544
+ from dara.components.common import Table
545
+
546
+ data = pandas.DataFrame([
547
+ {
548
+ 'col1': 'a',
549
+ 'col2': 1,
550
+ 'col3': 'F',
551
+ 'col4': '1990-02-12T00:00:00.000Z',
552
+ },
553
+ {
554
+ 'col1': 'b',
555
+ 'col2': 2,
556
+ 'col3': 'M',
557
+ 'col4': '1991-02-12T00:00:00.000Z',
558
+ },
559
+ ])
505
560
 
561
+ Table(data=data)
562
+ ```
563
+
564
+ When working with larger datasets, it is recommended to use a ServerVariable or DerivedVariable to avoid sending the entire dataset to the client.
565
+ They have built-in server-side filtering and pagination which is utilized by the Table component and integrated into its UI. They both support customization
566
+ of the filtering and pagination behavior, respectively via a custom `ServerVariable.backend` or a custom `DerivedVariable.filter_resolver`.
567
+
568
+ ServerVariables are ideal for static or mutable dataset that can be shared among users or specific to a user (with `scope='user'`) via e.g. data upload.
569
+ If possible, avoid recreating ServerVariables within e.g. `py_components`, as each instance will store a new copy of the data in memory.
570
+
571
+ ```python
572
+ from dara.core import ServerVariable
573
+ from dara.components.common import Table
574
+
575
+ data = ServerVariable(pandas.DataFrame([
576
+ {
577
+ 'col1': 'a',
578
+ 'col2': 1,
579
+ 'col3': 'F',
580
+ 'col4': '1990-02-12T00:00:00.000Z',
581
+ },
582
+ {
583
+ 'col1': 'b',
584
+ 'col2': 2,
585
+ 'col3': 'M',
586
+ 'col4': '1991-02-12T00:00:00.000Z',
587
+ },
588
+ ]))
589
+
590
+ Table(data=data)
591
+ ```
592
+
593
+ DerivedVariables allow you to create or resolve datasets on the fly, e.g. depending on user input.
594
+
595
+ ```python
596
+ import pandas
597
+ from dara.core import DerivedVariable
598
+ from dara.components.common import Table
599
+
600
+ async def create_data(user_input: str, row_count: int):
601
+ df = pandas.DataFrame()
602
+ for i in range(row_count):
603
+ df = df.append({
604
+ 'col1': user_input,
605
+ 'col2': i,
606
+ 'col3': 'F',
607
+ 'col4': '1990-02-12T00:00:00.000Z',
608
+ }, ignore_index=True)
609
+ return df
610
+
611
+ user_input = Variable('a')
612
+ row_count = Variable(10)
613
+
614
+ data = DerivedVariable(create_data, variables=[user_input, row_count])
615
+
616
+ Table(data=data)
617
+ ```
618
+
619
+ By default, the `Table` component will display all columns of the dataset. You can choose to customize which columns to display, their order, types and more.
620
+ Columns can be passed as lists of dicts, where the col_id of the cols must match the column name in the dataframe. Alternatively they can be Variables/DerivedVariables,
621
+ and if left undefined it will be inferred from the data passed.
506
622
  The list of columns can be a mix of dicts and strings. If a string is passed then it will be used as
507
623
  the col_id and label for the column whereas, passing a dict allows more control over the options. The
508
624
  available options for columns are as follows (see Column class for more detail):
@@ -520,9 +636,9 @@ class Table(ContentComponent):
520
636
 
521
637
  from pandas import DataFrame
522
638
  from dara.components.common import Table
523
- from dara.core import DataVariable
639
+ from dara.core import ServerVariable
524
640
 
525
- data = DataVariable(
641
+ data = ServerVariable(
526
642
  DataFrame([
527
643
  {
528
644
  'col1': 1,
@@ -549,9 +665,9 @@ class Table(ContentComponent):
549
665
 
550
666
  from pandas import DataFrame
551
667
  from dara.components.common import Table
552
- from dara.core import DataVariable
668
+ from dara.core import ServerVariable
553
669
 
554
- data = DataVariable(
670
+ data = ServerVariable(
555
671
  DataFrame([
556
672
  {
557
673
  'col1': 1,
@@ -590,9 +706,9 @@ class Table(ContentComponent):
590
706
 
591
707
  from pandas import DataFrame
592
708
  from dara.components.common import Table
593
- from dara.core import DataVariable, Variable
709
+ from dara.core import ServerVariable, Variable
594
710
 
595
- data = DataVariable(
711
+ data = ServerVariable(
596
712
  DataFrame([
597
713
  {
598
714
  'col1': 1,
@@ -626,9 +742,9 @@ class Table(ContentComponent):
626
742
 
627
743
  from pandas import DataFrame
628
744
  from dara.components.common import Table
629
- from dara.core import DataVariable
745
+ from dara.core import ServerVariable
630
746
 
631
- data = DataVariable(
747
+ data = ServerVariable(
632
748
  DataFrame([
633
749
  {
634
750
  'col1': 1,
@@ -659,7 +775,7 @@ class Table(ContentComponent):
659
775
 
660
776
  import pandas
661
777
  from dara.components.common import Table
662
- from dara.core import DataVariable
778
+ from dara.core import ServerVariable
663
779
 
664
780
  data = [{
665
781
  'age': 27,
@@ -672,7 +788,7 @@ class Table(ContentComponent):
672
788
  }]
673
789
  df = pandas.DataFrame(data, columns=['age', 'name', 'surname'])
674
790
  df.reset_index()
675
- Table(data=DataVariable(df), columns=[{'col_id': 'age', 'label': 'Age'}, 'index'])
791
+ Table(data=ServerVariable(df), columns=[{'col_id': 'age', 'label': 'Age'}, 'index'])
676
792
 
677
793
  ```
678
794
 
@@ -681,9 +797,9 @@ class Table(ContentComponent):
681
797
  ```python
682
798
  from pandas import DataFrame
683
799
  from dara.components.common import Table
684
- from dara.core import DataVariable
800
+ from dara.core import ServerVariable
685
801
 
686
- data = DataVariable(
802
+ data = ServerVariable(
687
803
  DataFrame(
688
804
  [
689
805
  {
@@ -720,42 +836,77 @@ class Table(ContentComponent):
720
836
  ```
721
837
 
722
838
  :param columns: The table's columns, this can be a list, a Variable/DerivedVariable or if left undefined it will be inferred from the data
723
- :param data: The table's data, must be a DataVariable or DerivedDataVariable
839
+ :param data: The table's data, can be a list of records or a Variable resolving to a list of records
724
840
  :param multi_select: Whether to allow selection of multiple rows, works with onclick_row and defaults to False
725
841
  :param show_checkboxes: Whether to show or hide checkboxes column when onclick_row is set. Defaults to True
726
842
  :param onclick_row: An action handler for when a row is clicked on the table
843
+ :param onselect_row: An action handler for when a row is selected via the checkbox column
727
844
  :param selected_indices: Optional variable to store the selected rows indices, must be a list of numbers. Note that these indices are
728
845
  the sequential indices of the rows as accepted by `DataFrame.iloc`, not the `row.index` value. If you would like the selection to persist over
729
- page reloads, you must set `persist_value=True` on the variable.
846
+ page reloads, you must use a `BrowserStore` on a `Variable`.
730
847
  :param search_columns: Optional list defining the columns to be searched, only the columns passed are searchable
731
848
  :param searchable: Boolean, if True table can be searched via Input and will only render matching rows
849
+ :param include_index: Boolean, if True the table will render the index column(s), defaults to True
732
850
  :param max_rows: if specified, table height will be fixed to accommodate the specified number of rows
851
+ :param suppress_click_events_for_selection: Whether to suppress click events for clicks in select boxes. Defaults to False
733
852
  """
734
853
 
735
- columns: Optional[Union[List[Union[Column, dict, str]], NonDataVariable]]
736
- data: AnyDataVariable
854
+ model_config = ConfigDict(ser_json_timedelta='float', use_enum_values=True, arbitrary_types_allowed=True)
855
+
856
+ columns: Sequence[Column | dict | str] | ClientVariable | None = None
857
+ data: AnyVariable | DataFrame | list
737
858
  multi_select: bool = False
738
859
  show_checkboxes: bool = True
739
- onclick_row: Optional[Action] = None
740
- selected_indices: Optional[Union[List[int], UrlVariable, Variable]] = None
741
- search_columns: Optional[List[str]] = None
860
+ onclick_row: Action | None = None
861
+ onselect_row: Action | None = None
862
+ selected_indices: list[int] | Variable | None = None
863
+ search_columns: list[str] | None = None
742
864
  searchable: bool = False
743
- max_rows: Optional[int] = None
744
- children: List[ComponentInstance] = []
865
+ include_index: bool = True
866
+ max_rows: int | None = None
867
+ suppress_click_events_for_selection: bool | None = False
745
868
 
746
- TableFormatterType = TableFormatterType
747
- TableFilter = TableFilter
869
+ TableFormatterType: ClassVar[TFormatterType] = TableFormatterType
870
+ TableFilter: ClassVar[TFilterType] = TableFilter
871
+
872
+ @field_validator('data')
873
+ @classmethod
874
+ def validate_data(cls, data):
875
+ # variables are fine, can't validate here
876
+ if isinstance(data, (DataFrame, AnyVariable)):
877
+ return data
878
+ if isinstance(data, list):
879
+ if not all(isinstance(item, dict) and all(isinstance(key, str) for key in item) for item in data):
880
+ raise ValueError(f'Invalid data passed to Table: {data}, expected a list of dicts with string keys')
881
+ return data
882
+ raise ValueError(f'Invalid data passed to Table: {type(data)}, expected a DataFrame or a variable')
883
+
884
+ @field_serializer('data', mode='wrap')
885
+ def serialize_field(self, value: Any, nxt: SerializerFunctionWrapHandler):
886
+ if isinstance(value, AnyVariable):
887
+ return nxt(value)
888
+
889
+ from dara.core.internal.encoder_registry import get_jsonable_encoder
890
+
891
+ try:
892
+ if isinstance(value, DataFrame):
893
+ value = value.to_dict(orient='records')
894
+ return jsonable_encoder(value, custom_encoder=get_jsonable_encoder())
895
+ except Exception as e:
896
+ dev_logger.error(
897
+ 'Error serializing raw data in Table, falling back to default serialization.'
898
+ 'Alternatively, you can provide a JSON-serializable dictionary in the "records format", i.e. `[{"col_a": 1}, {"col_a": 2}]`.',
899
+ error=e,
900
+ )
748
901
 
749
- class Config:
750
- json_encoders = {datetime: coerce_to_timemilli}
751
- use_enum_values = True
902
+ return nxt(value)
752
903
 
753
- @validator('columns')
904
+ @field_validator('columns')
754
905
  @classmethod
755
906
  def validate_columns(cls, columns):
756
907
  if columns is None:
757
908
  return None
758
- if isinstance(columns, List):
909
+ if isinstance(columns, list):
759
910
  cols = []
760
911
  for col in columns:
761
912
  if isinstance(col, Column):
@@ -765,14 +916,14 @@ class Table(ContentComponent):
765
916
  else:
766
917
  cols.append(Column(**col))
767
918
  return cols
768
- elif isinstance(columns, NonDataVariable):
919
+ elif isinstance(columns, ClientVariable):
769
920
  return columns
770
921
  else:
771
922
  raise ValueError(f'Invalid list passed to Table columns: {columns}, expected a list')
772
923
 
773
- def add_column(self, col: Union[str, dict, Column]):
924
+ def add_column(self, col: str | dict | Column):
774
925
  """Adds a new column to the data"""
775
- if not isinstance(self.columns, List):
926
+ if not isinstance(self.columns, list):
776
927
  raise ValueError('You cannot add_columns to a Variable type columns or if they are undefined')
777
928
  if isinstance(col, str):
778
929
  self.columns.append(Column(col_id=col))
@@ -15,12 +15,10 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from typing import Union
19
-
20
- from pydantic import validator
18
+ from pydantic import field_validator
21
19
 
22
20
  from dara.components.common.base_component import ContentComponent
23
- from dara.core.interactivity import NonDataVariable
21
+ from dara.core.interactivity import ClientVariable
24
22
 
25
23
 
26
24
  class Text(ContentComponent):
@@ -53,16 +51,16 @@ class Text(ContentComponent):
53
51
  :param formatted: Whether to display the text with existing formatting intact or not, default False
54
52
  """
55
53
 
56
- text: Union[str, NonDataVariable]
57
- align: str = 'left'
54
+ text: str | ClientVariable
55
+ align: str | None = 'left' # type: ignore # this is actually textAlign not align-items
58
56
  formatted: bool = False
59
57
 
60
- @validator('text')
58
+ @field_validator('text')
61
59
  @classmethod
62
60
  def only_strings(cls, value: str):
63
- if not isinstance(value, (str, NonDataVariable)):
61
+ if not isinstance(value, (str, ClientVariable)):
64
62
  raise ValueError(f'Invalid text passed to Text: {value}, expected a string')
65
63
  return value
66
64
 
67
- def __init__(self, text: Union[str, NonDataVariable], **kwargs):
65
+ def __init__(self, text: str | ClientVariable, **kwargs):
68
66
  super().__init__(text=text, **kwargs)
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from typing import Any, Literal, Optional
18
+ from typing import Any, Literal
19
19
 
20
20
  from dara.components.common.base_component import FormComponent
21
21
  from dara.core.base_definitions import Action
@@ -33,12 +33,12 @@ class Textarea(FormComponent):
33
33
  A Textarea component is created via:
34
34
 
35
35
  ```python
36
-
37
36
  from dara.core import Variable
38
37
  from dara.components.common import Textarea
39
38
 
40
- Textarea(value=Variable('initial textarea content'))
39
+ value_var = Variable('initial textarea content')
41
40
 
41
+ Textarea(value=value_var)
42
42
  ```
43
43
 
44
44
  :param autofocus: Boolean, if True then then initially render with the cursor in the component
@@ -49,7 +49,7 @@ class Textarea(FormComponent):
49
49
  """
50
50
 
51
51
  autofocus: bool = False
52
- value: Optional[Variable[Any]] = None
53
- onchange: Optional[Action] = None
54
- id: Optional[str] = None
55
- resize: Optional[Literal['none', 'both', 'horizontal', 'vertical', 'block', 'inline']] = None
52
+ value: Variable[Any] | None = None
53
+ onchange: Action | None = None
54
+ id: str | None = None
55
+ resize: Literal['none', 'both', 'horizontal', 'vertical', 'block', 'inline'] | None = None
@@ -16,7 +16,6 @@ limitations under the License.
16
16
  """
17
17
 
18
18
  import datetime
19
- from typing import Union
20
19
 
21
20
  import numpy
22
21
 
@@ -37,11 +36,9 @@ def date_to_datetime(d: datetime.date, hh=23, mm=59, ss=59) -> datetime.datetime
37
36
  return datetime.datetime(d.year, d.month, d.day, hh, mm, ss)
38
37
 
39
38
 
40
- def coerce_to_timemilli(t: Union[int, float, datetime.date, datetime.datetime]) -> float:
39
+ def coerce_to_timemilli(t: int | float | datetime.date | datetime.datetime) -> float:
41
40
  """Convert any of int/float/date/datetime into a timemilli."""
42
- if isinstance(t, int) or isinstance(t, numpy.int_):
43
- return float(t)
44
- elif isinstance(t, float) or isinstance(t, numpy.float_):
41
+ if isinstance(t, (int, numpy.signedinteger, float, numpy.floating)):
45
42
  return float(t)
46
43
  # The order matters - datetime.datetime is a subclass of the datetime.date
47
44
  elif isinstance(t, datetime.datetime):
@@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from typing import Union
19
-
20
18
  from dara.components.common.base_component import ModifierComponent
21
19
  from dara.components.common.stack import Stack
22
20
  from dara.core.definitions import ComponentInstance
@@ -74,17 +72,17 @@ class Tooltip(ModifierComponent):
74
72
  :param styling: Defines the style of the tooltip, can be 'default' or 'error'
75
73
  """
76
74
 
77
- content: Union[str, ComponentInstance, Variable[str], DerivedVariable[str]]
75
+ content: str | ComponentInstance | Variable[str] | DerivedVariable[str]
78
76
  placement: str = 'auto'
79
77
  styling: str = 'default'
80
78
 
81
79
  def __init__(
82
80
  self,
83
- *components: ComponentInstance,
84
- content: Union[str, ComponentInstance, Variable[str], DerivedVariable[str]],
81
+ *components: ComponentInstance | None,
82
+ content: str | ComponentInstance | Variable[str] | DerivedVariable[str],
85
83
  placement: str = 'auto',
86
84
  styling: str = 'default',
87
- **kwargs
85
+ **kwargs,
88
86
  ):
89
87
  # Unless there's one component and it's Stack, wrap components in Stack
90
88
  if not (len(components) == 1 and isinstance(components[0], Stack)):