PyDecisionGraph 0.3.2.post1__cp315-cp315-win_amd64.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 (148) hide show
  1. decision_graph/__infra__.pxd +1 -0
  2. decision_graph/__init__.pxd +1 -0
  3. decision_graph/__init__.py +39 -0
  4. decision_graph/decision_tree/__init__.pxd +2 -0
  5. decision_graph/decision_tree/__init__.py +85 -0
  6. decision_graph/decision_tree/bake/__infra__.pxd +502 -0
  7. decision_graph/decision_tree/bake/__init__.pxd +502 -0
  8. decision_graph/decision_tree/bake/__init__.py +29 -0
  9. decision_graph/decision_tree/bake/c_action.cp315-win_amd64.pyd +0 -0
  10. decision_graph/decision_tree/bake/c_action.h +324 -0
  11. decision_graph/decision_tree/bake/c_action.pxd +54 -0
  12. decision_graph/decision_tree/bake/c_action.pyi +126 -0
  13. decision_graph/decision_tree/bake/c_action.pyx +65 -0
  14. decision_graph/decision_tree/bake/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  15. decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
  16. decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
  17. decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
  18. decision_graph/decision_tree/bake/c_bake.cp315-win_amd64.pyd +0 -0
  19. decision_graph/decision_tree/bake/c_bake.h +629 -0
  20. decision_graph/decision_tree/bake/c_bake.pxd +52 -0
  21. decision_graph/decision_tree/bake/c_bake.pyi +151 -0
  22. decision_graph/decision_tree/bake/c_bake.pyx +142 -0
  23. decision_graph/decision_tree/bake/c_collections.cp315-win_amd64.pyd +0 -0
  24. decision_graph/decision_tree/bake/c_collections.h +692 -0
  25. decision_graph/decision_tree/bake/c_collections.pxd +52 -0
  26. decision_graph/decision_tree/bake/c_collections.pyi +193 -0
  27. decision_graph/decision_tree/bake/c_collections.pyx +161 -0
  28. decision_graph/decision_tree/bake/c_const.cp315-win_amd64.pyd +0 -0
  29. decision_graph/decision_tree/bake/c_const.h +395 -0
  30. decision_graph/decision_tree/bake/c_const.pxd +56 -0
  31. decision_graph/decision_tree/bake/c_const.pyi +305 -0
  32. decision_graph/decision_tree/bake/c_const.pyx +278 -0
  33. decision_graph/decision_tree/bake/c_edge.cp315-win_amd64.pyd +0 -0
  34. decision_graph/decision_tree/bake/c_edge.h +511 -0
  35. decision_graph/decision_tree/bake/c_edge.pxd +117 -0
  36. decision_graph/decision_tree/bake/c_edge.pyi +192 -0
  37. decision_graph/decision_tree/bake/c_edge.pyx +270 -0
  38. decision_graph/decision_tree/bake/c_eval.h +848 -0
  39. decision_graph/decision_tree/bake/c_eval.pxd +40 -0
  40. decision_graph/decision_tree/bake/c_expr.cp315-win_amd64.pyd +0 -0
  41. decision_graph/decision_tree/bake/c_expr.h +1900 -0
  42. decision_graph/decision_tree/bake/c_expr.pxd +129 -0
  43. decision_graph/decision_tree/bake/c_expr.pyi +280 -0
  44. decision_graph/decision_tree/bake/c_expr.pyx +272 -0
  45. decision_graph/decision_tree/bake/c_hierarchy.cp315-win_amd64.pyd +0 -0
  46. decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  47. decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
  48. decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
  49. decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
  50. decision_graph/decision_tree/bake/c_logic_group.cp315-win_amd64.pyd +0 -0
  51. decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  52. decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
  53. decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
  54. decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
  55. decision_graph/decision_tree/bake/c_node.cp315-win_amd64.pyd +0 -0
  56. decision_graph/decision_tree/bake/c_node.h +2660 -0
  57. decision_graph/decision_tree/bake/c_node.pxd +381 -0
  58. decision_graph/decision_tree/bake/c_node.pyi +438 -0
  59. decision_graph/decision_tree/bake/c_node.pyx +639 -0
  60. decision_graph/decision_tree/bake/c_reconstruct.cp315-win_amd64.pyd +0 -0
  61. decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
  62. decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
  63. decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
  64. decision_graph/decision_tree/bake/c_var.cp315-win_amd64.pyd +0 -0
  65. decision_graph/decision_tree/bake/c_var.h +2054 -0
  66. decision_graph/decision_tree/bake/c_var.pxd +205 -0
  67. decision_graph/decision_tree/bake/c_var.pyi +271 -0
  68. decision_graph/decision_tree/bake/c_var.pyx +228 -0
  69. decision_graph/decision_tree/capi/__infra__.pxd +33 -0
  70. decision_graph/decision_tree/capi/__init__.pxd +33 -0
  71. decision_graph/decision_tree/capi/__init__.py +57 -0
  72. decision_graph/decision_tree/capi/c_abc.cp315-win_amd64.pyd +0 -0
  73. decision_graph/decision_tree/capi/c_abc.pxd +246 -0
  74. decision_graph/decision_tree/capi/c_abc.pyi +770 -0
  75. decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
  76. decision_graph/decision_tree/capi/c_collection.cp315-win_amd64.pyd +0 -0
  77. decision_graph/decision_tree/capi/c_collection.pxd +22 -0
  78. decision_graph/decision_tree/capi/c_collection.pyi +245 -0
  79. decision_graph/decision_tree/capi/c_collection.pyx +138 -0
  80. decision_graph/decision_tree/capi/c_node.cp315-win_amd64.pyd +0 -0
  81. decision_graph/decision_tree/capi/c_node.pxd +95 -0
  82. decision_graph/decision_tree/capi/c_node.pyi +492 -0
  83. decision_graph/decision_tree/capi/c_node.pyx +551 -0
  84. decision_graph/decision_tree/exc.py +189 -0
  85. decision_graph/decision_tree/native/__init__.py +57 -0
  86. decision_graph/decision_tree/native/abc.py +1167 -0
  87. decision_graph/decision_tree/native/collection.py +134 -0
  88. decision_graph/decision_tree/native/node.py +521 -0
  89. decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
  90. decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
  91. decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
  92. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
  93. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
  94. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
  95. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
  96. decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
  97. decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
  98. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
  99. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
  100. decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
  101. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
  102. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
  103. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
  104. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  105. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
  106. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  107. decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
  108. decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
  109. decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
  110. decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
  111. decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
  112. decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
  113. decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
  114. decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
  115. decision_graph/logic_group/__init__.py +25 -0
  116. decision_graph/logic_group/base.py +100 -0
  117. decision_graph/logic_group/pending_request.py +384 -0
  118. decision_graph/webui/__init__.py +24 -0
  119. decision_graph/webui/bake/__init__.py +65 -0
  120. decision_graph/webui/bake/app.py +488 -0
  121. decision_graph/webui/bake/static/d3.v7.min.js +2 -0
  122. decision_graph/webui/bake/static/script.js +1440 -0
  123. decision_graph/webui/bake/static/style.css +920 -0
  124. decision_graph/webui/bake/templates/index.html +183 -0
  125. decision_graph/webui/bake/templates/offline.html +184 -0
  126. decision_graph/webui/capi/__init__.py +54 -0
  127. decision_graph/webui/capi/app.py +366 -0
  128. decision_graph/webui/capi/main.py +53 -0
  129. decision_graph/webui/capi/static/d3.v7.min.js +2 -0
  130. decision_graph/webui/capi/static/script.js +1026 -0
  131. decision_graph/webui/capi/static/style.css +656 -0
  132. decision_graph/webui/capi/templates/index.html +93 -0
  133. decision_graph/webui/capi/templates/offline.html +94 -0
  134. decision_graph/webui/native/__init__.py +54 -0
  135. decision_graph/webui/native/app.py +366 -0
  136. decision_graph/webui/native/main.py +53 -0
  137. decision_graph/webui/native/static/d3.v7.min.js +2 -0
  138. decision_graph/webui/native/static/script.js +1026 -0
  139. decision_graph/webui/native/static/style.css +656 -0
  140. decision_graph/webui/native/templates/index.html +93 -0
  141. decision_graph/webui/native/templates/offline.html +94 -0
  142. pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
  143. pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
  144. pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
  145. pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
  146. pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
  147. tests/bake_c_evaluator.cp315-win_amd64.pyd +0 -0
  148. tests/bake_eval_probe.cp315-win_amd64.pyd +0 -0
@@ -0,0 +1,25 @@
1
+ import logging
2
+
3
+ from .. import LOGGER
4
+
5
+ LOGGER = LOGGER.getChild("LogicGroup")
6
+
7
+
8
+ def set_logger(logger: logging.Logger):
9
+ global LOGGER
10
+ LOGGER = logger
11
+
12
+ base.LOGGER = logger.getChild('base')
13
+ pending_request.LOGGER = logger.getChild('delayed')
14
+
15
+
16
+ from .base import *
17
+ from .pending_request import *
18
+
19
+ __all__ = [
20
+ 'SignalLogicGroup', 'InstantConfirmationLogicGroup',
21
+
22
+ 'RequestType', 'RequestStatus', 'PendingRequest',
23
+ 'RequestRegistered', 'RequestDenied', 'RequestConfirmed',
24
+ 'DelayedConfirmationLogicGroup'
25
+ ]
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Any, overload, final
4
+
5
+ from . import LOGGER
6
+ from ..decision_tree import USING_CAPI, AttrExpression, LogicGroup, ActionNode, LGM, LongAction, ShortAction, NoAction
7
+
8
+ LOGGER = LOGGER.getChild('base')
9
+
10
+ __all__ = ['SignalLogicGroup', 'InstantConfirmationLogicGroup']
11
+
12
+
13
+ class SignalLogicGroup(LogicGroup):
14
+ def __new__(
15
+ cls,
16
+ *,
17
+ name: str = None,
18
+ parent: LogicGroup = None,
19
+ contexts: dict[str, Any] = None,
20
+ **kwargs
21
+ ):
22
+ instance = super().__new__(
23
+ cls,
24
+ name=name,
25
+ parent=parent,
26
+ contexts=contexts
27
+ )
28
+ instance.signal = 0
29
+ return instance
30
+
31
+ def get(self, attr: str, dtype: type = None, repr: str = None):
32
+ return AttrExpression(attr=attr, logic_group=self, dtype=dtype, repr=repr)
33
+
34
+ def reset(self):
35
+ self.signal = 0
36
+
37
+ @property
38
+ def signal(self):
39
+ return self.contexts.setdefault('signal', 0)
40
+
41
+ @signal.setter
42
+ def signal(self, value: int):
43
+ self.contexts['signal'] = value
44
+
45
+
46
+ @final
47
+ class InstantConfirmationLogicGroup(SignalLogicGroup):
48
+ def __new__(
49
+ cls,
50
+ *,
51
+ name: str = None,
52
+ parent: LogicGroup = None,
53
+ contexts: dict[str, Any] = None,
54
+ **kwargs
55
+ ):
56
+ if not parent:
57
+ parent = LGM.active_group
58
+
59
+ if not isinstance(parent, SignalLogicGroup):
60
+ raise TypeError('InstantConfirmationLogicGroup requires a SignalLogicGroup as parent!')
61
+
62
+ instance = super().__new__(
63
+ cls,
64
+ name=f'{parent.name}.Instant' if not name else name,
65
+ parent=parent,
66
+ contexts=contexts
67
+ )
68
+ return instance
69
+
70
+ def reset(self):
71
+ pass
72
+
73
+ @overload
74
+ def confirm(self, sig: Literal[1]) -> LongAction:
75
+ ...
76
+
77
+ @overload
78
+ def confirm(self, sig: Literal[-1]) -> ShortAction:
79
+ ...
80
+
81
+ def confirm(self, sig: Literal[-1, 1]) -> ActionNode:
82
+ self.signal = sig
83
+
84
+ if sig > 0:
85
+ return LongAction(sig=sig)
86
+ elif sig < 0:
87
+ return ShortAction(sig=sig)
88
+
89
+ if not LGM.inspection_mode:
90
+ LOGGER.warning(f'{self} received a confirmation of {sig=}! Which is not expected.')
91
+
92
+ return NoAction()
93
+
94
+ @property
95
+ def signal(self):
96
+ return self.parent.signal
97
+
98
+ @signal.setter
99
+ def signal(self, value: int):
100
+ self.parent.signal = value
@@ -0,0 +1,384 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import threading
5
+ import time
6
+ import uuid
7
+ from math import inf
8
+ from typing import Literal, Any
9
+
10
+ from algo_engine.exchange_profile import PROFILE
11
+
12
+ from . import LOGGER
13
+ from .base import SignalLogicGroup
14
+ from ..decision_tree import ActionNode, LogicGroup, NodeValueError, LongAction, ShortAction, LGM, NoAction
15
+
16
+ LOGGER = LOGGER.getChild('Request')
17
+
18
+ __all__ = [
19
+ 'RequestType', 'RequestStatus', 'PendingRequest',
20
+ 'RequestRegistered', 'RequestDenied', 'RequestConfirmed',
21
+ 'DelayedConfirmationLogicGroup'
22
+ ]
23
+
24
+
25
+ class RequestType(enum.StrEnum):
26
+ generic = enum.auto()
27
+ open = enum.auto()
28
+ unwind = enum.auto()
29
+
30
+
31
+ class RequestStatus(enum.StrEnum):
32
+ active = enum.auto()
33
+ idle = enum.auto()
34
+ locked = enum.auto()
35
+
36
+
37
+ class PendingRequest(object):
38
+ def __init__(
39
+ self,
40
+ name: str = None,
41
+ uid: uuid.UUID = None,
42
+ **kwargs
43
+ ):
44
+ self.uid = uid or uuid.uuid4()
45
+ self.name = name or f'PendingRequest.{self.uid}'
46
+ self.logic_group = kwargs.get('logic_group', LGM.active_group)
47
+
48
+ self.state = {
49
+ 'sig': kwargs.get('sig', 0),
50
+ 'rtype': kwargs.get('rtype', RequestType.generic),
51
+ 'rstatus': kwargs.get('rstatus', RequestStatus.idle),
52
+ 'activated_ts': kwargs.get('activated_ts', 0),
53
+ 'activated_by': kwargs.get('activated_by'),
54
+ 'deactivated_by': kwargs.get('deactivated_by'),
55
+ 'confirmed_by': kwargs.get('confirmed_by'),
56
+ 'timeout': kwargs.get('timeout', inf),
57
+ }
58
+
59
+ self.register_node = {}
60
+ self.confirmation_node = {}
61
+ self.denial_node = {}
62
+ self.lock = threading.Lock()
63
+
64
+ def bind(self, node_id: uuid.UUID, logic_node: RequestRegistered | RequestDenied | RequestConfirmed):
65
+ if isinstance(logic_node, RequestRegistered):
66
+ self.register_node[node_id] = logic_node
67
+ elif isinstance(logic_node, RequestDenied):
68
+ self.denial_node[node_id] = logic_node
69
+ elif isinstance(logic_node, RequestConfirmed):
70
+ self.confirmation_node[node_id] = logic_node
71
+ else:
72
+ raise TypeError('PendingRequest can only be bound to RequestRegistered, RequestConfirmed, or RequestDenied nodes!')
73
+ return logic_node
74
+
75
+ def activate(self, node_id: uuid.UUID):
76
+ if self.rstatus == RequestStatus.active:
77
+ return
78
+
79
+ if node_id not in self.register_node:
80
+ raise NodeValueError('The provided node_id is not bound with this PendingRequest!')
81
+
82
+ reg_node = self.register_node[node_id]
83
+
84
+ if not reg_node.sig:
85
+ raise NodeValueError('Signal Must not be zero.')
86
+
87
+ if not self.lock.acquire(blocking=False):
88
+ raise NodeValueError('Cannot activate a locked PendingRequest!')
89
+
90
+ self.state['sig'] = reg_node.sig
91
+ self.state['rtype'] = reg_node.rtype
92
+ self.state['rstatus'] = RequestStatus.active
93
+ self.state['activated_ts'] = self.logic_group.contexts.get('timestamp', time.time())
94
+ self.state['activated_by'] = reg_node
95
+ self.state['deactivated_by'] = None
96
+ self.state['confirmed_by'] = None
97
+ self.state['timeout'] = reg_node.timeout
98
+
99
+ self.lock.release()
100
+
101
+ def deactivate(self, node_id: uuid.UUID):
102
+ if self.rstatus == RequestStatus.idle:
103
+ return
104
+
105
+ if node_id not in self.denial_node:
106
+ raise NodeValueError('The provided node_id is not bound with this PendingRequest!')
107
+
108
+ node = self.denial_node[node_id]
109
+
110
+ if not self.lock.acquire(blocking=False):
111
+ raise NodeValueError('Cannot deactivate a locked PendingRequest!')
112
+
113
+ self.state['sig'] = 0
114
+ self.state['rtype'] = RequestType.generic
115
+ self.state['rstatus'] = RequestStatus.idle
116
+ self.state['activated_ts'] = 0
117
+ self.state['activated_by'] = None
118
+ self.state['deactivated_by'] = node
119
+ self.state['confirmed_by'] = None
120
+ self.state['timeout'] = 0
121
+
122
+ self.lock.release()
123
+
124
+ def confirm(self, node_id: uuid.UUID) -> ShortAction | NoAction | LongAction:
125
+ if self.rstatus != RequestStatus.active:
126
+ raise NodeValueError('Cannot confirm a inactive PendingRequest!')
127
+
128
+ if node_id not in self.confirmation_node:
129
+ raise NodeValueError('The provided node_id is not bound with this PendingRequest!')
130
+
131
+ node = self.confirmation_node[node_id]
132
+
133
+ if not self.lock.acquire(blocking=False):
134
+ raise NodeValueError('Cannot confirm a locked PendingRequest!')
135
+
136
+ # Step 1: Lock the request
137
+ self.state['rstatus'] = RequestStatus.locked
138
+ prelaunch_check = True
139
+
140
+ # Step 2: Check timeout
141
+ current_ts = self.logic_group.contexts.get('timestamp', time.time())
142
+ activated_ts = self.state['activated_ts']
143
+ timeout = self.state['timeout']
144
+ elapsed_time = PROFILE.trading_time_between(activated_ts, current_ts)
145
+ if elapsed_time >= timeout:
146
+ prelaunch_check = False
147
+
148
+ # Step 3: Check signal match
149
+ if prelaunch_check and node.sig != self.sig:
150
+ prelaunch_check = False
151
+
152
+ # Step 4: Finalize confirmation or denial
153
+ if prelaunch_check:
154
+ if node.sig > 0:
155
+ action = LongAction(sig=node.sig, auto_connect=False)
156
+ elif node.sig < 0:
157
+ action = ShortAction(sig=node.sig, auto_connect=False)
158
+ else:
159
+ action = NoAction(auto_connect=False)
160
+ else:
161
+ action = NoAction(auto_connect=False)
162
+
163
+ # Step 5: Reset the request state
164
+ self.state['sig'] = 0
165
+ self.state['rtype'] = RequestType.generic
166
+ self.state['rstatus'] = RequestStatus.idle
167
+ self.state['activated_ts'] = 0
168
+ self.state['activated_by'] = None
169
+ self.state['deactivated_by'] = None
170
+ self.state['confirmed_by'] = node
171
+ self.state['timeout'] = 0
172
+
173
+ # Step 6: Release the lock and return the action
174
+ self.lock.release()
175
+ return action
176
+
177
+ def __bool__(self):
178
+ if not self.sig:
179
+ return False
180
+ return True
181
+
182
+ @property
183
+ def sig(self) -> int:
184
+ return self.state['sig']
185
+
186
+ @property
187
+ def rtype(self) -> RequestType:
188
+ return self.state['rtype']
189
+
190
+ @property
191
+ def rstatus(self) -> RequestStatus:
192
+ return self.state['rstatus']
193
+
194
+ @property
195
+ def activated_ts(self) -> float:
196
+ return self.state['activated_ts']
197
+
198
+ @property
199
+ def activated_by(self) -> RequestRegistered:
200
+ return self.state['activated_by']
201
+
202
+ @property
203
+ def deactivated_by(self) -> RequestDenied:
204
+ return self.state['deactivated_by']
205
+
206
+ @property
207
+ def confirmed_by(self) -> RequestConfirmed:
208
+ return self.state['confirmed_by']
209
+
210
+ @property
211
+ def timeout(self) -> float:
212
+ return self.state['timeout']
213
+
214
+
215
+ class RequestRegistered(ActionNode):
216
+ def __new__(
217
+ cls,
218
+ *,
219
+ sig: Literal[-1, 1],
220
+ req: PendingRequest,
221
+ rtype: RequestType = RequestType.generic,
222
+ timeout: float = float('inf'),
223
+ action=None,
224
+ expression=None,
225
+ dtype=None,
226
+ repr=None,
227
+ auto_connect: bool = True,
228
+ **kwargs
229
+ ):
230
+ uid = uuid.uuid4()
231
+
232
+ if action is None:
233
+ action = lambda: req.activate(uid)
234
+
235
+ if expression is None:
236
+ expression = NoAction(auto_connect=False)
237
+
238
+ instance = super().__new__(
239
+ cls,
240
+ action=action,
241
+ expression=expression,
242
+ dtype=dtype,
243
+ repr=repr or f'<Pending Request Registered {sig=}>',
244
+ auto_connect=auto_connect
245
+ )
246
+
247
+ instance.uid = uid
248
+ instance.sig = sig
249
+ instance.req = req
250
+ instance.rtype = rtype
251
+ instance.timeout = timeout
252
+
253
+ return req.bind(uid, instance)
254
+
255
+
256
+ class RequestDenied(ActionNode):
257
+ def __new__(
258
+ cls,
259
+ *,
260
+ req: PendingRequest,
261
+ action=None,
262
+ expression=None,
263
+ dtype=None,
264
+ repr=None,
265
+ auto_connect: bool = True,
266
+ **kwargs
267
+ ):
268
+ uid = uuid.uuid4()
269
+
270
+ if action is None:
271
+ action = lambda: req.deactivate(uid)
272
+
273
+ if expression is None:
274
+ expression = NoAction(auto_connect=False)
275
+
276
+ instance = super().__new__(
277
+ cls,
278
+ action=action,
279
+ expression=expression,
280
+ dtype=dtype,
281
+ repr=repr or '<Pending Request Denied>',
282
+ auto_connect=auto_connect
283
+ )
284
+
285
+ instance.uid = uid
286
+ instance.req = req
287
+ return req.bind(uid, instance)
288
+
289
+
290
+ class RequestConfirmed(ActionNode):
291
+ def __new__(
292
+ cls,
293
+ *,
294
+ sig: Literal[-1, 1],
295
+ req: PendingRequest,
296
+ action=None,
297
+ expression=None,
298
+ dtype=None,
299
+ repr=None,
300
+ auto_connect: bool = True,
301
+ **kwargs
302
+ ):
303
+ uid = uuid.uuid4()
304
+
305
+ if action is None:
306
+ action = lambda: req.confirm(uid)
307
+
308
+ if expression is None:
309
+ expression = lambda: req.confirm(uid)
310
+
311
+ instance = super().__new__(
312
+ cls,
313
+ action=action,
314
+ expression=expression,
315
+ dtype=dtype,
316
+ repr=repr or f'<Pending Request Confirmed {sig=}>',
317
+ auto_connect=auto_connect
318
+ )
319
+
320
+ instance.uid = uid
321
+ instance.sig = sig
322
+ instance.req = req
323
+ return req.bind(uid, instance)
324
+
325
+
326
+ class DelayedConfirmationLogicGroup(SignalLogicGroup):
327
+ def __new__(
328
+ cls,
329
+ *,
330
+ name: str = None,
331
+ parent: LogicGroup = None,
332
+ contexts: dict[str, Any] = None,
333
+ **kwargs
334
+ ):
335
+ if not parent:
336
+ parent = LGM.active_group
337
+
338
+ if not isinstance(parent, SignalLogicGroup):
339
+ raise TypeError('InstantConfirmationLogicGroup requires a SignalLogicGroup as parent!')
340
+
341
+ instance = super().__new__(
342
+ cls,
343
+ name=f'{parent.name}.Instant' if not name else name,
344
+ parent=parent,
345
+ contexts=contexts
346
+ )
347
+
348
+ instance.req = instance.contexts['pending_request'] = PendingRequest()
349
+ return instance
350
+
351
+ def register(self, sig: Literal[1, -1], timeout: float = float('inf'), rtype: RequestType = RequestType.generic) -> RequestRegistered:
352
+ action_register = RequestRegistered(
353
+ sig=sig,
354
+ req=self.req,
355
+ rtype=rtype,
356
+ timeout=timeout
357
+ )
358
+ return action_register
359
+
360
+ def deny(self) -> RequestDenied:
361
+ action_deny = RequestDenied(
362
+ req=self.req
363
+ )
364
+ return action_deny
365
+
366
+ def confirm(self, sig: Literal[1, -1]) -> RequestConfirmed:
367
+ action_confirm = RequestConfirmed(
368
+ sig=sig,
369
+ req=self.req
370
+ )
371
+ return action_confirm
372
+
373
+ def reset(self):
374
+ self.req.reset()
375
+ super().reset()
376
+
377
+ @property
378
+ def signal(self):
379
+ return self.parent.signal
380
+
381
+ @signal.setter
382
+ def signal(self, value: Literal[-1, 0, 1]):
383
+ assert isinstance(value, (int, float))
384
+ self.parent.signal = value
@@ -0,0 +1,24 @@
1
+ """The web UIs — one per API surface.
2
+
3
+ Each layer gets its own sub-package and reads that layer directly:
4
+
5
+ - ``.capi`` ``decision_graph.decision_tree.capi`` — the Cython C API
6
+ - ``.native`` ``decision_graph.decision_tree.native`` — the pure-Python model
7
+ - ``.bake`` ``decision_graph.decision_tree.bake`` — the bake layer
8
+
9
+ Nothing is imported here on purpose: a UI drags in Flask and a directory of
10
+ browser assets, and importing this package is not asking for either. Reach for
11
+ the layer you want, by name::
12
+
13
+ from decision_graph.webui.bake import to_html
14
+
15
+ The three share no code, also on purpose. A visualiser is worth reading only if
16
+ it shows what ITS layer actually built, and a helper shared between two layers
17
+ is the first place a real difference between them gets papered over.
18
+ """
19
+
20
+ import logging
21
+
22
+ # The layer's logger hangs off the root one, so a consumer that re-points
23
+ # `decision_graph.LOGGER` re-points every UI with it.
24
+ LOGGER = logging.getLogger("DecisionGraph").getChild("WebUI")
@@ -0,0 +1,65 @@
1
+ """The web UI of the bake layer (``decision_graph.decision_tree.bake``).
2
+
3
+ The graph it draws is a bake graph — the same node set the bake protocol
4
+ prepares for a hot walk — read through the bake surface alone; see ``.app`` for
5
+ the one place that surface is named.
6
+
7
+ A bake graph comes in two states and the page draws both:
8
+
9
+ - UNBAKED, the way a build leaves it: the breakpoints a split build stopped at
10
+ are still in the graph, and a branch that was reserved and never filled is
11
+ still a placeholder;
12
+ - BAKED, the way a walk sees it: the breakpoints are gone (what they carried
13
+ took their place and their arm), the stand-ins are consolidated, and the
14
+ graph is sealed and locked.
15
+ """
16
+
17
+ import logging
18
+
19
+ from .. import LOGGER
20
+ from decision_graph.decision_tree.bake.c_hierarchy import RootLogicNode
21
+ from decision_graph.decision_tree.bake.c_node import LogicNode
22
+
23
+ LOGGER = LOGGER.getChild('WebUI.Bake')
24
+
25
+ from . import app
26
+ from .app import BakeWebUi
27
+
28
+
29
+ def show(root: LogicNode, with_eval: bool = True, **kwargs):
30
+ _app = BakeWebUi(
31
+ host=kwargs.get('host', "127.0.0.1"),
32
+ port=kwargs.get('port', 5000),
33
+ debug=kwargs.get('debug', False)
34
+ )
35
+ _app.show(
36
+ node=root,
37
+ with_eval=with_eval
38
+ )
39
+
40
+
41
+ def watch(root: RootLogicNode, interval: float = .5, block: bool = False, **kwargs):
42
+ _app = BakeWebUi(
43
+ host=kwargs.get('host', "127.0.0.1"),
44
+ port=kwargs.get('port', 5000),
45
+ debug=kwargs.get('debug', False)
46
+ )
47
+ _app.watch(
48
+ node=root,
49
+ interval=interval,
50
+ block=block
51
+ )
52
+
53
+
54
+ def to_html(root: LogicNode, file_name: str, with_eval: bool = True):
55
+ BakeWebUi.to_html(
56
+ node=root,
57
+ file_name=file_name,
58
+ with_eval=with_eval
59
+ )
60
+
61
+
62
+ def set_logger(logger: logging.Logger):
63
+ global LOGGER
64
+ LOGGER = logger.getChild('WebUI.Bake')
65
+ app.LOGGER = LOGGER