chartflow 0.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 (79) hide show
  1. chartflow/__init__.py +28 -0
  2. chartflow/_verify_importexport.py +169 -0
  3. chartflow/edit/__init__.py +119 -0
  4. chartflow/edit/code_editor.py +1007 -0
  5. chartflow/edit/code_folder.py +523 -0
  6. chartflow/edit/completer.py +2652 -0
  7. chartflow/edit/context_analyzer.py +521 -0
  8. chartflow/edit/demo.py +469 -0
  9. chartflow/edit/line_number_area.py +548 -0
  10. chartflow/edit/minimap.py +581 -0
  11. chartflow/edit/python_editor.py +509 -0
  12. chartflow/edit/syntax_highlighter.py +291 -0
  13. chartflow/edit/test_editor.py +190 -0
  14. chartflow/flow/__init__.py +31 -0
  15. chartflow/flow/_demo.py +98 -0
  16. chartflow/flow/chart.py +1101 -0
  17. chartflow/flow/editor.py +375 -0
  18. chartflow/flow/flow.py +6 -0
  19. chartflow/flow/qchart.py +1250 -0
  20. chartflow/flow/test/__init__.py +0 -0
  21. chartflow/flow/test/test_chart.py +537 -0
  22. chartflow/flow/test_decorator_sync.py +102 -0
  23. chartflow/fsm/__init__.py +84 -0
  24. chartflow/fsm/decorators.py +395 -0
  25. chartflow/fsm/demo.py +381 -0
  26. chartflow/fsm/demo_pyqt6.py +881 -0
  27. chartflow/fsm/demo_tree.py +46 -0
  28. chartflow/fsm/logic.py +447 -0
  29. chartflow/fsm/meta.py +569 -0
  30. chartflow/fsm/scheduler.py +427 -0
  31. chartflow/fsm/stage.py +2079 -0
  32. chartflow/fsm/stdio.py +66 -0
  33. chartflow/fsm/test/__init__.py +7 -0
  34. chartflow/fsm/test/test_decorators.py +210 -0
  35. chartflow/fsm/test/test_events.py +202 -0
  36. chartflow/fsm/test/test_integration.py +596 -0
  37. chartflow/fsm/test/test_logic.py +371 -0
  38. chartflow/fsm/test/test_meta.py +192 -0
  39. chartflow/fsm/test/test_runtime_param.py +336 -0
  40. chartflow/fsm/test/test_scheduler.py +280 -0
  41. chartflow/fsm/test/test_stage.py +314 -0
  42. chartflow/fsm/test_behavior_events.py +263 -0
  43. chartflow/fsm/test_nested_parallel.py +80 -0
  44. chartflow/fsm/test_stage_spec.py +448 -0
  45. chartflow/fsm/utils.py +34 -0
  46. chartflow/node/__init__.py +66 -0
  47. chartflow/node/_node_base.py +727 -0
  48. chartflow/node/_node_interaction.py +488 -0
  49. chartflow/node/_node_interface.py +426 -0
  50. chartflow/node/_node_markers.py +648 -0
  51. chartflow/node/_node_ports.py +536 -0
  52. chartflow/node/_port_interface.py +111 -0
  53. chartflow/node/arrow_item.py +287 -0
  54. chartflow/node/color_utils.py +113 -0
  55. chartflow/node/connection_item.py +939 -0
  56. chartflow/node/demo.py +258 -0
  57. chartflow/node/demo_arrow_drag.py +46 -0
  58. chartflow/node/demo_decorator.py +63 -0
  59. chartflow/node/demo_ports.py +69 -0
  60. chartflow/node/enums.py +35 -0
  61. chartflow/node/example.py +84 -0
  62. chartflow/node/layout_utils.py +307 -0
  63. chartflow/node/main_window.py +196 -0
  64. chartflow/node/menu_bar.py +240 -0
  65. chartflow/node/node_canvas.py +1730 -0
  66. chartflow/node/node_item.py +754 -0
  67. chartflow/node/popmenu.py +472 -0
  68. chartflow/node/port_item.py +591 -0
  69. chartflow/node/qnode_editor.py +73 -0
  70. chartflow/node/selection_rect.py +53 -0
  71. chartflow/node/style_panel.py +615 -0
  72. chartflow/node/styles.py +63 -0
  73. chartflow/node/test_qnode.py +2336 -0
  74. chartflow/showcase.py +528 -0
  75. chartflow/theme.py +508 -0
  76. chartflow-0.1.dist-info/METADATA +23 -0
  77. chartflow-0.1.dist-info/RECORD +79 -0
  78. chartflow-0.1.dist-info/WHEEL +5 -0
  79. chartflow-0.1.dist-info/top_level.txt +1 -0
chartflow/fsm/stdio.py ADDED
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+ """stdio compatibility module for qfsm.
3
+
4
+ This module provides a compatibility layer for the std.info/std.error/std.warn
5
+ functions used in the old version of qfsm.
6
+ """
7
+
8
+ # Color codes for terminal output
9
+ class Colors:
10
+ RESET = "\033[0m"
11
+ RED = "\033[31m"
12
+ GREEN = "\033[32m"
13
+ YELLOW = "\033[33m"
14
+ BLUE = "\033[34m"
15
+ MAGENTA = "\033[35m"
16
+ CYAN = "\033[36m"
17
+ WHITE = "\033[37m"
18
+ BRIGHT = "\033[1m"
19
+
20
+
21
+ def info(tag: str, message: str, extra: str = "") -> None:
22
+ """Print info message.
23
+
24
+ Args:
25
+ tag: Message tag/source (e.g., "LogicName.fbm")
26
+ message: Main message content
27
+ extra: Extra information to append
28
+ """
29
+ if extra:
30
+ print(f"[{tag}] {message} {extra}")
31
+ else:
32
+ print(f"[{tag}] {message}")
33
+
34
+
35
+ def error(tag: str, message: str, *args) -> None:
36
+ """Print error message.
37
+
38
+ Args:
39
+ tag: Message tag/source
40
+ message: Error message
41
+ *args: Additional arguments to print
42
+ """
43
+ msg = f"[{tag}] {message}"
44
+ if args:
45
+ msg += " " + " ".join(str(a) for a in args)
46
+ print(msg)
47
+
48
+
49
+ def warn(tag: str, message: str) -> None:
50
+ """Print warning message.
51
+
52
+ Args:
53
+ tag: Message tag/source
54
+ message: Warning message
55
+ """
56
+ print(f"[{tag}] {message}")
57
+
58
+
59
+ def debug(tag: str, message: str) -> None:
60
+ """Print debug message.
61
+
62
+ Args:
63
+ tag: Message tag/source
64
+ message: Debug message
65
+ """
66
+ print(f"[{tag}] {message}")
@@ -0,0 +1,7 @@
1
+ """
2
+ qfsm test package
3
+
4
+ Run tests with: python -m pytest qfsm/test/ -v
5
+ """
6
+
7
+ __all__ = []
@@ -0,0 +1,210 @@
1
+ """Tests for decorators"""
2
+
3
+ import pytest
4
+ from chartflow.fsm.decorators import (
5
+ recursive, listen, behavior,
6
+ sequence, selector, parallel
7
+ )
8
+ from chartflow.fsm.stage import Stage
9
+ from chartflow.fsm.meta import clear_prototypes
10
+
11
+
12
+ @pytest.fixture(autouse=True)
13
+ def reset_state():
14
+ """Reset state before each test"""
15
+ clear_prototypes()
16
+ Stage._Stage__id__ = {}
17
+ yield
18
+
19
+
20
+ class TestRecursiveDecorator:
21
+ """Test @recursive decorator"""
22
+
23
+ def test_recursive_marked(self):
24
+ """Test that @recursive marks method"""
25
+
26
+ class TestStage(Stage):
27
+ NAME = "TestStage"
28
+
29
+ @recursive
30
+ def my_state(self, inst):
31
+ return None
32
+
33
+ assert "my_state" in TestStage.__recursive__
34
+
35
+ def test_recursive_inheritance(self):
36
+ """Test that @recursive is inherited"""
37
+
38
+ class BaseStage(Stage):
39
+ NAME = "BaseStage"
40
+
41
+ @recursive
42
+ def base_state(self, inst):
43
+ return None
44
+
45
+ class DerivedStage(BaseStage):
46
+ NAME = "DerivedStage"
47
+
48
+ def derived_state(self, inst):
49
+ return None
50
+
51
+ assert "base_state" in DerivedStage.__recursive__
52
+
53
+
54
+ class TestListenDecorator:
55
+ """Test @listen decorator"""
56
+
57
+ def test_listen_marked(self):
58
+ """Test that @listen marks method"""
59
+
60
+ class TestStage(Stage):
61
+ NAME = "TestStage"
62
+
63
+ @listen("event1")
64
+ def handler1(self, data):
65
+ pass
66
+
67
+ @listen("event2", "source1", "source2")
68
+ def handler2(self, data):
69
+ pass
70
+
71
+ assert "event1" in TestStage.__listens__
72
+ assert "handler1" in [h[0] for h in TestStage.__listens__["event1"]]
73
+
74
+ assert "event2" in TestStage.__listens__
75
+ handlers = TestStage.__listens__["event2"]
76
+ assert any(h[0] == "handler2" and list(h[1]) == ["source1", "source2"] for h in handlers)
77
+
78
+ def test_listen_inheritance(self):
79
+ """Test that @listen is inherited"""
80
+
81
+ class BaseStage(Stage):
82
+ NAME = "BaseStage"
83
+
84
+ @listen("event1")
85
+ def handler1(self, data):
86
+ pass
87
+
88
+ class DerivedStage(BaseStage):
89
+ NAME = "DerivedStage"
90
+
91
+ @listen("event2")
92
+ def handler2(self, data):
93
+ pass
94
+
95
+ assert "event1" in DerivedStage.__listens__
96
+ assert "event2" in DerivedStage.__listens__
97
+
98
+
99
+ class TestBehaviorDecorator:
100
+ """Test @behavior decorator"""
101
+
102
+ def test_behavior_marked(self):
103
+ """Test that @behavior marks state transition"""
104
+
105
+ class TestStage(Stage):
106
+ NAME = "TestStage"
107
+
108
+ @behavior("next_state")
109
+ def current_state(self, inst):
110
+ return True
111
+
112
+ def next_state(self, inst):
113
+ return None
114
+
115
+ assert "current_state" in TestStage.__behaviors__
116
+ assert TestStage.__behaviors__["current_state"]["children"] == "next_state"
117
+ assert TestStage.__behaviors__["current_state"]["composite"] is None
118
+
119
+
120
+ class TestSequenceDecorator:
121
+ """Test @sequence decorator"""
122
+
123
+ def test_sequence_marked(self):
124
+ """Test that @sequence marks composite"""
125
+
126
+ class TestStage(Stage):
127
+ NAME = "TestStage"
128
+
129
+ @sequence("b", "c")
130
+ def a(self, inst):
131
+ return True
132
+
133
+ def b(self, inst):
134
+ return True
135
+
136
+ def c(self, inst):
137
+ return None
138
+
139
+ assert "a" in TestStage.__behaviors__
140
+ assert TestStage.__behaviors__["a"]["children"] == ["b", "c"]
141
+ assert TestStage.__behaviors__["a"]["composite"] == "sequence"
142
+
143
+
144
+ class TestSelectorDecorator:
145
+ """Test @selector decorator"""
146
+
147
+ def test_selector_marked(self):
148
+ """Test that @selector marks composite"""
149
+
150
+ class TestStage(Stage):
151
+ NAME = "TestStage"
152
+
153
+ @selector("b", "c")
154
+ def a(self, inst):
155
+ return False
156
+
157
+ def b(self, inst):
158
+ return True
159
+
160
+ def c(self, inst):
161
+ return None
162
+
163
+ assert "a" in TestStage.__behaviors__
164
+ assert TestStage.__behaviors__["a"]["children"] == ["b", "c"]
165
+ assert TestStage.__behaviors__["a"]["composite"] == "selector"
166
+
167
+
168
+ class TestParallelDecorator:
169
+ """Test @parallel decorator"""
170
+
171
+ def test_parallel_marked(self):
172
+ """Test that @parallel marks composite"""
173
+
174
+ class TestStage(Stage):
175
+ NAME = "TestStage"
176
+
177
+ @parallel("b", "c")
178
+ def a(self, inst):
179
+ return True
180
+
181
+ def b(self, inst):
182
+ return True
183
+
184
+ def c(self, inst):
185
+ return None
186
+
187
+ assert "a" in TestStage.__behaviors__
188
+ assert TestStage.__behaviors__["a"]["children"] == ["b", "c"]
189
+ assert TestStage.__behaviors__["a"]["composite"] == "parallel"
190
+
191
+
192
+ class TestDecoratorCombinations:
193
+ """Test combining decorators"""
194
+
195
+ def test_recursive_and_behavior(self):
196
+ """Test @recursive with @behavior"""
197
+
198
+ class TestStage(Stage):
199
+ NAME = "TestStage"
200
+
201
+ @recursive
202
+ @behavior("next")
203
+ def current(self, inst):
204
+ return True
205
+
206
+ def next(self, inst):
207
+ return None
208
+
209
+ assert "current" in TestStage.__recursive__
210
+ assert "current" in TestStage.__behaviors__
@@ -0,0 +1,202 @@
1
+ """Tests for EventSystem"""
2
+
3
+ import pytest
4
+ from chartflow.fsm.utils import EventSystem
5
+
6
+
7
+ class TestEventSystemBasic:
8
+ """Test basic EventSystem functionality"""
9
+
10
+ def test_listen_for_event(self):
11
+ """Test listenFor registers callback"""
12
+ es = EventSystem()
13
+
14
+ called = False
15
+
16
+ def handler(data):
17
+ nonlocal called
18
+ called = True
19
+
20
+ es.listenFor("test_event", handler)
21
+ es.pushEvent("test_event", {})
22
+
23
+ assert called
24
+
25
+ def test_multiple_handlers(self):
26
+ """Test multiple handlers for same event"""
27
+ es = EventSystem()
28
+
29
+ calls = []
30
+
31
+ def handler1(data):
32
+ calls.append(1)
33
+
34
+ def handler2(data):
35
+ calls.append(2)
36
+
37
+ es.listenFor("test_event", handler1)
38
+ es.listenFor("test_event", handler2)
39
+ es.pushEvent("test_event", {})
40
+
41
+ assert 1 in calls
42
+ assert 2 in calls
43
+
44
+ def test_cancel_listen(self):
45
+ """Test cancelListen removes callback"""
46
+ es = EventSystem()
47
+
48
+ called = False
49
+
50
+ def handler(data):
51
+ nonlocal called
52
+ called = True
53
+
54
+ es.listenFor("test_event", handler)
55
+ es.cancelListen("test_event", handler)
56
+ es.pushEvent("test_event", {})
57
+
58
+ assert not called
59
+
60
+ def test_cancel_all_listeners(self):
61
+ """Test cancelListen without handler removes all"""
62
+ es = EventSystem()
63
+
64
+ calls = []
65
+
66
+ def handler1(data):
67
+ calls.append(1)
68
+
69
+ def handler2(data):
70
+ calls.append(2)
71
+
72
+ es.listenFor("test_event", handler1)
73
+ es.listenFor("test_event", handler2)
74
+ es.cancelListen("test_event")
75
+ es.pushEvent("test_event", {})
76
+
77
+ assert len(calls) == 0
78
+
79
+
80
+ class TestEventSystemSourceFiltering:
81
+ """Test source filtering"""
82
+
83
+ def test_source_filter_matching(self):
84
+ """Test that events from matching sources are received"""
85
+ es = EventSystem()
86
+
87
+ calls = []
88
+
89
+ def handler(data):
90
+ calls.append(data)
91
+
92
+ es.listenFor("test_event", handler, "source1", "source2")
93
+ es.pushEvent("test_event", {"msg": "hello"}, source="source1")
94
+
95
+ assert len(calls) == 1
96
+ assert calls[0]["msg"] == "hello"
97
+
98
+ def test_source_filter_non_matching(self):
99
+ """Test that events from non-matching sources are filtered"""
100
+ es = EventSystem()
101
+
102
+ calls = []
103
+
104
+ def handler(data):
105
+ calls.append(data)
106
+
107
+ es.listenFor("test_event", handler, "source1")
108
+ es.pushEvent("test_event", {"msg": "hello"}, source="source2")
109
+
110
+ assert len(calls) == 0
111
+
112
+ def test_no_source_filter_receives_all(self):
113
+ """Test that listeners without source filter receive all"""
114
+ es = EventSystem()
115
+
116
+ calls = []
117
+
118
+ def handler(data):
119
+ calls.append(data)
120
+
121
+ es.listenFor("test_event", handler)
122
+ es.pushEvent("test_event", {"msg": "hello"}, source="any_source")
123
+
124
+ assert len(calls) == 1
125
+
126
+
127
+ class TestEventSystemTargetFiltering:
128
+ """Test target filtering"""
129
+
130
+ def test_target_filter_matching(self):
131
+ """Test that events with matching targets are received"""
132
+ es = EventSystem()
133
+
134
+ calls = []
135
+
136
+ def handler(data):
137
+ calls.append(data)
138
+
139
+ es.listenFor("test_event", handler)
140
+ # No targets specified means all listeners receive
141
+ es.pushEvent("test_event", {"msg": "hello"})
142
+
143
+ assert len(calls) == 1
144
+
145
+ def test_data_passed_to_handler(self):
146
+ """Test that data is passed to handler"""
147
+ es = EventSystem()
148
+
149
+ received_data = None
150
+
151
+ def handler(data):
152
+ nonlocal received_data
153
+ received_data = data
154
+
155
+ es.listenFor("test_event", handler)
156
+ es.pushEvent("test_event", {"key": "value", "num": 42})
157
+
158
+ assert received_data["key"] == "value"
159
+ assert received_data["num"] == 42
160
+
161
+
162
+ class TestEventSystemEdgeCases:
163
+ """Test edge cases"""
164
+
165
+ def test_no_listeners(self):
166
+ """Test that pushEvent with no listeners doesn't error"""
167
+ es = EventSystem()
168
+
169
+ es.pushEvent("test_event", {}) # Should not raise
170
+
171
+ def test_unknown_event(self):
172
+ """Test that pushEvent for unknown event doesn't error"""
173
+ es = EventSystem()
174
+
175
+ def handler(data):
176
+ pass
177
+
178
+ es.listenFor("other_event", handler)
179
+ es.pushEvent("test_event", {}) # Should not raise
180
+
181
+ def test_handler_exception(self):
182
+ """Test that handler exception doesn't break system"""
183
+ es = EventSystem()
184
+
185
+ calls = []
186
+
187
+ def bad_handler(data):
188
+ raise ValueError("Test error")
189
+
190
+ def good_handler(data):
191
+ calls.append(data)
192
+
193
+ es.listenFor("test_event", bad_handler)
194
+ es.listenFor("test_event", good_handler)
195
+
196
+ try:
197
+ es.pushEvent("test_event", {})
198
+ except:
199
+ pass # Exception might propagate
200
+
201
+ # Good handler should still be called
202
+ assert len(calls) == 1