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
@@ -17,40 +17,35 @@ limitations under the License.
17
17
 
18
18
  import os
19
19
  from enum import Enum
20
- from typing import Any, Optional, Union
20
+ from typing import Any
21
21
 
22
- from pydantic import BaseModel
23
-
24
- from dara.core.base_definitions import DaraBaseModel
22
+ from dara.core.base_definitions import DaraBaseModel as BaseModel
25
23
  from dara.core.definitions import ComponentInstanceType
26
24
 
27
25
 
28
26
  class ItemBadge(BaseModel):
29
27
  """Add a badge to an item in a list"""
30
28
 
31
- color: Optional[str]
29
+ color: str | None = None
32
30
  label: str
33
31
 
34
32
 
35
- class Item(DaraBaseModel):
33
+ class Item(BaseModel):
36
34
  """
37
35
  A class for serializing a list of options for the select to show
38
36
  """
39
37
 
40
- badge: Optional[ItemBadge]
41
- image: Optional[str]
38
+ badge: ItemBadge | None = None
39
+ image: str | None = None
42
40
  label: str
43
- value: Union[str, int, float, BaseModel]
44
-
45
- class Config:
46
- smart_union = True
41
+ value: str | int | float | BaseModel
47
42
 
48
43
  def __init__(
49
44
  self,
50
- value: Union[str, int, float, BaseModel],
45
+ value: str | int | float | BaseModel,
51
46
  label: str,
52
- badge: Optional[ItemBadge] = None,
53
- image: Optional[str] = None,
47
+ badge: ItemBadge | None = None,
48
+ image: str | None = None,
54
49
  ):
55
50
  # Handle general enums in the value field
56
51
  if isinstance(value, Enum):
@@ -83,14 +78,14 @@ class Item(DaraBaseModel):
83
78
  try:
84
79
  label = str(item)
85
80
  return Item(label=label, value=item)
86
- except Exception:
81
+ except Exception as e:
87
82
  raise ValueError(
88
83
  f'Item: {item} could not be parsed correctly. If your item is a complex structure please pass in an '
89
84
  f'Item class, with a string label defined'
90
- )
85
+ ) from e
91
86
 
92
87
 
93
- class CarouselItem(DaraBaseModel):
88
+ class CarouselItem(BaseModel):
94
89
  """
95
90
  CarouselItem provides with the following props:
96
91
 
@@ -103,25 +98,24 @@ class CarouselItem(DaraBaseModel):
103
98
  :param image_width: Optional string containing the width the image should take
104
99
  """
105
100
 
106
- title: Optional[str]
107
- subtitle: Optional[str]
108
- component: Optional[ComponentInstanceType]
109
- image: Optional[str]
110
- image_alt: Optional[str]
111
- image_height: Optional[str]
112
- image_width: Optional[str]
101
+ title: str | None = None
102
+ subtitle: str | None = None
103
+ component: ComponentInstanceType | None = None
104
+ image: str | None = None
105
+ image_alt: str | None = None
106
+ image_height: str | None = None
107
+ image_width: str | None = None
113
108
 
114
109
  def __init__(
115
110
  self,
116
- title: Optional[str] = None,
117
- subtitle: Optional[str] = None,
118
- component: Optional[ComponentInstanceType] = None,
119
- image: Optional[str] = None,
120
- image_alt: Optional[str] = None,
121
- image_height: Optional[str] = None,
122
- image_width: Optional[str] = None,
111
+ title: str | None = None,
112
+ subtitle: str | None = None,
113
+ component: ComponentInstanceType | None = None,
114
+ image: str | None = None,
115
+ image_alt: str | None = None,
116
+ image_height: str | None = None,
117
+ image_width: str | None = None,
123
118
  ):
124
-
125
119
  super().__init__(
126
120
  title=title,
127
121
  subtitle=subtitle,
@@ -154,8 +148,8 @@ class CarouselItem(DaraBaseModel):
154
148
  try:
155
149
  title = str(item)
156
150
  return CarouselItem(title=title)
157
- except Exception:
151
+ except Exception as e:
158
152
  raise ValueError(
159
153
  f'CarouselItem: {item} could not be parsed correctly. If your item is a complex structure please pass in an '
160
154
  f'CarouselItem class'
161
- )
155
+ ) from e
@@ -14,6 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
+ # ruff: noqa: F401, F403
17
18
 
18
19
  from dara.components.graphs.components import *
19
20
  from dara.components.graphs.definitions import *
@@ -15,8 +15,11 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from typing import Dict, List, Optional, Union
18
+ from typing import Any
19
19
 
20
+ from pydantic import ConfigDict, SerializerFunctionWrapHandler, field_serializer
21
+
22
+ from cai_causal_graph import CausalGraph, Skeleton
20
23
  from dara.components.graphs.definitions import (
21
24
  DEFAULT_LEGENDS,
22
25
  EditorMode,
@@ -54,25 +57,34 @@ class BaseGraphComponent(StyledComponentInstance):
54
57
  :param zoom_thresholds: Optional user-defined zoom thresholds. See [ZoomThresholds](../definitions/#zoomthresholds) for more details.
55
58
  """
56
59
 
57
- additional_legends: Optional[List[GraphLegend]] = None
58
- allow_selection_when_not_editable: Optional[bool] = False
59
- available_inputs: Optional[List[str]] = None
60
- default_legends: Dict[Union[EditorMode, str], List[GraphLegend]] = DEFAULT_LEGENDS
61
- disable_edge_add: Optional[bool] = None
62
- disable_latent_node_add: Optional[bool] = None
63
- disable_node_removal: Optional[bool] = None
64
- editable: Optional[bool] = False
65
- graph_layout: Optional[GraphLayout] = FcoseLayout()
66
- non_removable_nodes: Optional[List[str]] = None
67
- on_click_edge: Optional[Action] = None
68
- on_click_node: Optional[Action] = None
69
- on_update: Optional[Action] = None
70
- require_focus_to_zoom: Optional[bool] = True
71
- simultaneous_edge_node_selection: Optional[bool] = False
72
- tooltip_size: Optional[int] = None
73
- verbose_descriptions: Optional[bool] = None
74
- zoom_thresholds: Optional[ZoomThresholds] = None
60
+ additional_legends: list[GraphLegend] | None = None
61
+ allow_selection_when_not_editable: bool | None = False
62
+ available_inputs: list[str] | None = None
63
+ default_legends: dict[EditorMode | str, list[GraphLegend]] = DEFAULT_LEGENDS
64
+ disable_edge_add: bool | None = None
65
+ disable_latent_node_add: bool | None = None
66
+ disable_node_removal: bool | None = None
67
+ editable: bool | None = False
68
+ graph_layout: GraphLayout | None = FcoseLayout()
69
+ non_removable_nodes: list[str] | None = None
70
+ on_click_edge: Action | None = None
71
+ on_click_node: Action | None = None
72
+ on_update: Action | None = None
73
+ require_focus_to_zoom: bool | None = True
74
+ simultaneous_edge_node_selection: bool | None = False
75
+ tooltip_size: int | None = None
76
+ verbose_descriptions: bool | None = None
77
+ zoom_thresholds: ZoomThresholds | None = None
78
+
79
+ model_config = ConfigDict(
80
+ arbitrary_types_allowed=True,
81
+ extra='forbid',
82
+ )
83
+
84
+ @field_serializer('*', mode='wrap')
85
+ def serialize_graphs(self, value: Any, nxt: SerializerFunctionWrapHandler):
86
+ # handle serializing fields of type CausalGraph and Skeleton
87
+ if isinstance(value, (CausalGraph, Skeleton)):
88
+ return value.to_dict()
75
89
 
76
- class Config:
77
- arbitrary_types_allowed = True
78
- extra = 'forbid'
90
+ return nxt(value)
@@ -15,11 +15,9 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from typing import Optional, Union
18
+ from pydantic import field_validator, model_validator
19
19
 
20
20
  from cai_causal_graph import CausalGraph, Skeleton
21
- from pydantic import root_validator, validator
22
-
23
21
  from dara.components.graphs.components.base_graph_component import BaseGraphComponent
24
22
  from dara.components.graphs.definitions import EditorMode
25
23
  from dara.components.graphs.graph_layout import PlanarLayout
@@ -228,6 +226,10 @@ class CausalGraphViewer(BaseGraphComponent):
228
226
  :param disable_latent_node_add: Optional flag for disabling latent node addition
229
227
  :param disable_node_removal: Optional flag for disabling node removal
230
228
  :param editable: Optional flag to enable editing the graph by showing an editor frame around the graph
229
+ :param editor_mode: Optional editor mode to use. The following options are available:
230
+ EditorMode.DEFAULT - Default DAG viewer, assumes all edges are directed.
231
+ EditorMode.PAG - PAG viewer, displays all edge types.
232
+ EditorMode.RESOLVER - Resolver mode, allows users to accept edges.
231
233
  :param graph_layout: Optional layout configuration object
232
234
  :param non_removable_nodes: Optional list of node names that cannot be removed
233
235
  :param on_click_edge: Event handler for clicking on an edge
@@ -244,10 +246,10 @@ class CausalGraphViewer(BaseGraphComponent):
244
246
 
245
247
  js_module = '@darajs/components'
246
248
 
247
- causal_graph: Optional[Union[CausalGraph, DerivedVariable, Variable, dict, Skeleton]]
248
- editor_mode: Optional[EditorMode] = None
249
+ causal_graph: CausalGraph | DerivedVariable | Variable | dict | Skeleton
250
+ editor_mode: EditorMode | None = None
249
251
 
250
- @validator('causal_graph')
252
+ @field_validator('causal_graph')
251
253
  @classmethod
252
254
  def validate_causal_graph(cls, causal_graph):
253
255
  """
@@ -262,15 +264,11 @@ class CausalGraphViewer(BaseGraphComponent):
262
264
  )
263
265
  return causal_graph
264
266
 
265
- @root_validator
266
- @classmethod
267
- def validate_layout(cls, values: dict):
268
- if (
269
- isinstance(values.get('graph_layout'), PlanarLayout)
270
- and values.get('editor_mode', EditorMode.DEFAULT) != EditorMode.DEFAULT
271
- ):
267
+ @model_validator(mode='after')
268
+ def validate_layout(self):
269
+ if isinstance(self.graph_layout, PlanarLayout) and self.editor_mode != EditorMode.DEFAULT:
272
270
  dev_logger.warning(
273
271
  'Planar Layout is currently only supported with EditorMode.DEFAULT. Setting editor_mode to EditorMode.DEFAULT.'
274
272
  )
275
- values['editor_mode'] = EditorMode.DEFAULT
276
- return values
273
+ self.editor_mode = EditorMode.DEFAULT
274
+ return self
@@ -15,13 +15,19 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- from typing import Dict, List, Optional, Union
18
+ from typing import Any
19
+
20
+ from fastapi.encoders import jsonable_encoder
21
+ from pydantic import (
22
+ ConfigDict,
23
+ SerializerFunctionWrapHandler,
24
+ field_serializer,
25
+ model_validator,
26
+ )
27
+ from typing_extensions import TypedDict
19
28
 
20
29
  from cai_causal_graph.graph_components import Node
21
30
  from cai_causal_graph.type_definitions import EdgeConstraint as EdgeConstraintType
22
- from pydantic import root_validator
23
- from typing_extensions import TypedDict
24
-
25
31
  from dara.components.graphs.definitions import DEFAULT_LEGENDS, EditorMode, GraphLegend
26
32
  from dara.components.graphs.graph_layout import (
27
33
  GraphLayout,
@@ -30,7 +36,7 @@ from dara.components.graphs.graph_layout import (
30
36
  )
31
37
  from dara.core.base_definitions import Action
32
38
  from dara.core.definitions import StyledComponentInstance
33
- from dara.core.interactivity import NonDataVariable
39
+ from dara.core.interactivity import AnyVariable, ClientVariable
34
40
 
35
41
 
36
42
  class EdgeConstraint(TypedDict):
@@ -123,26 +129,43 @@ class VisualEdgeEncoder(StyledComponentInstance):
123
129
 
124
130
  js_module = '@darajs/components'
125
131
 
126
- additional_legends: Optional[List[GraphLegend]] = None
127
- allow_selection_when_not_editable: Optional[bool] = False
128
- default_legends: Dict[Union[EditorMode, str], List[GraphLegend]] = DEFAULT_LEGENDS
129
- editable: Optional[bool] = False
130
- graph_layout: Optional[GraphLayout] = MarketingLayout()
131
- initial_constraints: Optional[Union[List[EdgeConstraint], NonDataVariable]] = None
132
- nodes: Union[List[str], Dict[str, Node], NonDataVariable]
133
- on_click_edge: Optional[Action] = None
134
- on_click_node: Optional[Action] = None
135
- on_update: Optional[Action] = None
136
- require_focus_to_zoom: Optional[bool] = True
137
- tooltip_size: Optional[str] = None
138
-
139
- class Config:
140
- arbitrary_types_allowed = True
141
-
142
- @root_validator
143
- @classmethod
144
- def validate_layout(cls, values: dict):
145
- if isinstance(values.get('graph_layout'), PlanarLayout):
132
+ additional_legends: list[GraphLegend] | None = None
133
+ allow_selection_when_not_editable: bool | None = False
134
+ default_legends: dict[EditorMode | str, list[GraphLegend]] = DEFAULT_LEGENDS
135
+ editable: bool | None = False
136
+ graph_layout: GraphLayout | None = MarketingLayout()
137
+ initial_constraints: list[EdgeConstraint] | ClientVariable | None = None
138
+ nodes: list[str] | dict[str, Node] | ClientVariable
139
+ on_click_edge: Action | None = None
140
+ on_click_node: Action | None = None
141
+ on_update: Action | None = None
142
+ require_focus_to_zoom: bool | None = True
143
+ tooltip_size: str | None = None
144
+
145
+ model_config = ConfigDict(
146
+ arbitrary_types_allowed=True,
147
+ extra='forbid',
148
+ )
149
+
150
+ @model_validator(mode='after')
151
+ def validate_layout(self):
152
+ if isinstance(self.graph_layout, PlanarLayout):
146
153
  raise ValueError('Planar Layout is not currently supported by EdgeEncoder.')
147
154
 
148
- return values
155
+ return self
156
+
157
+ @field_serializer('nodes', mode='wrap')
158
+ def serialize_nodes(self, value: Any, nxt: SerializerFunctionWrapHandler):
159
+ if isinstance(value, dict):
160
+ if len(value.keys()) == 0:
161
+ return value
162
+ # Handle dict[str, Node]
163
+ if isinstance(value.get(list(value.keys())[0]), Node):
164
+ result = {k: v.to_dict() for k, v in value.items()}
165
+ return result
166
+
167
+ # For some reason just invoking nxt() in it will not invoke the proper variable serialization
168
+ if isinstance(value, AnyVariable):
169
+ return jsonable_encoder(value)
170
+
171
+ return nxt(value)
@@ -15,27 +15,28 @@ 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, Dict, List, Optional, Union
18
+ from typing import Any, ClassVar
19
19
 
20
- from pydantic import BaseModel, validator
20
+ from pydantic import field_validator
21
21
 
22
22
  from dara.core.base_definitions import Action
23
+ from dara.core.base_definitions import DaraBaseModel as BaseModel
23
24
  from dara.core.definitions import StyledComponentInstance
24
- from dara.core.interactivity import NonDataVariable
25
+ from dara.core.interactivity import ClientVariable
25
26
 
26
27
 
27
28
  class NodeMeta(BaseModel):
28
- label_size: Optional[int] = None
29
- wrap_text: Optional[bool] = None
30
- label: Optional[str] = None
31
- tooltip: Optional[Union[str, Dict[str, str]]] = None
29
+ label_size: int | None = None
30
+ wrap_text: bool | None = None
31
+ label: str | None = None
32
+ tooltip: str | dict[str, str] | None = None
32
33
 
33
34
 
34
35
  class Node(BaseModel):
35
36
  name: str
36
- meta: Optional[NodeMeta] = None
37
+ meta: NodeMeta | None = None
37
38
 
38
- Meta = NodeMeta
39
+ Meta: ClassVar[type[NodeMeta]] = NodeMeta
39
40
 
40
41
 
41
42
  class NodeHierarchyBuilder(StyledComponentInstance):
@@ -108,16 +109,16 @@ class NodeHierarchyBuilder(StyledComponentInstance):
108
109
  js_module = '@darajs/components'
109
110
 
110
111
  editable: bool = True
111
- nodes: Union[List[List[str]], List[str], List[Node], List[List[Node]], NonDataVariable]
112
- node_font_size: Optional[int] = None
113
- node_size: Optional[int] = None
114
- on_update: Optional[Action] = None
112
+ nodes: list[list[str]] | list[str] | list[Node] | list[list[Node]] | ClientVariable
113
+ node_font_size: int | None = None
114
+ node_size: int | None = None
115
+ on_update: Action | None = None
115
116
  wrap_node_text: bool = True
116
117
 
117
- @validator('nodes')
118
+ @field_validator('nodes')
118
119
  @classmethod
119
- def validate_nodes(cls, nodes: Any) -> Union[NonDataVariable, List[List[str]], List[List[Node]]]:
120
- if isinstance(nodes, NonDataVariable):
120
+ def validate_nodes(cls, nodes: Any) -> ClientVariable | list[list[str]] | list[list[Node]]:
121
+ if isinstance(nodes, ClientVariable):
121
122
  return nodes
122
123
  if not isinstance(nodes, list):
123
124
  raise ValueError('Nodes provided to NodeHierarchyBuilder must be a Variable or a list of strings/Nodes')
@@ -16,9 +16,11 @@ limitations under the License.
16
16
  """
17
17
 
18
18
  from enum import Enum
19
- from typing import ClassVar, Dict, List, Literal, Optional, Type, Union
19
+ from typing import ClassVar, Literal
20
20
 
21
- from pydantic import BaseModel, Field
21
+ from pydantic import Field
22
+
23
+ from dara.core.base_definitions import DaraBaseModel as BaseModel
22
24
 
23
25
 
24
26
  class EditorMode(str, Enum):
@@ -47,12 +49,17 @@ class CenterSymbol(str, Enum):
47
49
  BIDIRECTED = 'bidirected'
48
50
 
49
51
 
52
+ EdgeLegendType = type['EdgeLegend']
53
+ SpacerLegendType = type['SpacerLegend']
54
+ NodeLegendType = type['NodeLegend']
55
+
56
+
50
57
  class Legend(BaseModel):
51
58
  type: str
52
59
 
53
- Edge: ClassVar[Type['EdgeLegend']]
54
- Spacer: ClassVar[Type['SpacerLegend']]
55
- Node: ClassVar[Type['NodeLegend']]
60
+ Edge: ClassVar[EdgeLegendType]
61
+ Spacer: ClassVar[SpacerLegendType]
62
+ Node: ClassVar[NodeLegendType]
56
63
 
57
64
 
58
65
  class SpacerLegend(Legend):
@@ -62,8 +69,8 @@ class SpacerLegend(Legend):
62
69
  :param label: Optional label to show in the legend
63
70
  """
64
71
 
65
- type: Literal['spacer'] = Field(default='spacer', const=True)
66
- label: Optional[str] = None
72
+ type: Literal['spacer'] = Field(default='spacer', frozen=True) # type: ignore
73
+ label: str | None = None
67
74
 
68
75
 
69
76
  class EdgeLegend(Legend):
@@ -77,12 +84,12 @@ class EdgeLegend(Legend):
77
84
  :param dash_array: Optional [stroke-dasharray](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray) SVG path property - line will be dashed if specified
78
85
  """
79
86
 
80
- type: Literal['edge'] = Field(default='edge', const=True)
81
- label: Optional[str] = None
82
- arrow_type: Optional[ArrowType] = ArrowType.NORMAL
83
- center_symbol: Optional[CenterSymbol] = CenterSymbol.NONE
84
- color: Optional[str] = 'theme.grey5'
85
- dash_array: Optional[str] = None
87
+ type: Literal['edge'] = Field(default='edge', frozen=True) # type: ignore
88
+ label: str | None = None
89
+ arrow_type: ArrowType | None = ArrowType.NORMAL
90
+ center_symbol: CenterSymbol | None = CenterSymbol.NONE
91
+ color: str | None = 'theme.grey5'
92
+ dash_array: str | None = None
86
93
 
87
94
 
88
95
  class NodeLegend(Legend):
@@ -94,26 +101,26 @@ class NodeLegend(Legend):
94
101
  :param highlight_color: Optional color for the node symbol rim in the legend
95
102
  """
96
103
 
97
- type: Literal['node'] = Field(default='node', const=True)
98
- label: Optional[str] = None
99
- color: Optional[str] = 'theme.blue4'
100
- highlight_color: Optional[str] = 'theme.primary'
104
+ type: Literal['node'] = Field(default='node', frozen=True) # type: ignore
105
+ label: str | None = None
106
+ color: str | None = 'theme.blue4'
107
+ highlight_color: str | None = 'theme.primary'
101
108
 
102
109
 
103
110
  Legend.Edge = EdgeLegend
104
111
  Legend.Spacer = SpacerLegend
105
112
  Legend.Node = NodeLegend
106
113
 
107
- GraphLegend = Union[EdgeLegend, SpacerLegend, NodeLegend]
114
+ GraphLegend = EdgeLegend | SpacerLegend | NodeLegend
108
115
 
109
- DEFAULT_NODE_LEGENDS: List[GraphLegend] = [
116
+ DEFAULT_NODE_LEGENDS: list[GraphLegend] = [
110
117
  Legend.Node(color='theme.blue1', label='Latent'),
111
118
  Legend.Node(color='theme.secondary', label='Target'),
112
119
  Legend.Node(label='Other'),
113
120
  ]
114
121
 
115
122
  # Default legends for each editor mode
116
- DEFAULT_LEGENDS: Dict[Union[EditorMode, str], List[GraphLegend]] = {
123
+ DEFAULT_LEGENDS: dict[EditorMode | str, list[GraphLegend]] = {
117
124
  EditorMode.DEFAULT: DEFAULT_NODE_LEGENDS,
118
125
  EditorMode.PAG: [
119
126
  *DEFAULT_NODE_LEGENDS,