semantic-state-machine-graphable 0.1.4__tar.gz
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.
- semantic_state_machine_graphable-0.1.4/LICENSE +21 -0
- semantic_state_machine_graphable-0.1.4/PKG-INFO +78 -0
- semantic_state_machine_graphable-0.1.4/README.md +56 -0
- semantic_state_machine_graphable-0.1.4/pyproject.toml +46 -0
- semantic_state_machine_graphable-0.1.4/src/semantic_state_machine_graphable/__init__.py +0 -0
- semantic_state_machine_graphable-0.1.4/src/semantic_state_machine_graphable/graph.py +134 -0
- semantic_state_machine_graphable-0.1.4/src/semantic_state_machine_graphable/py.typed +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Richard West
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: semantic-state-machine-graphable
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A library for visualizing semantic-state-machine structures and AuditContext execution paths as graphs using graphable.
|
|
5
|
+
Keywords: state-machine,graphable,visualization,audit,type-safety
|
|
6
|
+
Author: Richard West
|
|
7
|
+
Author-email: Richard West <dopplereffect.us@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
15
|
+
Requires-Dist: graphable>=0.6.1
|
|
16
|
+
Requires-Dist: semantic-state-machine>=0.1.0
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Project-URL: Homepage, https://github.com/TheTrueSCU/semantic-state-machine-graphable
|
|
19
|
+
Project-URL: Repository, https://github.com/TheTrueSCU/semantic-state-machine-graphable
|
|
20
|
+
Project-URL: Issues, https://github.com/TheTrueSCU/semantic-state-machine-graphable/issues
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# semantic-state-machine-graphable
|
|
24
|
+
|
|
25
|
+
A library for visualizing `semantic-state-machine` structures and `AuditContext` execution paths as graphs using `graphable`.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
- **StateMachineGraph**: Visualize the static structure of a `semantic-state-machine.StateMachine`.
|
|
29
|
+
- **AuditContextGraph**: Visualize the execution history of an `AuditContext` as a graph, with edges annotated by transition indices.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### For Development
|
|
34
|
+
The project is managed with `uv`. To install dependencies:
|
|
35
|
+
```bash
|
|
36
|
+
uv sync
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### From PyPI
|
|
40
|
+
```bash
|
|
41
|
+
pip install semantic-state-machine-graphable
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Testing
|
|
45
|
+
The project uses `pytest` for testing. Run the test suite with coverage reporting:
|
|
46
|
+
```bash
|
|
47
|
+
PYTHONPATH=src uv run pytest --cov=semantic_state_machine_graphable --cov-report=term-missing
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### State Machine Visualization
|
|
53
|
+
```python
|
|
54
|
+
from semantic_state_machine import StateMachine
|
|
55
|
+
from semantic_state_machine_graphable.graph import StateMachineGraph
|
|
56
|
+
|
|
57
|
+
sm = StateMachine(...)
|
|
58
|
+
sm.add_transition(...)
|
|
59
|
+
|
|
60
|
+
graph = StateMachineGraph(sm)
|
|
61
|
+
# Now visualize or export the graph
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Execution Path Visualization
|
|
65
|
+
```python
|
|
66
|
+
from semantic_state_machine import AuditedStateMachine, AuditContext
|
|
67
|
+
from semantic_state_machine_graphable.graph import AuditContextGraph
|
|
68
|
+
|
|
69
|
+
sm = AuditedStateMachine(...)
|
|
70
|
+
ctx = AuditContext(...)
|
|
71
|
+
# ... execute transitions ...
|
|
72
|
+
|
|
73
|
+
graph = AuditContextGraph(ctx, sm)
|
|
74
|
+
# Now visualize or export the execution path graph
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# semantic-state-machine-graphable
|
|
2
|
+
|
|
3
|
+
A library for visualizing `semantic-state-machine` structures and `AuditContext` execution paths as graphs using `graphable`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **StateMachineGraph**: Visualize the static structure of a `semantic-state-machine.StateMachine`.
|
|
7
|
+
- **AuditContextGraph**: Visualize the execution history of an `AuditContext` as a graph, with edges annotated by transition indices.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
### For Development
|
|
12
|
+
The project is managed with `uv`. To install dependencies:
|
|
13
|
+
```bash
|
|
14
|
+
uv sync
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### From PyPI
|
|
18
|
+
```bash
|
|
19
|
+
pip install semantic-state-machine-graphable
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Testing
|
|
23
|
+
The project uses `pytest` for testing. Run the test suite with coverage reporting:
|
|
24
|
+
```bash
|
|
25
|
+
PYTHONPATH=src uv run pytest --cov=semantic_state_machine_graphable --cov-report=term-missing
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### State Machine Visualization
|
|
31
|
+
```python
|
|
32
|
+
from semantic_state_machine import StateMachine
|
|
33
|
+
from semantic_state_machine_graphable.graph import StateMachineGraph
|
|
34
|
+
|
|
35
|
+
sm = StateMachine(...)
|
|
36
|
+
sm.add_transition(...)
|
|
37
|
+
|
|
38
|
+
graph = StateMachineGraph(sm)
|
|
39
|
+
# Now visualize or export the graph
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Execution Path Visualization
|
|
43
|
+
```python
|
|
44
|
+
from semantic_state_machine import AuditedStateMachine, AuditContext
|
|
45
|
+
from semantic_state_machine_graphable.graph import AuditContextGraph
|
|
46
|
+
|
|
47
|
+
sm = AuditedStateMachine(...)
|
|
48
|
+
ctx = AuditContext(...)
|
|
49
|
+
# ... execute transitions ...
|
|
50
|
+
|
|
51
|
+
graph = AuditContextGraph(ctx, sm)
|
|
52
|
+
# Now visualize or export the execution path graph
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "semantic-state-machine-graphable"
|
|
3
|
+
version = "0.1.4"
|
|
4
|
+
description = "A library for visualizing semantic-state-machine structures and AuditContext execution paths as graphs using graphable."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Richard West", email = "dopplereffect.us@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
keywords = ["state-machine", "graphable", "visualization", "audit", "type-safety"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
17
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
18
|
+
]
|
|
19
|
+
requires-python = ">=3.13"
|
|
20
|
+
dependencies = [
|
|
21
|
+
"graphable>=0.6.1",
|
|
22
|
+
"semantic-state-machine>=0.1.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/TheTrueSCU/semantic-state-machine-graphable"
|
|
27
|
+
Repository = "https://github.com/TheTrueSCU/semantic-state-machine-graphable"
|
|
28
|
+
Issues = "https://github.com/TheTrueSCU/semantic-state-machine-graphable/issues"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["uv-build>=0.11.0"]
|
|
32
|
+
build-backend = "uv_build"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=9.0.3",
|
|
37
|
+
"pytest-cov>=7.1.0",
|
|
38
|
+
"pytest-gitignore>=1.3",
|
|
39
|
+
"pytest-html-plus>=0.5.2",
|
|
40
|
+
"pytest-isort>=4.0.0",
|
|
41
|
+
"pytest-randomly>=4.0.1",
|
|
42
|
+
"pytest-timeout>=2.4.0",
|
|
43
|
+
"pytest-xdist>=3.8.0",
|
|
44
|
+
"ruff>=0.15.10",
|
|
45
|
+
"ty>=0.0.29",
|
|
46
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import TypeVar
|
|
3
|
+
from semantic_state_machine import StateMachine, AuditContext
|
|
4
|
+
from graphable import Graph, Graphable
|
|
5
|
+
|
|
6
|
+
S = TypeVar("S", bound=Enum)
|
|
7
|
+
E = TypeVar("E", bound=Enum)
|
|
8
|
+
C = TypeVar("C")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StateNode[S: Enum](Graphable[S]):
|
|
12
|
+
"""A node in the graph representing a state.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
state: The Enum value representing the state.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, state: S):
|
|
19
|
+
super().__init__(state)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StateMachineGraph[S: Enum, E: Enum, C](Graph[StateNode[S]]):
|
|
23
|
+
"""A graph representation of a StateMachine's structure.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
sm: The StateMachine instance to visualize.
|
|
27
|
+
|
|
28
|
+
Notes:
|
|
29
|
+
Architectural Intent: Provides a static view of all possible
|
|
30
|
+
transitions defined in the state machine.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, sm: StateMachine[S, E, C]):
|
|
34
|
+
super().__init__()
|
|
35
|
+
self._sm = sm
|
|
36
|
+
self._sync()
|
|
37
|
+
|
|
38
|
+
def _get_or_create_node(self, state: S) -> StateNode[S]:
|
|
39
|
+
"""Retrieves an existing node for a state or creates a new one."""
|
|
40
|
+
try:
|
|
41
|
+
return self[state]
|
|
42
|
+
except KeyError:
|
|
43
|
+
node = StateNode(state)
|
|
44
|
+
self.add_node(node)
|
|
45
|
+
return node
|
|
46
|
+
|
|
47
|
+
def _sync(self):
|
|
48
|
+
"""Builds the Graph from the StateMachine's transitions."""
|
|
49
|
+
for (from_state, event), (to_state, _) in self._sm._transitions.items():
|
|
50
|
+
u = self._get_or_create_node(from_state)
|
|
51
|
+
v = self._get_or_create_node(to_state)
|
|
52
|
+
|
|
53
|
+
if v in u.dependents:
|
|
54
|
+
attrs = u.edge_attributes(v)
|
|
55
|
+
events = attrs.get("events", [])
|
|
56
|
+
if event not in events:
|
|
57
|
+
events.append(event)
|
|
58
|
+
u.set_edge_attribute(v, "events", events)
|
|
59
|
+
u.set_edge_attribute(v, "label", ", ".join(e.name for e in events))
|
|
60
|
+
else:
|
|
61
|
+
u.add_dependent(v, events=[event], label=event.name)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AuditContextGraph[S: Enum, E: Enum, C](Graph[StateNode[S]]):
|
|
65
|
+
"""A graph representation of the execution path recorded in an AuditContext.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
ctx: The AuditContext instance to visualize.
|
|
69
|
+
sm: Optional StateMachine instance to resolve target states.
|
|
70
|
+
If not provided, target states are inferred from the next entry
|
|
71
|
+
in the audit log, meaning the final transition's target state
|
|
72
|
+
cannot be determined.
|
|
73
|
+
|
|
74
|
+
Notes:
|
|
75
|
+
Architectural Intent: Visualizes the actual sequence of states
|
|
76
|
+
visited. Edges are annotated with the sequence of 1-based indices
|
|
77
|
+
from the audit log.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self, ctx: AuditContext[S, E], sm: StateMachine[S, E, C] | None = None
|
|
82
|
+
):
|
|
83
|
+
super().__init__()
|
|
84
|
+
self._ctx = ctx
|
|
85
|
+
self._sm = sm
|
|
86
|
+
self._sync()
|
|
87
|
+
|
|
88
|
+
def _get_or_create_node(self, state: S) -> StateNode[S]:
|
|
89
|
+
"""Retrieves an existing node for a state or creates a new one."""
|
|
90
|
+
try:
|
|
91
|
+
return self[state]
|
|
92
|
+
except KeyError:
|
|
93
|
+
node = StateNode(state)
|
|
94
|
+
self.add_node(node)
|
|
95
|
+
return node
|
|
96
|
+
|
|
97
|
+
def _sync(self):
|
|
98
|
+
"""Builds the Graph from the AuditContext's history."""
|
|
99
|
+
audit_data = self._ctx._audit
|
|
100
|
+
for i in range(len(audit_data)):
|
|
101
|
+
from_state, event = audit_data[i]
|
|
102
|
+
|
|
103
|
+
to_state = None
|
|
104
|
+
if self._sm:
|
|
105
|
+
# Use StateMachine to find to_state
|
|
106
|
+
try:
|
|
107
|
+
to_state, _ = self._sm._next_transition(from_state, event)
|
|
108
|
+
except Exception:
|
|
109
|
+
# If SM doesn't have it, try fallback to inference
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
if to_state is None and i + 1 < len(audit_data):
|
|
113
|
+
# Fallback to inference from next state
|
|
114
|
+
to_state = audit_data[i + 1][0]
|
|
115
|
+
|
|
116
|
+
if to_state is None:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
u = self._get_or_create_node(from_state)
|
|
120
|
+
v = self._get_or_create_node(to_state)
|
|
121
|
+
|
|
122
|
+
# The user requested "index (plus one)" which matches 1-based indexing.
|
|
123
|
+
index = i + 1
|
|
124
|
+
|
|
125
|
+
if v in u.dependents:
|
|
126
|
+
attrs = u.edge_attributes(v)
|
|
127
|
+
indices = attrs.get("indices", [])
|
|
128
|
+
indices.append(index)
|
|
129
|
+
u.set_edge_attribute(v, "indices", indices)
|
|
130
|
+
u.set_edge_attribute(
|
|
131
|
+
v, "label", f"{event.name} ({', '.join(map(str, indices))})"
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
u.add_dependent(v, indices=[index], label=f"{event.name} ({index})")
|
|
File without changes
|