mycorrhizal 0.1.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.
Files changed (37) hide show
  1. mycorrhizal/__init__.py +3 -0
  2. mycorrhizal/common/__init__.py +68 -0
  3. mycorrhizal/common/interface_builder.py +203 -0
  4. mycorrhizal/common/interfaces.py +412 -0
  5. mycorrhizal/common/timebase.py +99 -0
  6. mycorrhizal/common/wrappers.py +532 -0
  7. mycorrhizal/enoki/__init__.py +0 -0
  8. mycorrhizal/enoki/core.py +1545 -0
  9. mycorrhizal/enoki/testing_utils.py +529 -0
  10. mycorrhizal/enoki/util.py +220 -0
  11. mycorrhizal/hypha/__init__.py +0 -0
  12. mycorrhizal/hypha/core/__init__.py +107 -0
  13. mycorrhizal/hypha/core/builder.py +404 -0
  14. mycorrhizal/hypha/core/runtime.py +890 -0
  15. mycorrhizal/hypha/core/specs.py +234 -0
  16. mycorrhizal/hypha/util.py +38 -0
  17. mycorrhizal/rhizomorph/README.md +220 -0
  18. mycorrhizal/rhizomorph/__init__.py +0 -0
  19. mycorrhizal/rhizomorph/core.py +1729 -0
  20. mycorrhizal/rhizomorph/util.py +45 -0
  21. mycorrhizal/spores/__init__.py +124 -0
  22. mycorrhizal/spores/cache.py +208 -0
  23. mycorrhizal/spores/core.py +419 -0
  24. mycorrhizal/spores/dsl/__init__.py +48 -0
  25. mycorrhizal/spores/dsl/enoki.py +514 -0
  26. mycorrhizal/spores/dsl/hypha.py +399 -0
  27. mycorrhizal/spores/dsl/rhizomorph.py +351 -0
  28. mycorrhizal/spores/encoder/__init__.py +11 -0
  29. mycorrhizal/spores/encoder/base.py +42 -0
  30. mycorrhizal/spores/encoder/json.py +159 -0
  31. mycorrhizal/spores/extraction.py +484 -0
  32. mycorrhizal/spores/models.py +288 -0
  33. mycorrhizal/spores/transport/__init__.py +10 -0
  34. mycorrhizal/spores/transport/base.py +46 -0
  35. mycorrhizal-0.1.0.dist-info/METADATA +198 -0
  36. mycorrhizal-0.1.0.dist-info/RECORD +37 -0
  37. mycorrhizal-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Enoki State Machine Utilities
4
+
5
+ Utility functions for FSM operations like Mermaid diagram generation.
6
+ """
7
+ from typing import Dict, List, Set, Optional, Union
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class _TransitionInfo:
13
+ """Information about a transition for mermaid diagram"""
14
+ label: str
15
+ target: str
16
+ transition_type: str # "normal", "push", "pop", "again", "retry", etc.
17
+
18
+
19
+ def to_mermaid(fsm) -> str:
20
+ """
21
+ Generate Mermaid diagram from a StateMachine instance.
22
+
23
+ This creates a flowchart showing all states and their transitions,
24
+ including special handling for Push/Pop transitions.
25
+
26
+ Args:
27
+ fsm: A StateMachine instance (from mycorrhizal.enoki.core)
28
+
29
+ Returns:
30
+ Mermaid diagram as a string
31
+
32
+ Example:
33
+ ```python
34
+ from mycorrhizal.enoki.core import StateMachine
35
+ from mycorrhizal.enoki.util import to_mermaid
36
+
37
+ fsm = StateMachine(initial_state=MyState)
38
+ await fsm.initialize()
39
+
40
+ # Generate diagram
41
+ mermaid_diagram = to_mermaid(fsm)
42
+ print(mermaid_diagram)
43
+ ```
44
+ """
45
+ # Import here to avoid circular imports
46
+ from mycorrhizal.enoki.core import (
47
+ StateSpec,
48
+ StateRef,
49
+ LabeledTransition,
50
+ Push,
51
+ Pop,
52
+ Again,
53
+ Retry,
54
+ Unhandled,
55
+ Repeat,
56
+ Restart,
57
+ )
58
+
59
+ lines: List[str] = ["flowchart TD"]
60
+ node_ids: Dict[str, str] = {}
61
+ counter = 0
62
+
63
+ # Get all validated states from the registry
64
+ validation_result = fsm.registry.validate_all_states(
65
+ fsm.initial_state, fsm.error_state
66
+ )
67
+ state_names: Set[str] = validation_result.discovered_states
68
+
69
+ def nid(state_name: str) -> str:
70
+ """Get or create a node ID for a state"""
71
+ nonlocal counter
72
+ if state_name in node_ids:
73
+ return node_ids[state_name]
74
+ counter += 1
75
+ node_ids[state_name] = f"S{counter}"
76
+ return node_ids[state_name]
77
+
78
+ # Add initial state marker
79
+ initial_name = fsm.initial_state.name if isinstance(fsm.initial_state, StateSpec) else str(fsm.initial_state)
80
+ lines.append(f" start((start)) --> {nid(initial_name)}")
81
+
82
+ # Add error state if exists
83
+ if fsm.error_state:
84
+ error_name = fsm.error_state.name if isinstance(fsm.error_state, StateSpec) else str(fsm.error_state)
85
+ lines.append(f" {nid(error_name)}(((ERROR)))")
86
+
87
+ # Collect all transitions
88
+ transitions: Dict[str, List[_TransitionInfo]] = {}
89
+
90
+ for state_name in state_names:
91
+ state = fsm.registry.resolve_state(state_name)
92
+ if not state:
93
+ continue
94
+
95
+ transitions[state_name] = []
96
+
97
+ # Get the transition list
98
+ raw_transitions = state.get_transitions()
99
+
100
+ # Handle single transition
101
+ if isinstance(raw_transitions, (Again, Retry, Unhandled, Repeat, Restart, Pop)):
102
+ raw_transitions = [raw_transitions]
103
+ elif not isinstance(raw_transitions, list):
104
+ raw_transitions = list(raw_transitions) if raw_transitions else []
105
+
106
+ for trans in raw_transitions:
107
+ match trans:
108
+ case LabeledTransition(label, target):
109
+ match target:
110
+ case Again():
111
+ transitions[state_name].append(
112
+ _TransitionInfo(label.name, state_name, "again")
113
+ )
114
+ case Retry():
115
+ transitions[state_name].append(
116
+ _TransitionInfo(label.name, state_name, "retry")
117
+ )
118
+ case Unhandled():
119
+ transitions[state_name].append(
120
+ _TransitionInfo(label.name, state_name, "unhandled")
121
+ )
122
+ case Repeat():
123
+ transitions[state_name].append(
124
+ _TransitionInfo(label.name, state_name, "repeat")
125
+ )
126
+ case Restart():
127
+ transitions[state_name].append(
128
+ _TransitionInfo(label.name, state_name, "restart")
129
+ )
130
+ case Pop():
131
+ transitions[state_name].append(
132
+ _TransitionInfo(label.name, "__pop__", "pop")
133
+ )
134
+ case Push() as p:
135
+ # Push transitions - show all states being pushed
136
+ for push_state in p.push_states:
137
+ push_name = push_state.name if isinstance(push_state, StateSpec) else str(push_state)
138
+ transitions[state_name].append(
139
+ _TransitionInfo(f"{label.name} → push", push_name, "push")
140
+ )
141
+ case StateSpec() as target_state:
142
+ transitions[state_name].append(
143
+ _TransitionInfo(label.name, target_state.name, "normal")
144
+ )
145
+ case StateRef() as ref:
146
+ transitions[state_name].append(
147
+ _TransitionInfo(label.name, ref.state, "normal")
148
+ )
149
+ case _:
150
+ # Unknown transition type
151
+ pass
152
+
153
+ case Again():
154
+ transitions[state_name].append(
155
+ _TransitionInfo("", state_name, "again")
156
+ )
157
+ case Retry():
158
+ transitions[state_name].append(
159
+ _TransitionInfo("", state_name, "retry")
160
+ )
161
+ case Unhandled():
162
+ transitions[state_name].append(
163
+ _TransitionInfo("", state_name, "unhandled")
164
+ )
165
+ case Repeat():
166
+ transitions[state_name].append(
167
+ _TransitionInfo("", state_name, "repeat")
168
+ )
169
+ case Restart():
170
+ transitions[state_name].append(
171
+ _TransitionInfo("", state_name, "restart")
172
+ )
173
+ case Pop():
174
+ transitions[state_name].append(
175
+ _TransitionInfo("", "__pop__", "pop")
176
+ )
177
+
178
+ # Generate nodes and edges
179
+ for state_name in state_names:
180
+ state = fsm.registry.resolve_state(state_name)
181
+ if not state:
182
+ continue
183
+
184
+ state_id = nid(state_name)
185
+
186
+ # Determine node shape based on state properties
187
+ if state.config.terminal:
188
+ shape = f'[{state_name} (**terminal**)]'
189
+ elif state_name == initial_name:
190
+ shape = f'[{state_name}]'
191
+ elif fsm.error_state and state_name == (fsm.error_state.name if isinstance(fsm.error_state, StateSpec) else str(fsm.error_state)):
192
+ shape = f'[{state_name}]'
193
+ else:
194
+ shape = f'[{state_name}]'
195
+
196
+ lines.append(f" {state_id}{shape}")
197
+
198
+ # Add transitions
199
+ for trans in transitions.get(state_name, []):
200
+ if trans.transition_type in ("again", "retry", "repeat", "restart", "unhandled"):
201
+ # Self-loop
202
+ label = f'"{trans.transition_type}"' if trans.transition_type else ""
203
+ lines.append(f" {state_id} -->|{label}| {state_id}")
204
+ elif trans.transition_type == "pop":
205
+ # Pop goes to a special pop node
206
+ lines.append(f" {state_id} -->|\"{trans.label or 'pop'}\"| pop((pop))")
207
+ elif trans.transition_type == "push":
208
+ # Push transition
209
+ target_id = nid(trans.target)
210
+ lines.append(f" {state_id} -->|\"{trans.label}\"| {target_id}")
211
+ else:
212
+ # Normal transition
213
+ target_id = nid(trans.target)
214
+ lines.append(f" {state_id} -->|\"{trans.label}\"| {target_id}")
215
+
216
+ # Add pop node if any pop transitions exist
217
+ if any(t.transition_type == "pop" for transs in transitions.values() for t in transs):
218
+ lines.append(" pop((pop))")
219
+
220
+ return "\n".join(lines)
File without changes
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hypha - Decorator-based Colored Petri Net Framework
4
+
5
+ A DSL for defining and executing colored Petri nets with support for asyncio,
6
+ various place types, guards, and hierarchical subnet composition.
7
+
8
+ Usage:
9
+ from mycorrhizal.hypha.core import pn, Runner, PlaceType
10
+
11
+ @pn.net
12
+ def ProcessingNet(builder):
13
+ # Define places
14
+ pending = builder.place("pending", type=PlaceType.QUEUE)
15
+ processed = builder.place("processed", type=PlaceType.QUEUE)
16
+
17
+ # Define transitions
18
+ @builder.transition()
19
+ async def process(consumed, bb, timebase):
20
+ for token in consumed:
21
+ result = await handle(token)
22
+ yield {processed: result}
23
+
24
+ # Wire the net
25
+ builder.arc(pending, process).arc(processed)
26
+
27
+ # Run the Petri net
28
+ runner = Runner(ProcessingNet, bb=blackboard)
29
+ await runner.start(timebase)
30
+
31
+ Key Classes:
32
+ NetBuilder - Builder for constructing Petri nets
33
+ PlaceType - Type of place (QUEUE, SET, FLAG, COUNTER)
34
+ PlaceSpec - Specification for a place
35
+ TransitionSpec - Specification for a transition
36
+ Runner - Runtime for executing Petri nets
37
+
38
+ Place Types:
39
+ QUEUE - FIFO queue, allows multiple tokens
40
+ SET - Unordered collection with unique tokens
41
+ FLAG - Boolean place (has token or not)
42
+ COUNTER - Numeric place with bounded capacity
43
+
44
+ Transitions:
45
+ Transitions consume tokens from input places and produce tokens for output places.
46
+ They are async functions that yield dictionaries mapping place references to tokens.
47
+
48
+ Guards:
49
+ Guards are conditions that must be satisfied for a transition to fire.
50
+ They can check token values, blackboard state, or external conditions.
51
+ """
52
+
53
+ from .specs import (
54
+ PlaceType,
55
+ PlaceSpec,
56
+ GuardSpec,
57
+ TransitionSpec,
58
+ ArcSpec,
59
+ NetSpec,
60
+ PlaceRef,
61
+ TransitionRef,
62
+ SubnetRef,
63
+ )
64
+
65
+ from .builder import (
66
+ NetBuilder,
67
+ ArcChain,
68
+ PetriNetDSL,
69
+ pn,
70
+ )
71
+
72
+ from .runtime import (
73
+ PlaceRuntime,
74
+ TransitionRuntime,
75
+ NetRuntime,
76
+ Runner,
77
+ )
78
+
79
+ __all__ = [
80
+ # Core types
81
+ 'PlaceType',
82
+ 'GuardSpec',
83
+
84
+ # Specification types (for type hints)
85
+ 'PlaceSpec',
86
+ 'TransitionSpec',
87
+ 'ArcSpec',
88
+ 'NetSpec',
89
+
90
+ # Reference types
91
+ 'PlaceRef',
92
+ 'TransitionRef',
93
+ 'SubnetRef',
94
+
95
+ # Builder
96
+ 'NetBuilder',
97
+ 'ArcChain',
98
+
99
+ # Main API
100
+ 'pn',
101
+
102
+ # Runtime
103
+ 'PlaceRuntime',
104
+ 'TransitionRuntime',
105
+ 'NetRuntime',
106
+ 'Runner',
107
+ ]