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.
Files changed (45) hide show
  1. mycorrhizal/_version.py +1 -0
  2. mycorrhizal/common/__init__.py +15 -3
  3. mycorrhizal/common/cache.py +114 -0
  4. mycorrhizal/common/compilation.py +263 -0
  5. mycorrhizal/common/interface_detection.py +159 -0
  6. mycorrhizal/common/interfaces.py +3 -50
  7. mycorrhizal/common/mermaid.py +124 -0
  8. mycorrhizal/common/wrappers.py +1 -1
  9. mycorrhizal/hypha/core/builder.py +56 -8
  10. mycorrhizal/hypha/core/runtime.py +242 -107
  11. mycorrhizal/hypha/core/specs.py +19 -3
  12. mycorrhizal/mycelium/__init__.py +174 -0
  13. mycorrhizal/mycelium/core.py +619 -0
  14. mycorrhizal/mycelium/exceptions.py +30 -0
  15. mycorrhizal/mycelium/hypha_bridge.py +1143 -0
  16. mycorrhizal/mycelium/instance.py +440 -0
  17. mycorrhizal/mycelium/pn_context.py +276 -0
  18. mycorrhizal/mycelium/runner.py +165 -0
  19. mycorrhizal/mycelium/spores_integration.py +655 -0
  20. mycorrhizal/mycelium/tree_builder.py +102 -0
  21. mycorrhizal/mycelium/tree_spec.py +197 -0
  22. mycorrhizal/rhizomorph/README.md +82 -33
  23. mycorrhizal/rhizomorph/core.py +308 -82
  24. mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
  25. mycorrhizal/{enoki → septum}/core.py +326 -100
  26. mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
  27. mycorrhizal/{enoki → septum}/util.py +44 -21
  28. mycorrhizal/spores/__init__.py +72 -19
  29. mycorrhizal/spores/core.py +907 -75
  30. mycorrhizal/spores/dsl/__init__.py +8 -8
  31. mycorrhizal/spores/dsl/hypha.py +3 -15
  32. mycorrhizal/spores/dsl/rhizomorph.py +3 -11
  33. mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
  34. mycorrhizal/spores/encoder/json.py +21 -12
  35. mycorrhizal/spores/extraction.py +14 -11
  36. mycorrhizal/spores/models.py +75 -20
  37. mycorrhizal/spores/transport/__init__.py +9 -2
  38. mycorrhizal/spores/transport/base.py +36 -17
  39. mycorrhizal/spores/transport/file.py +126 -0
  40. mycorrhizal-0.2.0.dist-info/METADATA +335 -0
  41. mycorrhizal-0.2.0.dist-info/RECORD +54 -0
  42. mycorrhizal-0.1.0.dist-info/METADATA +0 -198
  43. mycorrhizal-0.1.0.dist-info/RECORD +0 -37
  44. /mycorrhizal/{enoki → septum}/__init__.py +0 -0
  45. {mycorrhizal-0.1.0.dist-info → mycorrhizal-0.2.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shared Mermaid diagram formatting utilities.
4
+
5
+ This module provides common formatting functions for generating Mermaid diagrams
6
+ across Mycorrhizal systems (Septum, Mycelium, etc.). Using these utilities ensures:
7
+ - Consistent visual styling across all systems
8
+ - Single source of truth for formatting logic
9
+ - Bug fixes apply to all systems
10
+
11
+ Usage:
12
+ from mycorrhizal.common.mermaid import format_state_node, format_transition
13
+
14
+ node = format_state_node("state1", "IdleState", is_initial=True)
15
+ edge = format_transition("state1", "state2", "START")
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Optional
21
+
22
+
23
+ def format_state_node(node_id: str, state_name: str, is_initial: bool = False) -> str:
24
+ """
25
+ Format a state node for Mermaid diagrams.
26
+
27
+ Args:
28
+ node_id: The unique identifier for this node (e.g., "fsm1_IdleState")
29
+ state_name: The display name for the state (e.g., "IdleState")
30
+ is_initial: Whether this is the initial/entry state
31
+
32
+ Returns:
33
+ Mermaid node definition as a string
34
+
35
+ Example:
36
+ >>> format_state_node("state1", "IdleState", is_initial=True)
37
+ ' state1(["IdleState (initial)"])'
38
+ >>> format_state_node("state2", "Running", is_initial=False)
39
+ ' state2(["Running"])'
40
+ """
41
+ display_name = state_name
42
+ if is_initial:
43
+ display_name = f"{state_name} (initial)"
44
+
45
+ # Use stadium shape (rounded rectangle) for states: node_id(["label"])
46
+ return f' {node_id}(["[{display_name}]"])'
47
+
48
+
49
+ def format_transition(
50
+ from_id: str,
51
+ to_id: str,
52
+ label: Optional[str] = None,
53
+ style: Optional[str] = None
54
+ ) -> str:
55
+ """
56
+ Format a transition edge for Mermaid diagrams.
57
+
58
+ Args:
59
+ from_id: Source node ID
60
+ to_id: Target node ID
61
+ label: Optional transition label (e.g., event name)
62
+ style: Optional edge style (e.g., ".thick" for thick lines)
63
+
64
+ Returns:
65
+ Mermaid edge definition as a string
66
+
67
+ Example:
68
+ >>> format_transition("state1", "state2", "START")
69
+ ' state1 -->|START| state2'
70
+ >>> format_transition("state1", "state2")
71
+ ' state1 --> state2'
72
+ """
73
+ edge = f" {from_id} -->"
74
+ if label:
75
+ edge = f"{edge}|{label}|"
76
+ edge = f"{edge} {to_id}"
77
+
78
+ if style:
79
+ edge = f"{edge}{style}"
80
+
81
+ return edge
82
+
83
+
84
+ def format_subgraph(subgraph_id: str, title: str, content: list[str]) -> str:
85
+ """
86
+ Format a subgraph for grouping related nodes.
87
+
88
+ Args:
89
+ subgraph_id: Unique identifier for the subgraph
90
+ title: Display title for the subgraph
91
+ content: List of lines containing the subgraph content
92
+
93
+ Returns:
94
+ Mermaid subgraph definition as a string
95
+
96
+ Example:
97
+ >>> content = [
98
+ ... ' state1(["Idle"])',
99
+ ... ' state2(["Running"])'
100
+ ... ]
101
+ >>> format_subgraph("FSM1", "My FSM", content)
102
+ ' subgraph FSM1 ["My FSM"]\\n state1(["Idle"])\\n state2(["Running"])\\n end'
103
+ """
104
+ lines = [f' subgraph {subgraph_id} ["{title}"]']
105
+ lines.extend(content)
106
+ lines.append(" end")
107
+ return "\n".join(lines)
108
+
109
+
110
+ def format_comment(text: str) -> str:
111
+ """
112
+ Format a comment for Mermaid diagrams.
113
+
114
+ Args:
115
+ text: Comment text
116
+
117
+ Returns:
118
+ Mermaid comment as a string
119
+
120
+ Example:
121
+ >>> format_comment("This is a comment")
122
+ ' %% This is a comment'
123
+ """
124
+ return f" %% {text}"
@@ -54,7 +54,7 @@ class BaseWrapper:
54
54
  and error handling.
55
55
  """
56
56
 
57
- __slots__ = ('_bb', '_allowed_fields', '_private_attrs')
57
+ __slots__ = ('_bb', '_allowed_fields', '_private_attrs', '__weakref__')
58
58
 
59
59
  def __init__(
60
60
  self,
@@ -6,7 +6,7 @@ NetBuilder provides the API for declaratively constructing Petri net specificati
6
6
  Used within @pn.net decorated functions.
7
7
  """
8
8
 
9
- from typing import Optional, Callable, List, Dict, Type, Any
9
+ from typing import Optional, Callable, List, Dict, Type, Any, Union
10
10
  from .specs import (
11
11
  NetSpec,
12
12
  PlaceSpec,
@@ -54,14 +54,52 @@ class NetBuilder:
54
54
 
55
55
  def place(
56
56
  self,
57
- name: str,
57
+ name_or_func: Union[str, Callable, None] = None,
58
58
  type: PlaceType = PlaceType.BAG,
59
59
  state_factory: Optional[Callable] = None,
60
- ) -> PlaceRef:
61
- """Declare a regular place"""
62
- place_spec = PlaceSpec(name, type, state_factory=state_factory)
63
- self.spec.places[name] = place_spec
64
- return PlaceRef(name, self.spec)
60
+ ) -> Union[PlaceRef, Callable]:
61
+ """Declare a regular place.
62
+
63
+ Can be used in three ways:
64
+
65
+ 1. As a method call (returns PlaceRef for use in arcs):
66
+ queue = builder.place("queue", type=PlaceType.QUEUE)
67
+
68
+ 2. As a decorator without parens (infers name from function):
69
+ @builder.place
70
+ def queue(bb):
71
+ return bb.tokens
72
+
73
+ 3. As a decorator with custom name:
74
+ @builder.place("custom_name")
75
+ def my_func(bb):
76
+ return bb.tokens
77
+ """
78
+ # Case 1: Used as decorator without parens - first arg is the function
79
+ if callable(name_or_func):
80
+ func = name_or_func
81
+ place_name = func.__name__
82
+ place_spec = PlaceSpec(place_name, type, handler=func, state_factory=state_factory)
83
+ self.spec.places[place_name] = place_spec
84
+ return PlaceRef(place_name, self.spec)
85
+
86
+ # Case 2 & 3: Used with explicit name (as method call or decorator with args)
87
+ # or called without any args (need to return decorator)
88
+ if isinstance(name_or_func, str):
89
+ name = name_or_func
90
+ place_spec = PlaceSpec(name, type, state_factory=state_factory)
91
+ self.spec.places[name] = place_spec
92
+ # Return PlaceRef - it's callable via __call__ for decorator use
93
+ return PlaceRef(name, self.spec)
94
+
95
+ # Case 4: Called without any args - shouldn't happen but handle gracefully
96
+ # This would be like @builder.place() with empty parens
97
+ def decorator(func: Callable) -> PlaceRef:
98
+ place_name = func.__name__
99
+ place_spec = PlaceSpec(place_name, type, handler=func, state_factory=state_factory)
100
+ self.spec.places[place_name] = place_spec
101
+ return PlaceRef(place_name, self.spec)
102
+ return decorator
65
103
 
66
104
  def io_input_place(self):
67
105
  """Decorator for IOInputPlace with async generator"""
@@ -98,7 +136,17 @@ class NetBuilder:
98
136
  guard: Optional[GuardSpec] = None,
99
137
  state_factory: Optional[Callable] = None,
100
138
  ):
101
- """Decorator for transition function"""
139
+ """Decorator for transition function.
140
+
141
+ Args:
142
+ guard: Optional guard specification
143
+ state_factory: Optional state factory for transition state
144
+
145
+ Usage:
146
+ @builder.transition()
147
+ async def my_transition(consumed, bb, timebase):
148
+ yield {output: token}
149
+ """
102
150
 
103
151
  def decorator(func: Callable) -> TransitionRef:
104
152
  name = func.__name__