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.
- mycorrhizal/__init__.py +3 -0
- mycorrhizal/common/__init__.py +68 -0
- mycorrhizal/common/interface_builder.py +203 -0
- mycorrhizal/common/interfaces.py +412 -0
- mycorrhizal/common/timebase.py +99 -0
- mycorrhizal/common/wrappers.py +532 -0
- mycorrhizal/enoki/__init__.py +0 -0
- mycorrhizal/enoki/core.py +1545 -0
- mycorrhizal/enoki/testing_utils.py +529 -0
- mycorrhizal/enoki/util.py +220 -0
- mycorrhizal/hypha/__init__.py +0 -0
- mycorrhizal/hypha/core/__init__.py +107 -0
- mycorrhizal/hypha/core/builder.py +404 -0
- mycorrhizal/hypha/core/runtime.py +890 -0
- mycorrhizal/hypha/core/specs.py +234 -0
- mycorrhizal/hypha/util.py +38 -0
- mycorrhizal/rhizomorph/README.md +220 -0
- mycorrhizal/rhizomorph/__init__.py +0 -0
- mycorrhizal/rhizomorph/core.py +1729 -0
- mycorrhizal/rhizomorph/util.py +45 -0
- mycorrhizal/spores/__init__.py +124 -0
- mycorrhizal/spores/cache.py +208 -0
- mycorrhizal/spores/core.py +419 -0
- mycorrhizal/spores/dsl/__init__.py +48 -0
- mycorrhizal/spores/dsl/enoki.py +514 -0
- mycorrhizal/spores/dsl/hypha.py +399 -0
- mycorrhizal/spores/dsl/rhizomorph.py +351 -0
- mycorrhizal/spores/encoder/__init__.py +11 -0
- mycorrhizal/spores/encoder/base.py +42 -0
- mycorrhizal/spores/encoder/json.py +159 -0
- mycorrhizal/spores/extraction.py +484 -0
- mycorrhizal/spores/models.py +288 -0
- mycorrhizal/spores/transport/__init__.py +10 -0
- mycorrhizal/spores/transport/base.py +46 -0
- mycorrhizal-0.1.0.dist-info/METADATA +198 -0
- mycorrhizal-0.1.0.dist-info/RECORD +37 -0
- mycorrhizal-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Spores Data Models
|
|
4
|
+
|
|
5
|
+
OCEL-compatible data models for event and object logging.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Dict, Optional, Union, Any
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# OCEL Data Models
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class Relationship:
|
|
22
|
+
"""
|
|
23
|
+
A relationship from an event to an object.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
object_id: The ID of the related object
|
|
27
|
+
qualifier: How the object relates to the event (e.g., "input", "output", "actor")
|
|
28
|
+
"""
|
|
29
|
+
object_id: str
|
|
30
|
+
qualifier: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class EventAttributeValue:
|
|
35
|
+
"""
|
|
36
|
+
An attribute value for an event.
|
|
37
|
+
|
|
38
|
+
OCEL attributes include timestamps for versioning.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
name: The attribute name
|
|
42
|
+
value: The string representation of the value
|
|
43
|
+
time: When this attribute value was set
|
|
44
|
+
"""
|
|
45
|
+
name: str
|
|
46
|
+
value: str
|
|
47
|
+
time: datetime
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ObjectAttributeValue:
|
|
52
|
+
"""
|
|
53
|
+
An attribute value for an object.
|
|
54
|
+
|
|
55
|
+
OCEL attributes include timestamps for versioning.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
name: The attribute name
|
|
59
|
+
value: The string representation of the value
|
|
60
|
+
time: When this attribute value was set
|
|
61
|
+
"""
|
|
62
|
+
name: str
|
|
63
|
+
value: str
|
|
64
|
+
time: datetime
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Event:
|
|
69
|
+
"""
|
|
70
|
+
An OCEL event representing something that happened.
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
id: Unique event identifier
|
|
74
|
+
type: Event type/category
|
|
75
|
+
time: When the event occurred
|
|
76
|
+
attributes: Event attributes (name -> EventAttributeValue)
|
|
77
|
+
relationships: Objects related to this event (qualifier -> Relationship)
|
|
78
|
+
"""
|
|
79
|
+
id: str
|
|
80
|
+
type: str
|
|
81
|
+
time: datetime
|
|
82
|
+
attributes: Dict[str, EventAttributeValue] = field(default_factory=dict)
|
|
83
|
+
relationships: Dict[str, Relationship] = field(default_factory=dict)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class Object:
|
|
88
|
+
"""
|
|
89
|
+
An OCEL object representing an entity in the system.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
id: Unique object identifier
|
|
93
|
+
type: Object type/category
|
|
94
|
+
attributes: Object attributes (name -> ObjectAttributeValue)
|
|
95
|
+
relationships: Other objects related to this object (qualifier -> Relationship)
|
|
96
|
+
"""
|
|
97
|
+
id: str
|
|
98
|
+
type: str
|
|
99
|
+
attributes: Dict[str, ObjectAttributeValue] = field(default_factory=dict)
|
|
100
|
+
relationships: Dict[str, Relationship] = field(default_factory=dict)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class LogRecord:
|
|
105
|
+
"""
|
|
106
|
+
A log record containing either an event or an object (not both).
|
|
107
|
+
|
|
108
|
+
This is the union type used for streaming to OCEL consumers.
|
|
109
|
+
|
|
110
|
+
Attributes:
|
|
111
|
+
event: An event record (if logging an event)
|
|
112
|
+
object: An object record (if logging an object)
|
|
113
|
+
|
|
114
|
+
Note:
|
|
115
|
+
Exactly one of event or object must be set.
|
|
116
|
+
"""
|
|
117
|
+
event: Optional[Event] = None
|
|
118
|
+
object: Optional[Object] = None
|
|
119
|
+
|
|
120
|
+
def __post_init__(self):
|
|
121
|
+
"""Validate that exactly one of event or object is set."""
|
|
122
|
+
if self.event is None and self.object is None:
|
|
123
|
+
raise ValueError("LogRecord must have either event or object set")
|
|
124
|
+
if self.event is not None and self.object is not None:
|
|
125
|
+
raise ValueError("LogRecord cannot have both event and object set")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ============================================================================
|
|
129
|
+
# Spores-Specific Metadata
|
|
130
|
+
# ============================================================================
|
|
131
|
+
|
|
132
|
+
class ObjectScope(Enum):
|
|
133
|
+
"""Scope for object associations with events."""
|
|
134
|
+
GLOBAL = "global" # Included in every event
|
|
135
|
+
EVENT = "event" # Only when explicitly requested
|
|
136
|
+
AUTO = "auto" # Automatically detected from context
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class ObjectRef:
|
|
141
|
+
"""
|
|
142
|
+
Metadata annotation for object references in blackboard interfaces.
|
|
143
|
+
|
|
144
|
+
Used with typing.Annotated to mark fields as OCEL objects:
|
|
145
|
+
|
|
146
|
+
robot: Annotated[Robot, ObjectRef(qualifier="actor", scope=ObjectScope.GLOBAL)]
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
qualifier: How the object relates to events ("input", "output", "actor", etc.)
|
|
150
|
+
scope: When to include this object in events
|
|
151
|
+
"""
|
|
152
|
+
qualifier: str
|
|
153
|
+
scope: Union[ObjectScope, str] = ObjectScope.EVENT
|
|
154
|
+
|
|
155
|
+
def __post_init__(self):
|
|
156
|
+
"""Convert string scope to ObjectScope enum if needed."""
|
|
157
|
+
if isinstance(self.scope, str):
|
|
158
|
+
object.__setattr__(self, 'scope', ObjectScope(self.scope))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True)
|
|
162
|
+
class EventAttr:
|
|
163
|
+
"""
|
|
164
|
+
Metadata annotation for event attributes in blackboard interfaces.
|
|
165
|
+
|
|
166
|
+
Used with typing.Annotated to mark fields that should be logged as event attributes:
|
|
167
|
+
|
|
168
|
+
mission_id: Annotated[str, EventAttr]
|
|
169
|
+
battery_level: Annotated[int, EventAttr]
|
|
170
|
+
|
|
171
|
+
Attributes:
|
|
172
|
+
name: Optional custom name for the attribute (defaults to field name)
|
|
173
|
+
"""
|
|
174
|
+
name: Optional[str] = None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ============================================================================
|
|
178
|
+
# Attribute Value Conversion
|
|
179
|
+
# ============================================================================
|
|
180
|
+
|
|
181
|
+
def attribute_value_from_python(value: Any, time: datetime) -> EventAttributeValue:
|
|
182
|
+
"""
|
|
183
|
+
Convert a Python value to an OCEL EventAttributeValue.
|
|
184
|
+
|
|
185
|
+
All values are converted to strings per OCEL specification.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
value: The Python value to convert
|
|
189
|
+
time: The timestamp for this attribute value
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
An EventAttributeValue with string representation
|
|
193
|
+
"""
|
|
194
|
+
# Handle None
|
|
195
|
+
if value is None:
|
|
196
|
+
str_value = "null"
|
|
197
|
+
|
|
198
|
+
# Handle enums (use name)
|
|
199
|
+
elif isinstance(value, Enum):
|
|
200
|
+
str_value = value.name
|
|
201
|
+
|
|
202
|
+
# Handle datetime (ISO format)
|
|
203
|
+
elif isinstance(value, datetime):
|
|
204
|
+
str_value = value.isoformat()
|
|
205
|
+
|
|
206
|
+
# Handle bool (lowercase true/false)
|
|
207
|
+
elif isinstance(value, bool):
|
|
208
|
+
str_value = "true" if value else "false"
|
|
209
|
+
|
|
210
|
+
# Handle primitives
|
|
211
|
+
elif isinstance(value, (str, int, float)):
|
|
212
|
+
str_value = str(value)
|
|
213
|
+
|
|
214
|
+
# Everything else: convert to string
|
|
215
|
+
else:
|
|
216
|
+
str_value = str(value)
|
|
217
|
+
|
|
218
|
+
return EventAttributeValue(
|
|
219
|
+
name="", # Name set by caller
|
|
220
|
+
value=str_value,
|
|
221
|
+
time=time
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def object_attribute_from_python(value: Any, time: datetime) -> ObjectAttributeValue:
|
|
226
|
+
"""
|
|
227
|
+
Convert a Python value to an OCEL ObjectAttributeValue.
|
|
228
|
+
|
|
229
|
+
Same conversion rules as attribute_value_from_python but for objects.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
value: The Python value to convert
|
|
233
|
+
time: The timestamp for this attribute value
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
An ObjectAttributeValue with string representation
|
|
237
|
+
"""
|
|
238
|
+
# Use same conversion logic
|
|
239
|
+
event_attr = attribute_value_from_python(value, time)
|
|
240
|
+
|
|
241
|
+
return ObjectAttributeValue(
|
|
242
|
+
name=event_attr.name,
|
|
243
|
+
value=event_attr.value,
|
|
244
|
+
time=event_attr.time
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ============================================================================
|
|
249
|
+
# Event ID Generation
|
|
250
|
+
# ============================================================================
|
|
251
|
+
|
|
252
|
+
def generate_event_id() -> str:
|
|
253
|
+
"""
|
|
254
|
+
Generate a unique event ID.
|
|
255
|
+
|
|
256
|
+
Simple implementation using counter. Could be enhanced with UUIDs
|
|
257
|
+
or more sophisticated schemes.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
A unique event identifier
|
|
261
|
+
"""
|
|
262
|
+
import uuid
|
|
263
|
+
return f"evt-{uuid.uuid4()}"
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def generate_object_id(obj: Any) -> str:
|
|
267
|
+
"""
|
|
268
|
+
Generate an object ID from an object.
|
|
269
|
+
|
|
270
|
+
Tries to use obj.id if available, otherwise generates a UUID.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
obj: The object to generate an ID for
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
A unique object identifier
|
|
277
|
+
"""
|
|
278
|
+
# Try to get id attribute
|
|
279
|
+
if hasattr(obj, 'id'):
|
|
280
|
+
return str(obj.id)
|
|
281
|
+
|
|
282
|
+
# Try Pydantic model's field
|
|
283
|
+
if hasattr(obj, '__dict__') and 'id' in obj.__dict__:
|
|
284
|
+
return str(obj.__dict__['id'])
|
|
285
|
+
|
|
286
|
+
# Generate UUID-based ID
|
|
287
|
+
import uuid
|
|
288
|
+
return f"obj-{uuid.uuid4()}"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Spores Transport Interface
|
|
4
|
+
|
|
5
|
+
Abstract base class for transporting encoded LogRecords.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Protocol, Callable, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Transport(Protocol):
|
|
15
|
+
"""
|
|
16
|
+
Protocol for transports that send encoded LogRecords.
|
|
17
|
+
|
|
18
|
+
Transports handle the actual delivery of log records to consumers
|
|
19
|
+
(e.g., HTTP POST, files, message queues, etc.).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
async def send(self, data: bytes, content_type: str) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Send encoded data to the transport destination.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
data: The encoded log record data
|
|
28
|
+
content_type: MIME content type (e.g., "application/json")
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
def is_async(self) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Return True if this transport is async (non-blocking).
|
|
35
|
+
|
|
36
|
+
Async transports don't block the caller when sending data.
|
|
37
|
+
They typically use background threads, queues, or async I/O.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
True if transport is async, False if synchronous
|
|
41
|
+
"""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
def close(self) -> None:
|
|
45
|
+
"""Close the transport and release resources."""
|
|
46
|
+
...
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mycorrhizal
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities and DSLs for modelling and implementing safe, performant, structured systems
|
|
5
|
+
Author-email: Jeff Ciesielski <jeffciesielski@gmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: pydantic>=2.11.7
|
|
8
|
+
Requires-Dist: typeguard==4.4.4
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Mycorrhizal
|
|
12
|
+
|
|
13
|
+
A Python library for building safe, structured, concurrent, event-driven systems.
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Mycorrhizal provides four domain-specific languages (DSLs) for modeling and implementing different aspects of complex systems:
|
|
18
|
+
|
|
19
|
+
* **Hypha** - Colored Petri nets for workflow modeling and orchestration
|
|
20
|
+
* **Rhizomorph** - Behavior trees for decision-making and control logic
|
|
21
|
+
* **Enoki** - Finite state machines for stateful components
|
|
22
|
+
* **Spores** - Event and object logging for observability and process mining
|
|
23
|
+
|
|
24
|
+
Each DSL can be used independently or combined to build sophisticated systems. All modules share common infrastructure for state management (blackboards) and time abstraction, enabling seamless composition.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install mycorrhizal
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Python 3.10 or later.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### Behavior Tree (Rhizomorph)
|
|
37
|
+
|
|
38
|
+
Define behavior trees using a decorator-based DSL:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from mycorrhizal.rhizomorph.core import bt, Runner, Status
|
|
42
|
+
|
|
43
|
+
@bt.tree
|
|
44
|
+
def ThreatResponse():
|
|
45
|
+
@bt.action
|
|
46
|
+
async def assess_threat(bb) -> Status:
|
|
47
|
+
# Analyze threat level
|
|
48
|
+
return Status.SUCCESS if bb.threat_level > 5 else Status.FAILURE
|
|
49
|
+
|
|
50
|
+
@bt.action
|
|
51
|
+
async def engage_countermeasures(bb) -> Status:
|
|
52
|
+
# Respond to threat
|
|
53
|
+
return Status.SUCCESS
|
|
54
|
+
|
|
55
|
+
@bt.root
|
|
56
|
+
@bt.sequence
|
|
57
|
+
def root():
|
|
58
|
+
yield assess_threat
|
|
59
|
+
yield engage_countermeasures
|
|
60
|
+
|
|
61
|
+
# Run the behavior tree
|
|
62
|
+
runner = Runner(ThreatResponse, bb=blackboard)
|
|
63
|
+
await runner.tick_until_complete()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Petri Net (Hypha)
|
|
67
|
+
|
|
68
|
+
Model workflows with colored Petri nets:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from mycorrhizal.hypha.core import pn, Runner, PlaceType
|
|
72
|
+
|
|
73
|
+
@pn.net
|
|
74
|
+
def ProcessingNet(builder):
|
|
75
|
+
# Define places
|
|
76
|
+
pending = builder.place("pending", type=PlaceType.QUEUE)
|
|
77
|
+
processed = builder.place("processed", type=PlaceType.QUEUE)
|
|
78
|
+
|
|
79
|
+
# Define transitions
|
|
80
|
+
@builder.transition()
|
|
81
|
+
async def process(consumed, bb, timebase):
|
|
82
|
+
for token in consumed:
|
|
83
|
+
result = await handle(token)
|
|
84
|
+
yield {processed: result}
|
|
85
|
+
|
|
86
|
+
# Wire the net
|
|
87
|
+
builder.arc(pending, process).arc(processed)
|
|
88
|
+
|
|
89
|
+
# Run the Petri net
|
|
90
|
+
runner = Runner(ProcessingNet, bb=blackboard)
|
|
91
|
+
await runner.start(timebase)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Finite State Machine (Enoki)
|
|
95
|
+
|
|
96
|
+
Build stateful components with FSMs:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from mycorrhizal.enoki.core import enoki, StateMachine, LabeledTransition
|
|
100
|
+
|
|
101
|
+
@enoki.state()
|
|
102
|
+
def IdleState():
|
|
103
|
+
@enoki.on_state
|
|
104
|
+
async def on_state(ctx):
|
|
105
|
+
if ctx.msg == "start":
|
|
106
|
+
return Events.START
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
@enoki.transitions
|
|
110
|
+
def transitions():
|
|
111
|
+
return [
|
|
112
|
+
LabeledTransition(Events.START, ProcessingState),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
# Create and run the FSM
|
|
116
|
+
fsm = StateMachine(initial_state=IdleState, common_data={})
|
|
117
|
+
await fsm.initialize()
|
|
118
|
+
fsm.send_message("start")
|
|
119
|
+
await fsm.tick()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Key Features
|
|
123
|
+
|
|
124
|
+
### Shared Infrastructure
|
|
125
|
+
|
|
126
|
+
All DSLs use common building blocks:
|
|
127
|
+
|
|
128
|
+
* **Blackboards** - Typed shared state using Pydantic models
|
|
129
|
+
* **Interfaces** - Decorator-based access control for blackboard fields
|
|
130
|
+
* **Timebase** - Abstract time for simulation and testing
|
|
131
|
+
|
|
132
|
+
### Composition
|
|
133
|
+
|
|
134
|
+
Combine DSLs to model complex systems:
|
|
135
|
+
|
|
136
|
+
* Embed behavior trees in Petri net transitions
|
|
137
|
+
* Run state machines within behavior tree actions
|
|
138
|
+
* Use Petri nets to orchestrate FSM-based components
|
|
139
|
+
|
|
140
|
+
### Observability
|
|
141
|
+
|
|
142
|
+
The Spores module provides OCEL-compliant logging:
|
|
143
|
+
|
|
144
|
+
* Automatic event extraction from DSL execution
|
|
145
|
+
* Object lifecycle tracking
|
|
146
|
+
* Transport layer for custom backends
|
|
147
|
+
|
|
148
|
+
## Examples
|
|
149
|
+
|
|
150
|
+
The repository contains comprehensive examples:
|
|
151
|
+
|
|
152
|
+
* `examples/hypha/` - Petri net patterns
|
|
153
|
+
* `examples/rhizomorph/` - Behavior tree patterns
|
|
154
|
+
* `examples/enoki/` - State machine patterns
|
|
155
|
+
* `examples/spores/` - Event logging integration
|
|
156
|
+
* `examples/interfaces/` - Type-safe blackboard access
|
|
157
|
+
|
|
158
|
+
Run examples with:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
uv run python examples/hypha/minimal_hypha_demo.py
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See [examples/README.md](examples/README.md) for a complete guide.
|
|
165
|
+
|
|
166
|
+
## Documentation
|
|
167
|
+
|
|
168
|
+
Full API documentation is available at [docs link here].
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Install dependencies
|
|
174
|
+
uv pip install -e ".[dev]"
|
|
175
|
+
|
|
176
|
+
# Run tests
|
|
177
|
+
pytest
|
|
178
|
+
|
|
179
|
+
# Run with coverage
|
|
180
|
+
pytest --cov=src/mycorrhizal --cov-report=html
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Project Status
|
|
184
|
+
|
|
185
|
+
This is a 0.1.0 release. The core APIs are stable and well-tested, but some features are still in development:
|
|
186
|
+
|
|
187
|
+
* Current: Four DSLs with decorator-based syntax
|
|
188
|
+
* Current: Comprehensive examples and tests
|
|
189
|
+
* Planned: Cross-DSL interoperability layer
|
|
190
|
+
* Planned: Enhanced composition patterns
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
195
|
+
|
|
196
|
+
## Author
|
|
197
|
+
|
|
198
|
+
Jeff Ciesielski
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
mycorrhizal/__init__.py,sha256=ajz1GSNU9xYVrFEDSz6Xwg7amWQ_yvW75tQa1ZvRIWc,3
|
|
2
|
+
mycorrhizal/common/__init__.py,sha256=vAL2NZFFVcoGoPlIREKRG0mrgx1prVTGTlWBopJ76Sw,1450
|
|
3
|
+
mycorrhizal/common/interface_builder.py,sha256=rBJzduqZ6EjD9MNGO669BcjkM9mYvsnMG0F1kd1wWaQ,6895
|
|
4
|
+
mycorrhizal/common/interfaces.py,sha256=fWw6bm2ejFmKUnl2pSTiSEwTP0Id8Kowg827_Mqir-s,12261
|
|
5
|
+
mycorrhizal/common/timebase.py,sha256=h8OymApwr_EvpRsfRJb6E5YosHg3jfqToOGAfMlL-Qw,2229
|
|
6
|
+
mycorrhizal/common/wrappers.py,sha256=hdj_1OgWe9RNqGlEkcklQ2j0FFn-AE9h5A55udYTr4o,17794
|
|
7
|
+
mycorrhizal/enoki/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
mycorrhizal/enoki/core.py,sha256=17oV8AGTI5aY299nAm3Jtc5iarImT2WPbMIwYE5LRsA,53697
|
|
9
|
+
mycorrhizal/enoki/testing_utils.py,sha256=S19p0nZ0UTfQm-hPHFAWqN9LAdfjL9uQKMFx_gaices,17307
|
|
10
|
+
mycorrhizal/enoki/util.py,sha256=oJ9vmHrbUdX99Rkh7bBeUHFzM5sS3yrtXOTQDA1524I,8434
|
|
11
|
+
mycorrhizal/hypha/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
mycorrhizal/hypha/util.py,sha256=tpvjWXTdGhtVUycQElcQg4usOy-tSEqwE9KEni8R30s,906
|
|
13
|
+
mycorrhizal/hypha/core/__init__.py,sha256=o6BE3fhRKjVfyIWwtNTO64kPt0udlZGyXr-ZuN_ZzF8,2518
|
|
14
|
+
mycorrhizal/hypha/core/builder.py,sha256=3oXG235bllLql8v2IiIa9K7ska_e4cHc8tvGGWwWZzA,15210
|
|
15
|
+
mycorrhizal/hypha/core/runtime.py,sha256=K7EDxPTYu9lyHoolBGrrOTws7f1xmbnU2SBVVLnoD3g,34848
|
|
16
|
+
mycorrhizal/hypha/core/specs.py,sha256=-MH4-ast3uxIWns3VpYV9vC_vqfjLCbvj61nbAOImn8,7882
|
|
17
|
+
mycorrhizal/rhizomorph/README.md,sha256=g-G8d00VgMOPriggsNveH7HqLhnJ9HlvoIzrwmUEy_M,8134
|
|
18
|
+
mycorrhizal/rhizomorph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
mycorrhizal/rhizomorph/core.py,sha256=BmgTbnzF2fzYhjQK4iAuO85LHCsN9-dKGok6U8bDv-g,58668
|
|
20
|
+
mycorrhizal/rhizomorph/util.py,sha256=dUgKAWX5RNtzFK01mGkz10tJyeTMCqyyfnZ8Mi97LkY,1280
|
|
21
|
+
mycorrhizal/spores/__init__.py,sha256=y-KPzgUMVXJKjClgQXmsykdWNe2g_U42pWRvdmmCqbg,2836
|
|
22
|
+
mycorrhizal/spores/cache.py,sha256=0FuWHKudgyC7vlrlcJ_kmRKr7OAJuC2gxRURReMlqF4,6119
|
|
23
|
+
mycorrhizal/spores/core.py,sha256=mfo1dQtDeyyYedIiaQ16YPhcBpIe-4wgzWXiMCjIj1I,12310
|
|
24
|
+
mycorrhizal/spores/extraction.py,sha256=YnUGABcVgZVSDCXyOQewWG62NAkdievJrXPIA_eJkxg,14918
|
|
25
|
+
mycorrhizal/spores/models.py,sha256=P5hlBaKmK6pCyLxBwGgPB4U9ef29_9fEoNEbYXJLV6g,7964
|
|
26
|
+
mycorrhizal/spores/dsl/__init__.py,sha256=bkxHTkaPrDWUlU1pjYlGJOM-772M5_k7R4aZ8HaxMNM,1153
|
|
27
|
+
mycorrhizal/spores/dsl/enoki.py,sha256=eVYNBp3ESZ7lK91vE8H5JWkXX14pOtf6cdAWSLiqAsE,15506
|
|
28
|
+
mycorrhizal/spores/dsl/hypha.py,sha256=vcJimWbZT2MmGhj8Lbn3izF1PO9HVJe1YQi5P3EiZ6o,11959
|
|
29
|
+
mycorrhizal/spores/dsl/rhizomorph.py,sha256=ChG2fRQD9DR90dLKLYSQR72asC-7eLccvBwZtz8C8kw,9898
|
|
30
|
+
mycorrhizal/spores/encoder/__init__.py,sha256=hpp1W4xwQUG1N1zdrea0coBPtMf-WYSE77Q780TinP8,204
|
|
31
|
+
mycorrhizal/spores/encoder/base.py,sha256=xFKk-f5_W_HNrOgNXyquUwhcrcz28LmeT5Y5enDVrwU,870
|
|
32
|
+
mycorrhizal/spores/encoder/json.py,sha256=dyQSTXfG0VQPSNt9ed8VEgMwxFUPnLtV6u_bSRuEnNY,4747
|
|
33
|
+
mycorrhizal/spores/transport/__init__.py,sha256=-QYmAwqaTbhrgEXr16qSYWx2f6J22d0SklPlm_Y8RWo,168
|
|
34
|
+
mycorrhizal/spores/transport/base.py,sha256=lJ2sLlXd-ZDeMn3ujSoYTneUB3Zwlz0mpWh5yxKK6Lw,1201
|
|
35
|
+
mycorrhizal-0.1.0.dist-info/METADATA,sha256=fKifHtnUMVeIKv13_kukZ6rtotKusA3m20VSXRr9qK0,4895
|
|
36
|
+
mycorrhizal-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
37
|
+
mycorrhizal-0.1.0.dist-info/RECORD,,
|