mycorrhizal 0.1.2__py3-none-any.whl → 0.2.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.
- mycorrhizal/_version.py +1 -1
- mycorrhizal/common/__init__.py +15 -3
- mycorrhizal/common/cache.py +114 -0
- mycorrhizal/common/compilation.py +263 -0
- mycorrhizal/common/interface_detection.py +159 -0
- mycorrhizal/common/interfaces.py +3 -50
- mycorrhizal/common/mermaid.py +124 -0
- mycorrhizal/common/wrappers.py +1 -1
- mycorrhizal/hypha/core/builder.py +11 -1
- mycorrhizal/hypha/core/runtime.py +242 -107
- mycorrhizal/mycelium/__init__.py +174 -0
- mycorrhizal/mycelium/core.py +619 -0
- mycorrhizal/mycelium/exceptions.py +30 -0
- mycorrhizal/mycelium/hypha_bridge.py +1143 -0
- mycorrhizal/mycelium/instance.py +440 -0
- mycorrhizal/mycelium/pn_context.py +276 -0
- mycorrhizal/mycelium/runner.py +165 -0
- mycorrhizal/mycelium/spores_integration.py +655 -0
- mycorrhizal/mycelium/tree_builder.py +102 -0
- mycorrhizal/mycelium/tree_spec.py +197 -0
- mycorrhizal/rhizomorph/README.md +82 -33
- mycorrhizal/rhizomorph/core.py +287 -119
- mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
- mycorrhizal/{enoki → septum}/core.py +326 -100
- mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
- mycorrhizal/{enoki → septum}/util.py +44 -21
- mycorrhizal/spores/__init__.py +3 -3
- mycorrhizal/spores/core.py +149 -28
- mycorrhizal/spores/dsl/__init__.py +8 -8
- mycorrhizal/spores/dsl/hypha.py +3 -15
- mycorrhizal/spores/dsl/rhizomorph.py +3 -11
- mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
- mycorrhizal/spores/encoder/json.py +21 -12
- mycorrhizal/spores/extraction.py +14 -11
- mycorrhizal/spores/models.py +53 -20
- mycorrhizal-0.2.1.dist-info/METADATA +335 -0
- mycorrhizal-0.2.1.dist-info/RECORD +54 -0
- mycorrhizal-0.1.2.dist-info/METADATA +0 -198
- mycorrhizal-0.1.2.dist-info/RECORD +0 -39
- /mycorrhizal/{enoki → septum}/__init__.py +0 -0
- {mycorrhizal-0.1.2.dist-info → mycorrhizal-0.2.1.dist-info}/WHEEL +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Septum Testing Utilities
|
|
4
4
|
|
|
5
|
-
Testing infrastructure for the class-method-based
|
|
5
|
+
Testing infrastructure for the class-method-based Septum architecture.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import unittest
|
|
@@ -13,8 +13,8 @@ import gevent
|
|
|
13
13
|
from gevent import sleep
|
|
14
14
|
import traceback
|
|
15
15
|
|
|
16
|
-
# Import the
|
|
17
|
-
from mycorrhizal.
|
|
16
|
+
# Import the Septum modules
|
|
17
|
+
from mycorrhizal.septum import (
|
|
18
18
|
State,
|
|
19
19
|
StateMachine,
|
|
20
20
|
StateConfiguration,
|
|
@@ -151,8 +151,8 @@ def simulate_state_lifecycle(
|
|
|
151
151
|
return results
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
class
|
|
155
|
-
"""Base test case class with assertion methods for
|
|
154
|
+
class SeptumTestCase(unittest.TestCase):
|
|
155
|
+
"""Base test case class with assertion methods for Septum FSM testing."""
|
|
156
156
|
|
|
157
157
|
def assertStateTransition(
|
|
158
158
|
self, state_class, expected_transition, context=None, **context_kwargs
|
|
@@ -328,7 +328,7 @@ class EnokiTestCase(unittest.TestCase):
|
|
|
328
328
|
self.assertIsNotNone(timeout_result, "on_timeout should return a transition")
|
|
329
329
|
|
|
330
330
|
|
|
331
|
-
class StateMachineTestCase(
|
|
331
|
+
class StateMachineTestCase(SeptumTestCase):
|
|
332
332
|
"""Extended test case for testing full StateMachine behavior."""
|
|
333
333
|
|
|
334
334
|
def assertFSMTransition(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Septum State Machine Utilities
|
|
4
4
|
|
|
5
5
|
Utility functions for FSM operations like Mermaid diagram generation.
|
|
6
6
|
"""
|
|
@@ -16,6 +16,24 @@ class _TransitionInfo:
|
|
|
16
16
|
transition_type: str # "normal", "push", "pop", "again", "retry", etc.
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def _short_state_name(state_name: str) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Extract short state name from full qualified name.
|
|
22
|
+
|
|
23
|
+
Converts 'examples.septum.network_protocol_fsm.IdleState' to 'IdleState'.
|
|
24
|
+
Handles edge cases like '<string>.IdleState' gracefully.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
state_name: Full state name with module path
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Short state name (just the class name)
|
|
31
|
+
"""
|
|
32
|
+
if "." in state_name:
|
|
33
|
+
return state_name.rsplit(".", 1)[-1]
|
|
34
|
+
return state_name
|
|
35
|
+
|
|
36
|
+
|
|
19
37
|
def to_mermaid(fsm) -> str:
|
|
20
38
|
"""
|
|
21
39
|
Generate Mermaid diagram from a StateMachine instance.
|
|
@@ -24,15 +42,15 @@ def to_mermaid(fsm) -> str:
|
|
|
24
42
|
including special handling for Push/Pop transitions.
|
|
25
43
|
|
|
26
44
|
Args:
|
|
27
|
-
fsm: A StateMachine instance (from mycorrhizal.
|
|
45
|
+
fsm: A StateMachine instance (from mycorrhizal.septum.core)
|
|
28
46
|
|
|
29
47
|
Returns:
|
|
30
48
|
Mermaid diagram as a string
|
|
31
49
|
|
|
32
50
|
Example:
|
|
33
51
|
```python
|
|
34
|
-
from mycorrhizal.
|
|
35
|
-
from mycorrhizal.
|
|
52
|
+
from mycorrhizal.septum.core import StateMachine
|
|
53
|
+
from mycorrhizal.septum.util import to_mermaid
|
|
36
54
|
|
|
37
55
|
fsm = StateMachine(initial_state=MyState)
|
|
38
56
|
await fsm.initialize()
|
|
@@ -43,7 +61,7 @@ def to_mermaid(fsm) -> str:
|
|
|
43
61
|
```
|
|
44
62
|
"""
|
|
45
63
|
# Import here to avoid circular imports
|
|
46
|
-
from mycorrhizal.
|
|
64
|
+
from mycorrhizal.septum.core import (
|
|
47
65
|
StateSpec,
|
|
48
66
|
StateRef,
|
|
49
67
|
LabeledTransition,
|
|
@@ -77,12 +95,12 @@ def to_mermaid(fsm) -> str:
|
|
|
77
95
|
|
|
78
96
|
# Add initial state marker
|
|
79
97
|
initial_name = fsm.initial_state.name if isinstance(fsm.initial_state, StateSpec) else str(fsm.initial_state)
|
|
80
|
-
lines.append(f
|
|
98
|
+
lines.append(f' start((start)) --> {nid(initial_name)}')
|
|
81
99
|
|
|
82
100
|
# Add error state if exists
|
|
83
101
|
if fsm.error_state:
|
|
84
102
|
error_name = fsm.error_state.name if isinstance(fsm.error_state, StateSpec) else str(fsm.error_state)
|
|
85
|
-
lines.append(f
|
|
103
|
+
lines.append(f' {nid(error_name)}{{"ERROR"}}')
|
|
86
104
|
|
|
87
105
|
# Collect all transitions
|
|
88
106
|
transitions: Dict[str, List[_TransitionInfo]] = {}
|
|
@@ -135,8 +153,9 @@ def to_mermaid(fsm) -> str:
|
|
|
135
153
|
# Push transitions - show all states being pushed
|
|
136
154
|
for push_state in p.push_states:
|
|
137
155
|
push_name = push_state.name if isinstance(push_state, StateSpec) else str(push_state)
|
|
156
|
+
# Use -> instead of unicode arrow for better compatibility
|
|
138
157
|
transitions[state_name].append(
|
|
139
|
-
_TransitionInfo(f"{label.name}
|
|
158
|
+
_TransitionInfo(f"{label.name}->push", push_name, "push")
|
|
140
159
|
)
|
|
141
160
|
case StateSpec() as target_state:
|
|
142
161
|
transitions[state_name].append(
|
|
@@ -182,39 +201,43 @@ def to_mermaid(fsm) -> str:
|
|
|
182
201
|
continue
|
|
183
202
|
|
|
184
203
|
state_id = nid(state_name)
|
|
204
|
+
short_name = _short_state_name(state_name)
|
|
185
205
|
|
|
186
206
|
# Determine node shape based on state properties
|
|
207
|
+
# All nodes use quotes to handle special characters
|
|
187
208
|
if state.config.terminal:
|
|
188
|
-
|
|
209
|
+
# Terminal state: use double parentheses and note terminal status
|
|
210
|
+
lines.append(f' {state_id}[["{short_name}<br/>terminal"]]')
|
|
189
211
|
elif state_name == initial_name:
|
|
190
|
-
|
|
212
|
+
# Initial state: normal shape
|
|
213
|
+
lines.append(f' {state_id}["{short_name}"]')
|
|
191
214
|
elif fsm.error_state and state_name == (fsm.error_state.name if isinstance(fsm.error_state, StateSpec) else str(fsm.error_state)):
|
|
192
|
-
|
|
215
|
+
# Error state: use rhombus shape
|
|
216
|
+
lines.append(f' {state_id}{{"{short_name}"}}')
|
|
193
217
|
else:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
lines.append(f" {state_id}{shape}")
|
|
218
|
+
# Normal state
|
|
219
|
+
lines.append(f' {state_id}["{short_name}"]')
|
|
197
220
|
|
|
198
221
|
# Add transitions
|
|
199
222
|
for trans in transitions.get(state_name, []):
|
|
200
223
|
if trans.transition_type in ("again", "retry", "repeat", "restart", "unhandled"):
|
|
201
|
-
# Self-loop
|
|
202
|
-
|
|
203
|
-
lines.append(f" {state_id} -->|{label}| {state_id}")
|
|
224
|
+
# Self-loop with transition type
|
|
225
|
+
lines.append(f' {state_id} -->|"{trans.transition_type}"| {state_id}')
|
|
204
226
|
elif trans.transition_type == "pop":
|
|
205
227
|
# Pop goes to a special pop node
|
|
206
|
-
|
|
228
|
+
label = trans.label if trans.label else "pop"
|
|
229
|
+
lines.append(f' {state_id} -->|"{label}"| pop((pop))')
|
|
207
230
|
elif trans.transition_type == "push":
|
|
208
231
|
# Push transition
|
|
209
232
|
target_id = nid(trans.target)
|
|
210
|
-
lines.append(f
|
|
233
|
+
lines.append(f' {state_id} -->|"{trans.label}"| {target_id}')
|
|
211
234
|
else:
|
|
212
235
|
# Normal transition
|
|
213
236
|
target_id = nid(trans.target)
|
|
214
|
-
lines.append(f
|
|
237
|
+
lines.append(f' {state_id} -->|"{trans.label}"| {target_id}')
|
|
215
238
|
|
|
216
239
|
# Add pop node if any pop transitions exist
|
|
217
240
|
if any(t.transition_type == "pop" for transs in transitions.values() for t in transs):
|
|
218
|
-
lines.append(
|
|
241
|
+
lines.append(' pop((pop))')
|
|
219
242
|
|
|
220
243
|
return "\n".join(lines)
|
mycorrhizal/spores/__init__.py
CHANGED
|
@@ -61,7 +61,7 @@ Usage (DSL Adapters):
|
|
|
61
61
|
DSL Adapters:
|
|
62
62
|
HyphaAdapter - Log Petri net transitions with token relationships
|
|
63
63
|
RhizomorphAdapter - Log behavior tree node execution with status
|
|
64
|
-
|
|
64
|
+
SeptumAdapter - Log state machine execution and lifecycle events
|
|
65
65
|
|
|
66
66
|
Annotation Types:
|
|
67
67
|
EventAttr - Mark blackboard fields for automatic event attribute extraction (DSL adapters)
|
|
@@ -117,7 +117,7 @@ from .transport import SyncTransport, AsyncTransport, Transport, SyncFileTranspo
|
|
|
117
117
|
from .dsl import (
|
|
118
118
|
HyphaAdapter,
|
|
119
119
|
RhizomorphAdapter,
|
|
120
|
-
|
|
120
|
+
SeptumAdapter,
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
|
|
@@ -173,5 +173,5 @@ __all__ = [
|
|
|
173
173
|
# DSL Adapters
|
|
174
174
|
'HyphaAdapter',
|
|
175
175
|
'RhizomorphAdapter',
|
|
176
|
-
'
|
|
176
|
+
'SeptumAdapter',
|
|
177
177
|
]
|
mycorrhizal/spores/core.py
CHANGED
|
@@ -266,11 +266,7 @@ class AsyncEventLogger(EventLogger):
|
|
|
266
266
|
|
|
267
267
|
attr_values = {}
|
|
268
268
|
for key, value in kwargs.items():
|
|
269
|
-
attr_values[key] =
|
|
270
|
-
name=key,
|
|
271
|
-
value=str(value),
|
|
272
|
-
time=timestamp
|
|
273
|
-
)
|
|
269
|
+
attr_values[key] = attribute_value_from_python(value)
|
|
274
270
|
|
|
275
271
|
event = Event(
|
|
276
272
|
id=generate_event_id(),
|
|
@@ -293,11 +289,7 @@ class AsyncEventLogger(EventLogger):
|
|
|
293
289
|
|
|
294
290
|
attr_values = {}
|
|
295
291
|
for key, value in kwargs.items():
|
|
296
|
-
attr_values[key] =
|
|
297
|
-
name=key,
|
|
298
|
-
value=str(value),
|
|
299
|
-
time=timestamp
|
|
300
|
-
)
|
|
292
|
+
attr_values[key] = object_attribute_from_python(value, time=timestamp)
|
|
301
293
|
|
|
302
294
|
obj = Object(
|
|
303
295
|
id=obj_id,
|
|
@@ -346,7 +338,7 @@ class AsyncEventLogger(EventLogger):
|
|
|
346
338
|
source, obj_type, attrs = rel_spec
|
|
347
339
|
|
|
348
340
|
# Resolve object from source
|
|
349
|
-
obj = self._resolve_source(source, context)
|
|
341
|
+
obj = self._resolve_source(source, context, obj_type)
|
|
350
342
|
if obj is None:
|
|
351
343
|
continue
|
|
352
344
|
|
|
@@ -392,14 +384,84 @@ class AsyncEventLogger(EventLogger):
|
|
|
392
384
|
|
|
393
385
|
return context
|
|
394
386
|
|
|
395
|
-
def _resolve_source(self, source: str, context: Dict[str, Any]) -> Any:
|
|
396
|
-
"""Resolve source for async version.
|
|
387
|
+
def _resolve_source(self, source: str, context: Dict[str, Any], obj_type: str | None = None) -> Any:
|
|
388
|
+
"""Resolve source for async version.
|
|
389
|
+
|
|
390
|
+
If source is "bb" (blackboard) and obj_type is provided, extract the field
|
|
391
|
+
of that type from the blackboard instead of returning the entire blackboard.
|
|
392
|
+
"""
|
|
397
393
|
if source == "return" or source == "ret":
|
|
398
394
|
return context.get("return")
|
|
399
395
|
elif source == "self":
|
|
400
396
|
return context.get("self")
|
|
401
397
|
else:
|
|
402
|
-
|
|
398
|
+
obj = context.get(source)
|
|
399
|
+
|
|
400
|
+
# If source is blackboard and we need a specific type, extract that field
|
|
401
|
+
if obj_type and source == "bb" and hasattr(obj, '__annotations__'):
|
|
402
|
+
return self._find_object_by_type(obj, obj_type)
|
|
403
|
+
|
|
404
|
+
return obj
|
|
405
|
+
|
|
406
|
+
def _find_object_by_type(self, blackboard: Any, obj_type: str) -> Any:
|
|
407
|
+
"""Find a field in the blackboard that matches the requested object type.
|
|
408
|
+
|
|
409
|
+
Scans the blackboard's fields and returns the first field whose type
|
|
410
|
+
annotation matches obj_type.
|
|
411
|
+
"""
|
|
412
|
+
# Get the class to check annotations
|
|
413
|
+
obj_class = blackboard if isinstance(blackboard, type) else type(blackboard)
|
|
414
|
+
|
|
415
|
+
if not hasattr(obj_class, '__annotations__'):
|
|
416
|
+
return blackboard
|
|
417
|
+
|
|
418
|
+
# Check each field's type annotation
|
|
419
|
+
for field_name, field_type in obj_class.__annotations__.items():
|
|
420
|
+
# Handle Annotated types
|
|
421
|
+
if get_origin(field_type) is Annotated:
|
|
422
|
+
args = get_args(field_type)
|
|
423
|
+
if args:
|
|
424
|
+
actual_type = args[0]
|
|
425
|
+
# Check if type name matches (handle both str and type)
|
|
426
|
+
type_name = actual_type if isinstance(actual_type, str) else actual_type.__name__
|
|
427
|
+
if type_name == obj_type:
|
|
428
|
+
field_value = getattr(blackboard, field_name, None)
|
|
429
|
+
# Return the actual value, not None
|
|
430
|
+
if field_value is not None:
|
|
431
|
+
return field_value
|
|
432
|
+
# Handle Union types (e.g., Sample | None)
|
|
433
|
+
elif get_origin(field_type) is Union:
|
|
434
|
+
args = get_args(field_type)
|
|
435
|
+
for arg in args:
|
|
436
|
+
# Skip None
|
|
437
|
+
if arg is type(None):
|
|
438
|
+
continue
|
|
439
|
+
# Check if this arg matches our target type
|
|
440
|
+
if get_origin(arg) is Annotated:
|
|
441
|
+
annotated_args = get_args(arg)
|
|
442
|
+
if annotated_args:
|
|
443
|
+
actual_type = annotated_args[0]
|
|
444
|
+
type_name = actual_type if isinstance(actual_type, str) else actual_type.__name__
|
|
445
|
+
if type_name == obj_type:
|
|
446
|
+
field_value = getattr(blackboard, field_name, None)
|
|
447
|
+
if field_value is not None:
|
|
448
|
+
return field_value
|
|
449
|
+
else:
|
|
450
|
+
type_name = arg if isinstance(arg, str) else arg.__name__
|
|
451
|
+
if type_name == obj_type:
|
|
452
|
+
field_value = getattr(blackboard, field_name, None)
|
|
453
|
+
if field_value is not None:
|
|
454
|
+
return field_value
|
|
455
|
+
else:
|
|
456
|
+
# Check if type name matches
|
|
457
|
+
type_name = field_type if isinstance(field_type, str) else field_type.__name__
|
|
458
|
+
if type_name == obj_type:
|
|
459
|
+
field_value = getattr(blackboard, field_name, None)
|
|
460
|
+
if field_value is not None:
|
|
461
|
+
return field_value
|
|
462
|
+
|
|
463
|
+
# Fallback: return blackboard if no matching field found
|
|
464
|
+
return blackboard
|
|
403
465
|
|
|
404
466
|
def _get_object_id(self, obj: Any) -> str | None:
|
|
405
467
|
"""Extract object ID for async version."""
|
|
@@ -518,11 +580,7 @@ class SyncEventLogger(EventLogger):
|
|
|
518
580
|
|
|
519
581
|
attr_values = {}
|
|
520
582
|
for key, value in kwargs.items():
|
|
521
|
-
attr_values[key] =
|
|
522
|
-
name=key,
|
|
523
|
-
value=str(value),
|
|
524
|
-
time=timestamp
|
|
525
|
-
)
|
|
583
|
+
attr_values[key] = attribute_value_from_python(value)
|
|
526
584
|
|
|
527
585
|
event = Event(
|
|
528
586
|
id=generate_event_id(),
|
|
@@ -550,11 +608,7 @@ class SyncEventLogger(EventLogger):
|
|
|
550
608
|
|
|
551
609
|
attr_values = {}
|
|
552
610
|
for key, value in kwargs.items():
|
|
553
|
-
attr_values[key] =
|
|
554
|
-
name=key,
|
|
555
|
-
value=str(value),
|
|
556
|
-
time=timestamp
|
|
557
|
-
)
|
|
611
|
+
attr_values[key] = object_attribute_from_python(value, time=timestamp)
|
|
558
612
|
|
|
559
613
|
obj = Object(
|
|
560
614
|
id=obj_id,
|
|
@@ -624,7 +678,7 @@ class SyncEventLogger(EventLogger):
|
|
|
624
678
|
source, obj_type, attrs = rel_spec
|
|
625
679
|
|
|
626
680
|
# Resolve object from source
|
|
627
|
-
obj = self._resolve_source(source, context)
|
|
681
|
+
obj = self._resolve_source(source, context, obj_type)
|
|
628
682
|
if obj is None:
|
|
629
683
|
continue
|
|
630
684
|
|
|
@@ -678,7 +732,7 @@ class SyncEventLogger(EventLogger):
|
|
|
678
732
|
|
|
679
733
|
return context
|
|
680
734
|
|
|
681
|
-
def _resolve_source(self, source: str, context: Dict[str, Any]) -> Any:
|
|
735
|
+
def _resolve_source(self, source: str, context: Dict[str, Any], obj_type: str | None = None) -> Any:
|
|
682
736
|
"""
|
|
683
737
|
Resolve an object from a source expression.
|
|
684
738
|
|
|
@@ -686,13 +740,80 @@ class SyncEventLogger(EventLogger):
|
|
|
686
740
|
- "return" or "ret": Return value
|
|
687
741
|
- "self": For methods
|
|
688
742
|
- Any other string: Parameter name from context
|
|
743
|
+
- If source is "bb" (blackboard) and obj_type is provided, extract the field of that type
|
|
689
744
|
"""
|
|
690
745
|
if source == "return" or source == "ret":
|
|
691
746
|
return context.get("return")
|
|
692
747
|
elif source == "self":
|
|
693
748
|
return context.get("self")
|
|
694
749
|
else:
|
|
695
|
-
|
|
750
|
+
obj = context.get(source)
|
|
751
|
+
|
|
752
|
+
# If source is blackboard and we need a specific type, extract that field
|
|
753
|
+
if obj_type and source == "bb" and hasattr(obj, '__annotations__'):
|
|
754
|
+
return self._find_object_by_type(obj, obj_type)
|
|
755
|
+
|
|
756
|
+
return obj
|
|
757
|
+
|
|
758
|
+
def _find_object_by_type(self, blackboard: Any, obj_type: str) -> Any:
|
|
759
|
+
"""Find a field in the blackboard that matches the requested object type.
|
|
760
|
+
|
|
761
|
+
Scans the blackboard's fields and returns the first field whose type
|
|
762
|
+
annotation matches obj_type.
|
|
763
|
+
"""
|
|
764
|
+
# Get the class to check annotations
|
|
765
|
+
obj_class = blackboard if isinstance(blackboard, type) else type(blackboard)
|
|
766
|
+
|
|
767
|
+
if not hasattr(obj_class, '__annotations__'):
|
|
768
|
+
return blackboard
|
|
769
|
+
|
|
770
|
+
# Check each field's type annotation
|
|
771
|
+
for field_name, field_type in obj_class.__annotations__.items():
|
|
772
|
+
# Handle Annotated types
|
|
773
|
+
if get_origin(field_type) is Annotated:
|
|
774
|
+
args = get_args(field_type)
|
|
775
|
+
if args:
|
|
776
|
+
actual_type = args[0]
|
|
777
|
+
# Check if type name matches (handle both str and type)
|
|
778
|
+
type_name = actual_type if isinstance(actual_type, str) else actual_type.__name__
|
|
779
|
+
if type_name == obj_type:
|
|
780
|
+
field_value = getattr(blackboard, field_name, None)
|
|
781
|
+
# Return the actual value, not None
|
|
782
|
+
if field_value is not None:
|
|
783
|
+
return field_value
|
|
784
|
+
# Handle Union types (e.g., Sample | None)
|
|
785
|
+
elif get_origin(field_type) is Union:
|
|
786
|
+
args = get_args(field_type)
|
|
787
|
+
for arg in args:
|
|
788
|
+
# Skip None
|
|
789
|
+
if arg is type(None):
|
|
790
|
+
continue
|
|
791
|
+
# Check if this arg matches our target type
|
|
792
|
+
if get_origin(arg) is Annotated:
|
|
793
|
+
annotated_args = get_args(arg)
|
|
794
|
+
if annotated_args:
|
|
795
|
+
actual_type = annotated_args[0]
|
|
796
|
+
type_name = actual_type if isinstance(actual_type, str) else actual_type.__name__
|
|
797
|
+
if type_name == obj_type:
|
|
798
|
+
field_value = getattr(blackboard, field_name, None)
|
|
799
|
+
if field_value is not None:
|
|
800
|
+
return field_value
|
|
801
|
+
else:
|
|
802
|
+
type_name = arg if isinstance(arg, str) else arg.__name__
|
|
803
|
+
if type_name == obj_type:
|
|
804
|
+
field_value = getattr(blackboard, field_name, None)
|
|
805
|
+
if field_value is not None:
|
|
806
|
+
return field_value
|
|
807
|
+
else:
|
|
808
|
+
# Check if type name matches
|
|
809
|
+
type_name = field_type if isinstance(field_type, str) else field_type.__name__
|
|
810
|
+
if type_name == obj_type:
|
|
811
|
+
field_value = getattr(blackboard, field_name, None)
|
|
812
|
+
if field_value is not None:
|
|
813
|
+
return field_value
|
|
814
|
+
|
|
815
|
+
# Fallback: return blackboard if no matching field found
|
|
816
|
+
return blackboard
|
|
696
817
|
|
|
697
818
|
def _get_object_id(self, obj: Any) -> str | None:
|
|
698
819
|
"""
|
|
@@ -1031,7 +1152,7 @@ class SporeDecorator:
|
|
|
1031
1152
|
type=event_type,
|
|
1032
1153
|
time=timestamp,
|
|
1033
1154
|
attributes={
|
|
1034
|
-
k:
|
|
1155
|
+
k: attribute_value_from_python(v)
|
|
1035
1156
|
for k, v in event_attrs.items()
|
|
1036
1157
|
},
|
|
1037
1158
|
relationships={
|
|
@@ -5,11 +5,11 @@ Spores DSL Adapters
|
|
|
5
5
|
DSL-specific adapters for integrating spores logging with:
|
|
6
6
|
- Hypha (Petri nets)
|
|
7
7
|
- Rhizomorph (Behavior trees)
|
|
8
|
-
-
|
|
8
|
+
- Septum (State machines)
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
11
|
```python
|
|
12
|
-
from mycorrhizal.spores.dsl import HyphaAdapter, RhizomorphAdapter,
|
|
12
|
+
from mycorrhizal.spores.dsl import HyphaAdapter, RhizomorphAdapter, SeptumAdapter
|
|
13
13
|
|
|
14
14
|
# For Hypha (Petri nets)
|
|
15
15
|
hypha_adapter = HyphaAdapter()
|
|
@@ -27,11 +27,11 @@ Usage:
|
|
|
27
27
|
async def check(bb: Blackboard) -> Status:
|
|
28
28
|
return Status.SUCCESS
|
|
29
29
|
|
|
30
|
-
# For
|
|
31
|
-
|
|
30
|
+
# For Septum (State machines)
|
|
31
|
+
septum_adapter = SeptumAdapter()
|
|
32
32
|
|
|
33
|
-
@
|
|
34
|
-
@
|
|
33
|
+
@septum.on_state
|
|
34
|
+
@septum_adapter.log_state(event_type="state_execute")
|
|
35
35
|
async def on_state(ctx: SharedContext):
|
|
36
36
|
return Events.DONE
|
|
37
37
|
```
|
|
@@ -39,10 +39,10 @@ Usage:
|
|
|
39
39
|
|
|
40
40
|
from .hypha import HyphaAdapter
|
|
41
41
|
from .rhizomorph import RhizomorphAdapter
|
|
42
|
-
from .
|
|
42
|
+
from .septum import SeptumAdapter
|
|
43
43
|
|
|
44
44
|
__all__ = [
|
|
45
45
|
'HyphaAdapter',
|
|
46
46
|
'RhizomorphAdapter',
|
|
47
|
-
'
|
|
47
|
+
'SeptumAdapter',
|
|
48
48
|
]
|
mycorrhizal/spores/dsl/hypha.py
CHANGED
|
@@ -237,18 +237,10 @@ async def _log_transition_event(
|
|
|
237
237
|
event_attrs = {}
|
|
238
238
|
|
|
239
239
|
# Add token count
|
|
240
|
-
event_attrs["token_count"] =
|
|
241
|
-
name="token_count",
|
|
242
|
-
value=str(len(consumed)),
|
|
243
|
-
time=timestamp
|
|
244
|
-
)
|
|
240
|
+
event_attrs["token_count"] = attribute_value_from_python(len(consumed))
|
|
245
241
|
|
|
246
242
|
# Add transition name
|
|
247
|
-
event_attrs["transition_name"] =
|
|
248
|
-
name="transition_name",
|
|
249
|
-
value=func.__name__,
|
|
250
|
-
time=timestamp
|
|
251
|
-
)
|
|
243
|
+
event_attrs["transition_name"] = attribute_value_from_python(func.__name__)
|
|
252
244
|
|
|
253
245
|
# Extract from blackboard
|
|
254
246
|
bb_attrs = extract_attributes_from_blackboard(bb, timestamp)
|
|
@@ -358,11 +350,7 @@ async def _log_place_event(
|
|
|
358
350
|
|
|
359
351
|
# Build event attributes
|
|
360
352
|
event_attrs = {
|
|
361
|
-
"place_name":
|
|
362
|
-
name="place_name",
|
|
363
|
-
value=func.__name__,
|
|
364
|
-
time=timestamp
|
|
365
|
-
)
|
|
353
|
+
"place_name": attribute_value_from_python(func.__name__)
|
|
366
354
|
}
|
|
367
355
|
|
|
368
356
|
# Extract from blackboard
|
|
@@ -20,7 +20,7 @@ from ...spores import (
|
|
|
20
20
|
Event,
|
|
21
21
|
LogRecord,
|
|
22
22
|
Relationship,
|
|
23
|
-
|
|
23
|
+
attribute_value_from_python,
|
|
24
24
|
generate_event_id,
|
|
25
25
|
)
|
|
26
26
|
from ...spores.extraction import (
|
|
@@ -167,19 +167,11 @@ async def _log_node_event(
|
|
|
167
167
|
event_attrs = {}
|
|
168
168
|
|
|
169
169
|
# Add node name
|
|
170
|
-
event_attrs["node_name"] =
|
|
171
|
-
name="node_name",
|
|
172
|
-
value=func.__name__,
|
|
173
|
-
time=timestamp
|
|
174
|
-
)
|
|
170
|
+
event_attrs["node_name"] = attribute_value_from_python(func.__name__)
|
|
175
171
|
|
|
176
172
|
# Add status if requested and result is a Status
|
|
177
173
|
if log_status and isinstance(result, Status):
|
|
178
|
-
event_attrs["status"] =
|
|
179
|
-
name="status",
|
|
180
|
-
value=result.name,
|
|
181
|
-
time=timestamp
|
|
182
|
-
)
|
|
174
|
+
event_attrs["status"] = attribute_value_from_python(result.name)
|
|
183
175
|
|
|
184
176
|
# Extract from blackboard
|
|
185
177
|
bb_attrs = extract_attributes_from_blackboard(bb, timestamp)
|