mycorrhizal 0.1.0__py3-none-any.whl → 0.2.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.
- mycorrhizal/_version.py +1 -0
- 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 +56 -8
- mycorrhizal/hypha/core/runtime.py +242 -107
- mycorrhizal/hypha/core/specs.py +19 -3
- 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 +308 -82
- 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 +72 -19
- mycorrhizal/spores/core.py +907 -75
- 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 +75 -20
- mycorrhizal/spores/transport/__init__.py +9 -2
- mycorrhizal/spores/transport/base.py +36 -17
- mycorrhizal/spores/transport/file.py +126 -0
- mycorrhizal-0.2.0.dist-info/METADATA +335 -0
- mycorrhizal-0.2.0.dist-info/RECORD +54 -0
- mycorrhizal-0.1.0.dist-info/METADATA +0 -198
- mycorrhizal-0.1.0.dist-info/RECORD +0 -37
- /mycorrhizal/{enoki → septum}/__init__.py +0 -0
- {mycorrhizal-0.1.0.dist-info → mycorrhizal-0.2.0.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
|
@@ -6,22 +6,53 @@ OCEL (Object-Centric Event Log) compatible logging system for tracking events
|
|
|
6
6
|
and objects in Mycorrhizal systems. Provides automatic extraction of events
|
|
7
7
|
and objects from DSL execution.
|
|
8
8
|
|
|
9
|
-
Usage:
|
|
10
|
-
from mycorrhizal.spores import configure,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
Usage (Standalone Event and Object Logging):
|
|
10
|
+
from mycorrhizal.spores import configure, get_spore_sync, get_spore_async
|
|
11
|
+
from mycorrhizal.spores.transport import SyncFileTransport, AsyncFileTransport
|
|
12
|
+
from mycorrhizal.spores.models import SporesAttr
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
|
|
16
|
+
# Mark domain model fields for object attribute logging
|
|
17
|
+
class Order(BaseModel):
|
|
18
|
+
id: str
|
|
19
|
+
status: Annotated[str, SporesAttr]
|
|
20
|
+
total: Annotated[float, SporesAttr]
|
|
21
|
+
|
|
22
|
+
# Sync code
|
|
23
|
+
configure(transport=SyncFileTransport("logs/ocel.jsonl"))
|
|
24
|
+
spore = get_spore_sync(__name__)
|
|
25
|
+
|
|
26
|
+
@spore.log_event(
|
|
27
|
+
event_type="OrderCreated",
|
|
28
|
+
relationships={"order": ("return", "Order")},
|
|
29
|
+
)
|
|
30
|
+
def create_order(customer: Customer, items: list) -> Order:
|
|
31
|
+
return Order(...)
|
|
32
|
+
|
|
33
|
+
# Async code
|
|
34
|
+
configure(transport=AsyncFileTransport("logs/ocel.jsonl"))
|
|
35
|
+
aspore = get_spore_async(__name__)
|
|
36
|
+
|
|
37
|
+
@aspore.log_event(
|
|
38
|
+
event_type="OrderCreated",
|
|
39
|
+
relationships={"order": ("return", "Order")},
|
|
17
40
|
)
|
|
41
|
+
async def create_order_async(customer: Customer, items: list) -> Order:
|
|
42
|
+
return Order(...)
|
|
43
|
+
|
|
44
|
+
Usage (DSL Adapters):
|
|
45
|
+
from mycorrhizal.spores import configure, spore
|
|
46
|
+
from mycorrhizal.spores.models import EventAttr, ObjectRef, ObjectScope
|
|
47
|
+
from pydantic import BaseModel
|
|
48
|
+
from typing import Annotated
|
|
18
49
|
|
|
19
|
-
# Mark
|
|
50
|
+
# Mark blackboard fields for event attribute extraction
|
|
20
51
|
class MissionContext(BaseModel):
|
|
21
52
|
mission_id: Annotated[str, EventAttr]
|
|
22
53
|
robot: Annotated[Robot, ObjectRef(qualifier="actor", scope=ObjectScope.GLOBAL)]
|
|
23
54
|
|
|
24
|
-
# Mark object types
|
|
55
|
+
# Mark object types for logging
|
|
25
56
|
@spore.object(object_type="Robot")
|
|
26
57
|
class Robot(BaseModel):
|
|
27
58
|
id: str
|
|
@@ -30,14 +61,17 @@ Usage:
|
|
|
30
61
|
DSL Adapters:
|
|
31
62
|
HyphaAdapter - Log Petri net transitions with token relationships
|
|
32
63
|
RhizomorphAdapter - Log behavior tree node execution with status
|
|
33
|
-
|
|
64
|
+
SeptumAdapter - Log state machine execution and lifecycle events
|
|
65
|
+
|
|
66
|
+
Annotation Types:
|
|
67
|
+
EventAttr - Mark blackboard fields for automatic event attribute extraction (DSL adapters)
|
|
68
|
+
SporesAttr - Mark domain model fields for automatic object attribute logging (log_event)
|
|
69
|
+
ObjectRef - Mark blackboard fields as object references with scope
|
|
34
70
|
|
|
35
71
|
Key Concepts:
|
|
36
72
|
Event - Something that happens at a point in time with attributes
|
|
37
73
|
Object - An entity with a type and attributes
|
|
38
74
|
Relationship - Link between events and objects (e.g., "actor", "target")
|
|
39
|
-
EventAttr - Mark blackboard fields for automatic event attribute extraction
|
|
40
|
-
ObjectRef - Mark blackboard fields as object references with scope
|
|
41
75
|
|
|
42
76
|
Object Scopes:
|
|
43
77
|
EVENT - Object exists only for this event (default)
|
|
@@ -48,8 +82,13 @@ from .core import (
|
|
|
48
82
|
configure,
|
|
49
83
|
get_config,
|
|
50
84
|
get_object_cache,
|
|
85
|
+
get_spore_sync,
|
|
86
|
+
get_spore_async,
|
|
51
87
|
spore,
|
|
52
88
|
SporesConfig,
|
|
89
|
+
EventLogger,
|
|
90
|
+
AsyncEventLogger,
|
|
91
|
+
SyncEventLogger,
|
|
53
92
|
)
|
|
54
93
|
|
|
55
94
|
from .models import (
|
|
@@ -62,6 +101,7 @@ from .models import (
|
|
|
62
101
|
ObjectRef,
|
|
63
102
|
ObjectScope,
|
|
64
103
|
EventAttr,
|
|
104
|
+
SporesAttr,
|
|
65
105
|
generate_event_id,
|
|
66
106
|
generate_object_id,
|
|
67
107
|
attribute_value_from_python,
|
|
@@ -71,17 +111,20 @@ from .models import (
|
|
|
71
111
|
from .cache import ObjectLRUCache
|
|
72
112
|
|
|
73
113
|
from .encoder import Encoder, JSONEncoder
|
|
74
|
-
from .transport import Transport
|
|
114
|
+
from .transport import SyncTransport, AsyncTransport, Transport, SyncFileTransport, AsyncFileTransport
|
|
75
115
|
|
|
76
116
|
# DSL adapters
|
|
77
117
|
from .dsl import (
|
|
78
118
|
HyphaAdapter,
|
|
79
119
|
RhizomorphAdapter,
|
|
80
|
-
|
|
120
|
+
SeptumAdapter,
|
|
81
121
|
)
|
|
82
122
|
|
|
83
123
|
|
|
84
|
-
|
|
124
|
+
try:
|
|
125
|
+
from mycorrhizal._version import version as __version__
|
|
126
|
+
except ImportError:
|
|
127
|
+
__version__ = "0.0.0+dev"
|
|
85
128
|
|
|
86
129
|
|
|
87
130
|
__all__ = [
|
|
@@ -89,7 +132,12 @@ __all__ = [
|
|
|
89
132
|
'configure',
|
|
90
133
|
'get_config',
|
|
91
134
|
'get_object_cache',
|
|
92
|
-
'
|
|
135
|
+
'get_spore_sync',
|
|
136
|
+
'get_spore_async',
|
|
137
|
+
'EventLogger',
|
|
138
|
+
'AsyncEventLogger',
|
|
139
|
+
'SyncEventLogger',
|
|
140
|
+
'spore', # For DSL adapters and @spore.object() decorator
|
|
93
141
|
'SporesConfig',
|
|
94
142
|
|
|
95
143
|
# Models
|
|
@@ -102,6 +150,7 @@ __all__ = [
|
|
|
102
150
|
'ObjectRef',
|
|
103
151
|
'ObjectScope',
|
|
104
152
|
'EventAttr',
|
|
153
|
+
'SporesAttr',
|
|
105
154
|
'generate_event_id',
|
|
106
155
|
'generate_object_id',
|
|
107
156
|
'attribute_value_from_python',
|
|
@@ -115,10 +164,14 @@ __all__ = [
|
|
|
115
164
|
'JSONEncoder',
|
|
116
165
|
|
|
117
166
|
# Transport
|
|
118
|
-
'
|
|
167
|
+
'SyncTransport',
|
|
168
|
+
'AsyncTransport',
|
|
169
|
+
'Transport', # Alias for AsyncTransport (backward compatibility)
|
|
170
|
+
'SyncFileTransport',
|
|
171
|
+
'AsyncFileTransport',
|
|
119
172
|
|
|
120
173
|
# DSL Adapters
|
|
121
174
|
'HyphaAdapter',
|
|
122
175
|
'RhizomorphAdapter',
|
|
123
|
-
'
|
|
176
|
+
'SeptumAdapter',
|
|
124
177
|
]
|