streamlit-schema-editor 0.1.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.
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from .api import streamlit_schema_editor
4
+ from .types import (
5
+ ColumnSpec,
6
+ EventContext,
7
+ GroupSpec,
8
+ GroupLayoutMode,
9
+ Metadata,
10
+ Position,
11
+ RelationshipSpec,
12
+ SchemaEditorEvent,
13
+ SchemaEditorValue,
14
+ SelectionState,
15
+ TableSpec,
16
+ TableLayoutWithinGroupMode,
17
+ ValidationSpec,
18
+ ValidationStatus,
19
+ )
20
+
21
+ __all__ = [
22
+ "ColumnSpec",
23
+ "EventContext",
24
+ "GroupSpec",
25
+ "GroupLayoutMode",
26
+ "Metadata",
27
+ "Position",
28
+ "RelationshipSpec",
29
+ "SchemaEditorEvent",
30
+ "SchemaEditorValue",
31
+ "SelectionState",
32
+ "TableSpec",
33
+ "TableLayoutWithinGroupMode",
34
+ "ValidationSpec",
35
+ "ValidationStatus",
36
+ "streamlit_schema_editor",
37
+ ]
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import streamlit as st
7
+ from streamlit.errors import StreamlitAPIException
8
+
9
+ _COMPONENT: Any | None = None
10
+ _COMPONENT_NAME = "streamlit-schema-editor.streamlit_schema_editor"
11
+
12
+
13
+ def _load_built_asset(pattern: str) -> str:
14
+ build_dir = Path(__file__).resolve().parent / "frontend" / "build"
15
+ matches = sorted(build_dir.glob(pattern))
16
+ if len(matches) != 1:
17
+ raise RuntimeError(
18
+ f"Expected exactly one built asset matching '{pattern}' in '{build_dir}', found {len(matches)}."
19
+ )
20
+ content = matches[0].read_text(encoding="utf-8")
21
+ # CCv2 treats path-like single-line strings as file references. Add an internal
22
+ # newline so this is always interpreted as inline JS/CSS during fallback.
23
+ return f"{content}\n/* streamlit-schema-editor-inline */"
24
+
25
+
26
+ def get_component() -> Any:
27
+ global _COMPONENT
28
+ if _COMPONENT is None:
29
+ try:
30
+ _COMPONENT = st.components.v2.component(
31
+ _COMPONENT_NAME,
32
+ html='<div class="react-root"></div>',
33
+ js="index-*.js",
34
+ css="index-*.css",
35
+ )
36
+ except StreamlitAPIException as exc:
37
+ # Local source runs may not have manifest discovery active.
38
+ if "must be declared in pyproject.toml with asset_dir" not in str(exc):
39
+ raise
40
+
41
+ _COMPONENT = st.components.v2.component(
42
+ _COMPONENT_NAME,
43
+ html='<div class="react-root"></div>',
44
+ js=_load_built_asset("index-*.js"),
45
+ css=_load_built_asset("index-*.css"),
46
+ )
47
+ return _COMPONENT
48
+
49
+
50
+ __all__ = ["get_component"]
@@ -0,0 +1,232 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, cast
4
+
5
+ from ._component import get_component
6
+ from .types import (
7
+ GroupLayoutMode,
8
+ GroupSpec,
9
+ SchemaEditorValue,
10
+ SelectionState,
11
+ TableLayoutWithinGroupMode,
12
+ TableSpec,
13
+ RelationshipSpec,
14
+ )
15
+
16
+
17
+ def _noop() -> None:
18
+ """Keep CCv2 state keys available in the returned result."""
19
+
20
+
21
+ def _coerce_result(result: Any) -> dict[str, Any]:
22
+ if isinstance(result, dict):
23
+ return result
24
+
25
+ if result is None:
26
+ return {}
27
+
28
+ coerced: dict[str, Any] = {}
29
+ for key in (
30
+ "groups",
31
+ "tables",
32
+ "relationships",
33
+ "selection",
34
+ "event",
35
+ "event_context",
36
+ ):
37
+ if hasattr(result, key):
38
+ coerced[key] = getattr(result, key)
39
+
40
+ return coerced
41
+
42
+
43
+ def _coerce_selection(value: Any) -> SelectionState:
44
+ if isinstance(value, dict):
45
+ return {
46
+ "selected_table_id": value.get("selected_table_id"),
47
+ "selected_column_id": value.get("selected_column_id"),
48
+ "selected_relationship_id": value.get("selected_relationship_id"),
49
+ }
50
+
51
+ return {
52
+ "selected_table_id": getattr(value, "selected_table_id", None),
53
+ "selected_column_id": getattr(value, "selected_column_id", None),
54
+ "selected_relationship_id": getattr(value, "selected_relationship_id", None),
55
+ }
56
+
57
+
58
+ def _resolve_interaction_flag(explicit: bool | None, *, editable: bool) -> bool:
59
+ if explicit is not None:
60
+ return explicit
61
+
62
+ return editable
63
+
64
+
65
+ def _resolve_draggable_flag(explicit: bool | None) -> bool:
66
+ if explicit is not None:
67
+ return explicit
68
+
69
+ return True
70
+
71
+
72
+ def _resolve_limit(
73
+ override: int | None,
74
+ fallback: int | None,
75
+ ) -> int | None:
76
+ if override is not None:
77
+ return override
78
+
79
+ return fallback
80
+
81
+
82
+ def _state_or_fallback(
83
+ value: dict[str, Any],
84
+ key: str,
85
+ fallback: Any,
86
+ ) -> Any:
87
+ resolved = value.get(key)
88
+ if resolved is None:
89
+ return fallback
90
+
91
+ return resolved
92
+
93
+
94
+ def streamlit_schema_editor(
95
+ tables: list[TableSpec],
96
+ relationships: list[RelationshipSpec],
97
+ *,
98
+ groups: list[GroupSpec] | None = None,
99
+ height: int = 600,
100
+ fit_view: bool = True,
101
+ editable: bool = True,
102
+ connectable: bool | None = None,
103
+ draggable: bool | None = None,
104
+ deletable: bool | None = None,
105
+ show_controls: bool = False,
106
+ show_arrowheads: bool = True,
107
+ show_edge_button: bool = False,
108
+ show_column_count_badge: bool = True,
109
+ show_groups: bool = True,
110
+ group_layout: GroupLayoutMode = "manual",
111
+ group_order: list[str] | None = None,
112
+ table_layout_within_group: TableLayoutWithinGroupMode = "manual",
113
+ show_validation: bool = True,
114
+ validation_refresh_key: str | int | float | None = None,
115
+ column_type_options: list[str] | None = None,
116
+ allow_zoom: bool = True,
117
+ allow_duplicate_edges: bool = False,
118
+ max_connections_per_handle: int | None = None,
119
+ max_incoming_connections_per_handle: int | None = None,
120
+ max_outgoing_connections_per_handle: int | None = None,
121
+ max_incoming_per_target: int | None = None,
122
+ max_outgoing_per_source: int | None = None,
123
+ key: str | None = None,
124
+ ) -> SchemaEditorValue:
125
+ """Render a schema canvas backed by React Flow.
126
+
127
+ Parameters
128
+ ----------
129
+ tables:
130
+ The tables to render as database-schema nodes.
131
+ groups:
132
+ Optional labeled group containers used to visually organize tables.
133
+ relationships:
134
+ Relationships between table columns.
135
+ height:
136
+ Pixel height passed to the mounted component.
137
+ fit_view:
138
+ Whether the initial render should auto-fit the graph.
139
+ key:
140
+ Optional Streamlit component key.
141
+
142
+ Returns
143
+ -------
144
+ SchemaEditorValue
145
+ The current group positions, table positions, relationships, selection
146
+ state, and the last semantic event emitted by the component.
147
+ """
148
+
149
+ default_selection: SelectionState = {
150
+ "selected_table_id": None,
151
+ "selected_column_id": None,
152
+ "selected_relationship_id": None,
153
+ }
154
+ resolved_connectable = _resolve_interaction_flag(connectable, editable=editable)
155
+ resolved_draggable = _resolve_draggable_flag(draggable)
156
+ resolved_deletable = _resolve_interaction_flag(deletable, editable=editable)
157
+ resolved_max_incoming_connections_per_handle = _resolve_limit(
158
+ max_incoming_connections_per_handle,
159
+ max_connections_per_handle,
160
+ )
161
+ resolved_max_outgoing_connections_per_handle = _resolve_limit(
162
+ max_outgoing_connections_per_handle,
163
+ max_connections_per_handle,
164
+ )
165
+ if max_incoming_per_target is not None:
166
+ resolved_max_incoming_connections_per_handle = max_incoming_per_target
167
+ if max_outgoing_per_source is not None:
168
+ resolved_max_outgoing_connections_per_handle = max_outgoing_per_source
169
+
170
+ component_value = get_component()(
171
+ key=key,
172
+ height=height,
173
+ default={
174
+ "selection": default_selection,
175
+ "event_context": None,
176
+ },
177
+ data={
178
+ "groups": groups,
179
+ "tables": tables,
180
+ "relationships": relationships,
181
+ "editable": editable,
182
+ "fit_view": fit_view,
183
+ "height": height,
184
+ "connectable": resolved_connectable,
185
+ "draggable": resolved_draggable,
186
+ "deletable": resolved_deletable,
187
+ "show_controls": show_controls,
188
+ "show_arrowheads": show_arrowheads,
189
+ "show_edge_button": show_edge_button,
190
+ "show_column_count_badge": show_column_count_badge,
191
+ "show_groups": show_groups,
192
+ "group_layout": group_layout,
193
+ "group_order": group_order,
194
+ "table_layout_within_group": table_layout_within_group,
195
+ "show_validation": show_validation,
196
+ "validation_refresh_key": validation_refresh_key,
197
+ "column_type_options": column_type_options,
198
+ "allow_zoom": allow_zoom,
199
+ "allow_duplicate_edges": allow_duplicate_edges,
200
+ "max_connections_per_handle": max_connections_per_handle,
201
+ "max_incoming_connections_per_handle": resolved_max_incoming_connections_per_handle,
202
+ "max_outgoing_connections_per_handle": resolved_max_outgoing_connections_per_handle,
203
+ },
204
+ on_groups_change=_noop,
205
+ on_tables_change=_noop,
206
+ on_relationships_change=_noop,
207
+ on_selection_change=_noop,
208
+ on_event_change=_noop,
209
+ on_event_context_change=_noop,
210
+ )
211
+
212
+ value = _coerce_result(component_value)
213
+ selection = _coerce_selection(value.get("selection") or default_selection)
214
+
215
+ return cast(
216
+ SchemaEditorValue,
217
+ {
218
+ "groups": _state_or_fallback(value, "groups", groups or []),
219
+ "tables": _state_or_fallback(value, "tables", tables),
220
+ "relationships": _state_or_fallback(value, "relationships", relationships),
221
+ "selection": {
222
+ "selected_table_id": selection["selected_table_id"],
223
+ "selected_column_id": selection["selected_column_id"],
224
+ "selected_relationship_id": selection["selected_relationship_id"],
225
+ },
226
+ "event": value.get("event"),
227
+ "event_context": value.get("event_context"),
228
+ },
229
+ )
230
+
231
+
232
+ __all__ = ["streamlit_schema_editor"]