st-graph-workbench 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.
- st_graph_workbench/__init__.py +115 -0
- st_graph_workbench/component/__init__.py +1 -0
- st_graph_workbench/component/_expansion_contract.py +106 -0
- st_graph_workbench/component/_ids.py +44 -0
- st_graph_workbench/component/_typing.py +6 -0
- st_graph_workbench/component/_warnings.py +8 -0
- st_graph_workbench/component/commands.py +705 -0
- st_graph_workbench/component/component.py +990 -0
- st_graph_workbench/component/elements.py +191 -0
- st_graph_workbench/component/events.py +70 -0
- st_graph_workbench/component/expansion.py +1207 -0
- st_graph_workbench/component/expansion_provider.py +170 -0
- st_graph_workbench/component/icons.py +54 -0
- st_graph_workbench/component/layouts.py +100 -0
- st_graph_workbench/component/records.py +369 -0
- st_graph_workbench/component/styles.py +267 -0
- st_graph_workbench/component/types.py +83 -0
- st_graph_workbench/component/validation.py +147 -0
- st_graph_workbench/frontend/build/111-76b489c917f19c7113b7.js +1 -0
- st_graph_workbench/frontend/build/477-3b51cf5f6d2483b89d23.js +1 -0
- st_graph_workbench/frontend/build/527-15ddbbbebc4c095e1a00.js +1 -0
- st_graph_workbench/frontend/build/576-875a5e1c17a808590401.js +1 -0
- st_graph_workbench/frontend/build/644-fd47658f645d99a1478f.js +1 -0
- st_graph_workbench/frontend/build/95-b2593eb4f78f89f4f83a.js +1 -0
- st_graph_workbench/frontend/build/THIRD_PARTY_LICENSES.txt +651 -0
- st_graph_workbench/frontend/build/component.html +905 -0
- st_graph_workbench/frontend/build/icons/account_balance.svg +1 -0
- st_graph_workbench/frontend/build/icons/alternate_email.svg +1 -0
- st_graph_workbench/frontend/build/icons/analytics.svg +1 -0
- st_graph_workbench/frontend/build/icons/assured_workload.svg +1 -0
- st_graph_workbench/frontend/build/icons/badge.svg +1 -0
- st_graph_workbench/frontend/build/icons/business.svg +1 -0
- st_graph_workbench/frontend/build/icons/campaign.svg +1 -0
- st_graph_workbench/frontend/build/icons/cell_tower.svg +1 -0
- st_graph_workbench/frontend/build/icons/chat.svg +1 -0
- st_graph_workbench/frontend/build/icons/cloud.svg +1 -0
- st_graph_workbench/frontend/build/icons/credit_card.svg +1 -0
- st_graph_workbench/frontend/build/icons/description.svg +1 -0
- st_graph_workbench/frontend/build/icons/directions_car.svg +1 -0
- st_graph_workbench/frontend/build/icons/dns.svg +1 -0
- st_graph_workbench/frontend/build/icons/email.svg +1 -0
- st_graph_workbench/frontend/build/icons/factory.svg +1 -0
- st_graph_workbench/frontend/build/icons/flag.svg +1 -0
- st_graph_workbench/frontend/build/icons/flight.svg +1 -0
- st_graph_workbench/frontend/build/icons/folder.svg +1 -0
- st_graph_workbench/frontend/build/icons/group.svg +1 -0
- st_graph_workbench/frontend/build/icons/home.svg +1 -0
- st_graph_workbench/frontend/build/icons/inventory.svg +1 -0
- st_graph_workbench/frontend/build/icons/key.svg +1 -0
- st_graph_workbench/frontend/build/icons/language.svg +1 -0
- st_graph_workbench/frontend/build/icons/laptop.svg +1 -0
- st_graph_workbench/frontend/build/icons/link.svg +1 -0
- st_graph_workbench/frontend/build/icons/local_shipping.svg +1 -0
- st_graph_workbench/frontend/build/icons/lock.svg +1 -0
- st_graph_workbench/frontend/build/icons/medical_information.svg +1 -0
- st_graph_workbench/frontend/build/icons/medical_services.svg +1 -0
- st_graph_workbench/frontend/build/icons/monitor.svg +1 -0
- st_graph_workbench/frontend/build/icons/person.svg +1 -0
- st_graph_workbench/frontend/build/icons/phone.svg +1 -0
- st_graph_workbench/frontend/build/icons/photo_camera.svg +1 -0
- st_graph_workbench/frontend/build/icons/place.svg +1 -0
- st_graph_workbench/frontend/build/icons/request_quote.svg +1 -0
- st_graph_workbench/frontend/build/icons/router.svg +1 -0
- st_graph_workbench/frontend/build/icons/schedule.svg +1 -0
- st_graph_workbench/frontend/build/icons/science.svg +1 -0
- st_graph_workbench/frontend/build/icons/sell.svg +1 -0
- st_graph_workbench/frontend/build/icons/shopping_cart.svg +1 -0
- st_graph_workbench/frontend/build/icons/sim_card.svg +1 -0
- st_graph_workbench/frontend/build/icons/smartphone.svg +1 -0
- st_graph_workbench/frontend/build/icons/storage.svg +1 -0
- st_graph_workbench/frontend/build/icons/store.svg +1 -0
- st_graph_workbench/frontend/build/icons/swap_horiz.svg +1 -0
- st_graph_workbench/frontend/build/icons/train.svg +1 -0
- st_graph_workbench/frontend/build/icons/translate.svg +1 -0
- st_graph_workbench/frontend/build/icons/videocam.svg +1 -0
- st_graph_workbench/frontend/build/icons/wallet.svg +1 -0
- st_graph_workbench/frontend/build/icons/warehouse.svg +1 -0
- st_graph_workbench/frontend/build/icons/wifi.svg +1 -0
- st_graph_workbench/frontend/build/index-04eff230345e2c3afcce.js +2 -0
- st_graph_workbench/frontend/build/index-04eff230345e2c3afcce.js.LICENSE.txt +9 -0
- st_graph_workbench/frontend/build/style.css +1268 -0
- st_graph_workbench/py.typed +0 -0
- st_graph_workbench/pyproject.toml +7 -0
- st_graph_workbench-0.1.0.dist-info/METADATA +561 -0
- st_graph_workbench-0.1.0.dist-info/RECORD +91 -0
- st_graph_workbench-0.1.0.dist-info/WHEEL +4 -0
- st_graph_workbench-0.1.0.dist-info/licenses/LICENSE +202 -0
- st_graph_workbench-0.1.0.dist-info/licenses/NOTICE +14 -0
- st_graph_workbench-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +31 -0
- st_graph_workbench-0.1.0.dist-info/licenses/licenses/material-symbols.txt +202 -0
- st_graph_workbench-0.1.0.dist-info/licenses/licenses/st-link-analysis.txt +21 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from st_graph_workbench.component.component import (
|
|
2
|
+
AnalysisAction,
|
|
3
|
+
CrudAction,
|
|
4
|
+
EditAction,
|
|
5
|
+
ElementsSync,
|
|
6
|
+
NodeAction,
|
|
7
|
+
PerformanceProfile,
|
|
8
|
+
SelectionMode,
|
|
9
|
+
ViewportAction,
|
|
10
|
+
graph_workbench,
|
|
11
|
+
)
|
|
12
|
+
from st_graph_workbench.component.styles import NodeStyle, EdgeStyle, StyleRule
|
|
13
|
+
from st_graph_workbench.component.events import Event
|
|
14
|
+
from st_graph_workbench.component.elements import (
|
|
15
|
+
delete_elements,
|
|
16
|
+
get_element,
|
|
17
|
+
update_element_data,
|
|
18
|
+
upsert_elements,
|
|
19
|
+
)
|
|
20
|
+
from st_graph_workbench.component.records import (
|
|
21
|
+
dataframe_to_records,
|
|
22
|
+
records_to_dataframe,
|
|
23
|
+
)
|
|
24
|
+
from st_graph_workbench.component.types import (
|
|
25
|
+
ConnectedDragConfig,
|
|
26
|
+
Element,
|
|
27
|
+
ElementId,
|
|
28
|
+
ElementIdInput,
|
|
29
|
+
Elements,
|
|
30
|
+
GraphCommand,
|
|
31
|
+
GraphEvent,
|
|
32
|
+
Layout,
|
|
33
|
+
ProgressiveLoadConfig,
|
|
34
|
+
Record,
|
|
35
|
+
ToolbarConfig,
|
|
36
|
+
)
|
|
37
|
+
from st_graph_workbench.component.commands import (
|
|
38
|
+
GraphCommandValidationError,
|
|
39
|
+
add_elements_command,
|
|
40
|
+
apply_graph_command,
|
|
41
|
+
apply_graph_commands,
|
|
42
|
+
clear_graph_command,
|
|
43
|
+
delete_elements_command,
|
|
44
|
+
set_elements_command,
|
|
45
|
+
update_data_command,
|
|
46
|
+
upsert_elements_command,
|
|
47
|
+
validate_graph_commands,
|
|
48
|
+
viewport_command,
|
|
49
|
+
)
|
|
50
|
+
from st_graph_workbench.component.validation import (
|
|
51
|
+
ElementValidationError,
|
|
52
|
+
validate_elements,
|
|
53
|
+
)
|
|
54
|
+
from st_graph_workbench.component.expansion import (
|
|
55
|
+
ExpansionConfig,
|
|
56
|
+
ExpansionController,
|
|
57
|
+
ExpansionOperation,
|
|
58
|
+
ExpansionProvider,
|
|
59
|
+
ExpansionRequest,
|
|
60
|
+
ExpansionResponse,
|
|
61
|
+
)
|
|
62
|
+
from st_graph_workbench.component.expansion_provider import InMemoryExpansionProvider
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"ConnectedDragConfig",
|
|
66
|
+
"ExpansionConfig",
|
|
67
|
+
"ExpansionController",
|
|
68
|
+
"ExpansionOperation",
|
|
69
|
+
"ExpansionProvider",
|
|
70
|
+
"ExpansionRequest",
|
|
71
|
+
"ExpansionResponse",
|
|
72
|
+
"InMemoryExpansionProvider",
|
|
73
|
+
"graph_workbench",
|
|
74
|
+
"NodeStyle",
|
|
75
|
+
"EdgeStyle",
|
|
76
|
+
"StyleRule",
|
|
77
|
+
"Event",
|
|
78
|
+
"AnalysisAction",
|
|
79
|
+
"CrudAction",
|
|
80
|
+
"dataframe_to_records",
|
|
81
|
+
"delete_elements",
|
|
82
|
+
"Element",
|
|
83
|
+
"ElementId",
|
|
84
|
+
"ElementIdInput",
|
|
85
|
+
"ElementValidationError",
|
|
86
|
+
"Elements",
|
|
87
|
+
"EditAction",
|
|
88
|
+
"ElementsSync",
|
|
89
|
+
"GraphCommand",
|
|
90
|
+
"GraphCommandValidationError",
|
|
91
|
+
"GraphEvent",
|
|
92
|
+
"Layout",
|
|
93
|
+
"NodeAction",
|
|
94
|
+
"PerformanceProfile",
|
|
95
|
+
"ProgressiveLoadConfig",
|
|
96
|
+
"ToolbarConfig",
|
|
97
|
+
"SelectionMode",
|
|
98
|
+
"add_elements_command",
|
|
99
|
+
"apply_graph_command",
|
|
100
|
+
"apply_graph_commands",
|
|
101
|
+
"clear_graph_command",
|
|
102
|
+
"delete_elements_command",
|
|
103
|
+
"get_element",
|
|
104
|
+
"set_elements_command",
|
|
105
|
+
"update_data_command",
|
|
106
|
+
"update_element_data",
|
|
107
|
+
"upsert_elements",
|
|
108
|
+
"upsert_elements_command",
|
|
109
|
+
"records_to_dataframe",
|
|
110
|
+
"Record",
|
|
111
|
+
"validate_elements",
|
|
112
|
+
"validate_graph_commands",
|
|
113
|
+
"ViewportAction",
|
|
114
|
+
"viewport_command",
|
|
115
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal modules for the st-graph-workbench Streamlit component."""
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Shared record and filter validation for expansion controllers and providers."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ._ids import normalize_id
|
|
10
|
+
from .types import Elements
|
|
11
|
+
from .validation import validate_elements
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def positive_limit(value: int, name: str) -> int:
|
|
15
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
|
|
16
|
+
raise ValueError(f"{name} must be a positive integer.")
|
|
17
|
+
return value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def normalize_elements(elements: Elements, *, strict: bool = True) -> Elements:
|
|
21
|
+
validate_elements(elements, strict=strict)
|
|
22
|
+
elements = deepcopy(elements)
|
|
23
|
+
for group in ("nodes", "edges"):
|
|
24
|
+
elements.setdefault(group, [])
|
|
25
|
+
for record in elements[group]:
|
|
26
|
+
data = record["data"]
|
|
27
|
+
fields = (
|
|
28
|
+
("id", "source", "target") if group == "edges" else ("id", "parent")
|
|
29
|
+
)
|
|
30
|
+
for name in fields:
|
|
31
|
+
if name == "parent" and data.get(name) in (None, ""):
|
|
32
|
+
data.pop(name, None)
|
|
33
|
+
elif name in data:
|
|
34
|
+
data[name] = normalize_id(data[name], option_name=name)
|
|
35
|
+
try:
|
|
36
|
+
json.dumps(elements, allow_nan=False)
|
|
37
|
+
except (TypeError, ValueError) as error:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"Expansion records must contain JSON-compatible finite values."
|
|
40
|
+
) from error
|
|
41
|
+
for node in elements["nodes"]:
|
|
42
|
+
if "position" not in node:
|
|
43
|
+
continue
|
|
44
|
+
position = node["position"]
|
|
45
|
+
if not isinstance(position, dict) or any(
|
|
46
|
+
isinstance(position.get(axis), bool)
|
|
47
|
+
or not isinstance(position.get(axis), (int, float))
|
|
48
|
+
or not math.isfinite(float(position[axis]))
|
|
49
|
+
for axis in ("x", "y")
|
|
50
|
+
):
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Node {node['data']['id']} position requires finite numeric x and y."
|
|
53
|
+
)
|
|
54
|
+
return elements
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_instant(value: Any) -> datetime:
|
|
58
|
+
instant = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
59
|
+
return instant.replace(tzinfo=timezone.utc) if instant.tzinfo is None else instant
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def normalize_query(value: dict[str, Any] | None) -> dict[str, Any]:
|
|
63
|
+
if value is not None and not isinstance(value, dict):
|
|
64
|
+
raise ValueError("Expansion filters must be a dictionary.")
|
|
65
|
+
result = deepcopy(value or {})
|
|
66
|
+
unknown = set(result) - {
|
|
67
|
+
"direction",
|
|
68
|
+
"relationships",
|
|
69
|
+
"attributes",
|
|
70
|
+
"time_field",
|
|
71
|
+
"time_from",
|
|
72
|
+
"time_to",
|
|
73
|
+
}
|
|
74
|
+
if unknown:
|
|
75
|
+
raise ValueError(f"Unknown expansion filters: {sorted(unknown)}")
|
|
76
|
+
result.setdefault("direction", "both")
|
|
77
|
+
if result["direction"] not in ("both", "incoming", "outgoing"):
|
|
78
|
+
raise ValueError("direction must be both, incoming or outgoing.")
|
|
79
|
+
relationships = result.get("relationships", [])
|
|
80
|
+
if not isinstance(relationships, list) or any(
|
|
81
|
+
not isinstance(x, str) for x in relationships
|
|
82
|
+
):
|
|
83
|
+
raise ValueError("relationships must be a list of strings.")
|
|
84
|
+
result["relationships"] = sorted(set(relationships))
|
|
85
|
+
result.setdefault("attributes", {})
|
|
86
|
+
result.setdefault("time_field", "observed_at")
|
|
87
|
+
if not isinstance(result["attributes"], dict):
|
|
88
|
+
raise ValueError("attributes must be a dictionary of exact-match properties.")
|
|
89
|
+
if not isinstance(result["time_field"], str) or not result["time_field"].strip():
|
|
90
|
+
raise ValueError("time_field must be a nonempty property name.")
|
|
91
|
+
bounds = {}
|
|
92
|
+
for name in ("time_from", "time_to"):
|
|
93
|
+
if result.get(name) in (None, ""):
|
|
94
|
+
continue
|
|
95
|
+
try:
|
|
96
|
+
if not isinstance(result[name], str):
|
|
97
|
+
raise ValueError
|
|
98
|
+
bounds[name] = parse_instant(result[name])
|
|
99
|
+
except (TypeError, ValueError) as error:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"{name} must be an ISO-8601 date or timestamp."
|
|
102
|
+
) from error
|
|
103
|
+
if len(bounds) == 2 and bounds["time_from"] > bounds["time_to"]:
|
|
104
|
+
raise ValueError("time_from must not be later than time_to.")
|
|
105
|
+
json.dumps(result, allow_nan=False)
|
|
106
|
+
return result
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from collections.abc import Iterable, Mapping
|
|
5
|
+
|
|
6
|
+
from st_graph_workbench.component.types import ElementIdInput
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize_id(value: object, *, option_name: str) -> str:
|
|
10
|
+
"""Normalize one supported scalar identifier to a string."""
|
|
11
|
+
if value is None or value == "":
|
|
12
|
+
raise TypeError(f"`{option_name}` values cannot be empty.")
|
|
13
|
+
if isinstance(value, str | bool | int):
|
|
14
|
+
return str(value)
|
|
15
|
+
if isinstance(value, float) and math.isfinite(value):
|
|
16
|
+
return str(value)
|
|
17
|
+
raise TypeError(
|
|
18
|
+
f"`{option_name}` values must be non-empty string, finite number, "
|
|
19
|
+
f"or boolean IDs, not `{type(value).__name__}`."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalize_id_list(values: ElementIdInput, *, option_name: str) -> list[str]:
|
|
24
|
+
"""Normalize one scalar ID or an iterable of IDs to string IDs."""
|
|
25
|
+
if values is None:
|
|
26
|
+
return []
|
|
27
|
+
if isinstance(values, str | bool | int | float):
|
|
28
|
+
return [normalize_id(values, option_name=option_name)]
|
|
29
|
+
if isinstance(values, Mapping):
|
|
30
|
+
raise TypeError(
|
|
31
|
+
f"`{option_name}` must be one scalar ID or an iterable of ID values, "
|
|
32
|
+
"not a dict."
|
|
33
|
+
)
|
|
34
|
+
if not isinstance(values, Iterable):
|
|
35
|
+
raise TypeError(
|
|
36
|
+
f"`{option_name}` must be one scalar ID or an iterable of ID values, "
|
|
37
|
+
f"not `{type(values).__name__}`."
|
|
38
|
+
)
|
|
39
|
+
return [normalize_id(value, option_name=option_name) for value in values]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def normalize_id_set(values: ElementIdInput, *, option_name: str) -> set[str]:
|
|
43
|
+
"""Normalize one scalar ID or an iterable of IDs to a set of string IDs."""
|
|
44
|
+
return set(normalize_id_list(values, option_name=option_name))
|