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
@@ -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
- - Enoki (State machines)
8
+ - Septum (State machines)
9
9
 
10
10
  Usage:
11
11
  ```python
12
- from mycorrhizal.spores.dsl import HyphaAdapter, RhizomorphAdapter, EnokiAdapter
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 Enoki (State machines)
31
- enoki_adapter = EnokiAdapter()
30
+ # For Septum (State machines)
31
+ septum_adapter = SeptumAdapter()
32
32
 
33
- @enoki.on_state
34
- @enoki_adapter.log_state(event_type="state_execute")
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 .enoki import EnokiAdapter
42
+ from .septum import SeptumAdapter
43
43
 
44
44
  __all__ = [
45
45
  'HyphaAdapter',
46
46
  'RhizomorphAdapter',
47
- 'EnokiAdapter',
47
+ 'SeptumAdapter',
48
48
  ]
@@ -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"] = EventAttributeValue(
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"] = EventAttributeValue(
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": EventAttributeValue(
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
- EventAttributeValue,
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"] = EventAttributeValue(
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"] = EventAttributeValue(
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)
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Spores Adapter for Enoki (State Machines)
3
+ Spores Adapter for Septum (State Machines)
4
4
 
5
- Provides logging integration for Enoki state machines.
5
+ Provides logging integration for Septum state machines.
6
6
  Extracts context information and automatically creates event/object logs.
7
7
  """
8
8
 
@@ -22,21 +22,22 @@ from ...spores import (
22
22
  Relationship,
23
23
  EventAttributeValue,
24
24
  generate_event_id,
25
+ attribute_value_from_python,
25
26
  )
26
27
  from ...spores.extraction import (
27
28
  extract_attributes_from_blackboard,
28
29
  extract_objects_from_blackboard,
29
30
  )
30
31
  from ...spores.core import _send_log_record, get_object_cache
31
- from ...enoki.core import SharedContext, TransitionType
32
+ from ...septum.core import SharedContext, TransitionType
32
33
 
33
34
 
34
35
  logger = logging.getLogger(__name__)
35
36
 
36
37
 
37
- class EnokiAdapter:
38
+ class SeptumAdapter:
38
39
  """
39
- Adapter for Enoki state machine logging.
40
+ Adapter for Septum state machine logging.
40
41
 
41
42
  Provides decorators and helpers for logging:
42
43
  - State entry/exit/timeout events
@@ -46,13 +47,13 @@ class EnokiAdapter:
46
47
 
47
48
  Usage:
48
49
  ```python
49
- from mycorrhizal.spores.dsl import EnokiAdapter
50
+ from mycorrhizal.spores.dsl import SeptumAdapter
50
51
 
51
- adapter = EnokiAdapter()
52
+ adapter = SeptumAdapter()
52
53
 
53
- @enoki.state()
54
+ @septum.state()
54
55
  def MyState():
55
- @enoki.on_state
56
+ @septum.on_state
56
57
  @adapter.log_state(event_type="state_execute")
57
58
  async def on_state(ctx: SharedContext):
58
59
  # Event automatically logged with:
@@ -61,7 +62,7 @@ class EnokiAdapter:
61
62
  # - relationships to objects (with ObjectRef annotations)
62
63
  return Events.DONE
63
64
 
64
- @enoki.on_enter
65
+ @septum.on_enter
65
66
  @adapter.log_state_lifecycle(event_type="state_enter")
66
67
  async def on_enter(ctx: SharedContext):
67
68
  pass
@@ -69,7 +70,7 @@ class EnokiAdapter:
69
70
  """
70
71
 
71
72
  def __init__(self):
72
- """Initialize the Enoki adapter."""
73
+ """Initialize the Septum adapter."""
73
74
  self._enabled = True
74
75
 
75
76
  def enable(self):
@@ -88,7 +89,7 @@ class EnokiAdapter:
88
89
  log_transition: bool = True,
89
90
  ) -> Callable:
90
91
  """
91
- Decorator to log Enoki state execution.
92
+ Decorator to log Septum state execution.
92
93
 
93
94
  Automatically captures:
94
95
  - State name
@@ -214,19 +215,11 @@ async def _log_state_event(
214
215
  if hasattr(ctx, 'current_state') and ctx.current_state:
215
216
  state_name = getattr(ctx.current_state, 'name', state_name)
216
217
 
217
- event_attrs["state_name"] = EventAttributeValue(
218
- name="state_name",
219
- value=state_name,
220
- time=timestamp
221
- )
218
+ event_attrs["state_name"] = attribute_value_from_python(state_name)
222
219
 
223
220
  # Add transition result if requested
224
221
  if log_transition and isinstance(result, TransitionType):
225
- event_attrs["transition"] = EventAttributeValue(
226
- name="transition",
227
- value=result.name,
228
- time=timestamp
229
- )
222
+ event_attrs["transition"] = attribute_value_from_python(result.name)
230
223
 
231
224
  # Extract from ctx.common (blackboard)
232
225
  if hasattr(ctx, 'common') and ctx.common:
@@ -235,11 +228,7 @@ async def _log_state_event(
235
228
 
236
229
  # Add message if present
237
230
  if hasattr(ctx, 'msg') and ctx.msg is not None:
238
- event_attrs["message_type"] = EventAttributeValue(
239
- name="message_type",
240
- value=type(ctx.msg).__name__,
241
- time=timestamp
242
- )
231
+ event_attrs["message_type"] = attribute_value_from_python(type(ctx.msg).__name__)
243
232
 
244
233
  # Extract objects from ctx.common
245
234
  relationships = {}
@@ -293,16 +282,8 @@ async def _log_lifecycle_event(
293
282
 
294
283
  # Build event attributes
295
284
  event_attrs = {
296
- "lifecycle_method": EventAttributeValue(
297
- name="lifecycle_method",
298
- value=func.__name__,
299
- time=timestamp
300
- ),
301
- "phase": EventAttributeValue(
302
- name="phase",
303
- value=phase,
304
- time=timestamp
305
- )
285
+ "lifecycle_method": attribute_value_from_python(func.__name__),
286
+ "phase": attribute_value_from_python(phase)
306
287
  }
307
288
 
308
289
  # Add state name
@@ -310,11 +291,7 @@ async def _log_lifecycle_event(
310
291
  if hasattr(ctx, 'current_state') and ctx.current_state:
311
292
  state_name = getattr(ctx.current_state, 'name', state_name)
312
293
 
313
- event_attrs["state_name"] = EventAttributeValue(
314
- name="state_name",
315
- value=state_name,
316
- time=timestamp
317
- )
294
+ event_attrs["state_name"] = attribute_value_from_python(state_name)
318
295
 
319
296
  # Add static attributes
320
297
  if attributes:
@@ -322,17 +299,9 @@ async def _log_lifecycle_event(
322
299
  if callable(value):
323
300
  if hasattr(ctx, 'common') and ctx.common:
324
301
  result = value(ctx.common)
325
- event_attrs[key] = EventAttributeValue(
326
- name=key,
327
- value=str(result),
328
- time=timestamp
329
- )
302
+ event_attrs[key] = attribute_value_from_python(str(result))
330
303
  else:
331
- event_attrs[key] = EventAttributeValue(
332
- name=key,
333
- value=str(value),
334
- time=timestamp
335
- )
304
+ event_attrs[key] = attribute_value_from_python(str(value))
336
305
 
337
306
  # Extract from ctx.common
338
307
  if hasattr(ctx, 'common') and ctx.common:
@@ -435,26 +404,14 @@ async def _log_message_event(
435
404
 
436
405
  # Build event attributes
437
406
  event_attrs = {
438
- "message_type": EventAttributeValue(
439
- name="message_type",
440
- value=type(ctx.msg).__name__,
441
- time=timestamp
442
- ),
443
- "handler": EventAttributeValue(
444
- name="handler",
445
- value=func.__name__,
446
- time=timestamp
447
- )
407
+ "message_type": attribute_value_from_python(type(ctx.msg).__name__),
408
+ "handler": attribute_value_from_python(func.__name__)
448
409
  }
449
410
 
450
411
  # Add state name
451
412
  if hasattr(ctx, 'current_state') and ctx.current_state:
452
413
  state_name = getattr(ctx.current_state, 'name', 'unknown')
453
- event_attrs["state_name"] = EventAttributeValue(
454
- name="state_name",
455
- value=state_name,
456
- time=timestamp
457
- )
414
+ event_attrs["state_name"] = attribute_value_from_python(state_name)
458
415
 
459
416
  # Add static attributes
460
417
  if attributes:
@@ -462,17 +419,9 @@ async def _log_message_event(
462
419
  if callable(value):
463
420
  if hasattr(ctx, 'common') and ctx.common:
464
421
  result = value(ctx.common)
465
- event_attrs[key] = EventAttributeValue(
466
- name=key,
467
- value=str(result),
468
- time=timestamp
469
- )
422
+ event_attrs[key] = attribute_value_from_python(str(result))
470
423
  else:
471
- event_attrs[key] = EventAttributeValue(
472
- name=key,
473
- value=str(value),
474
- time=timestamp
475
- )
424
+ event_attrs[key] = attribute_value_from_python(str(value))
476
425
 
477
426
  # Extract from ctx.common
478
427
  if hasattr(ctx, 'common') and ctx.common:
@@ -27,23 +27,26 @@ class JSONEncoder(Encoder):
27
27
  "event": {
28
28
  "id": "evt-123",
29
29
  "type": "process_item",
30
- "time": "2025-01-11T12:34:56.789Z",
30
+ "activity": null,
31
+ "time": "2025-01-11T12:34:56.789000000Z",
31
32
  "attributes": [
32
- {"name": "priority", "value": "high", "time": "2025-01-11T12:34:56.789Z"}
33
+ {"name": "priority", "value": "high", "type": "string", "time": null}
33
34
  ],
34
35
  "relationships": [
35
36
  {"objectId": "obj-456", "qualifier": "input"}
36
37
  ]
37
- }
38
+ },
39
+ "object": null
38
40
  }
39
41
 
40
42
  Or for objects:
41
43
  {
44
+ "event": null,
42
45
  "object": {
43
46
  "id": "obj-456",
44
47
  "type": "WorkItem",
45
48
  "attributes": [
46
- {"name": "status", "value": "pending", "time": "2025-01-11T12:34:56.789Z"}
49
+ {"name": "status", "value": "pending", "type": "string", "time": null}
47
50
  ],
48
51
  "relationships": []
49
52
  }
@@ -60,11 +63,11 @@ class JSONEncoder(Encoder):
60
63
  Returns:
61
64
  JSON-encoded bytes
62
65
  """
63
- # Convert LogRecord to dict
66
+ # Convert LogRecord to dict with union structure (both fields, one null)
64
67
  if record.event is not None:
65
- data = {"event": self._event_to_dict(record.event)}
68
+ data = {"event": self._event_to_dict(record.event), "object": None}
66
69
  else:
67
- data = {"object": self._object_to_dict(record.object)}
70
+ data = {"event": None, "object": self._object_to_dict(record.object)}
68
71
 
69
72
  # Encode to JSON
70
73
  return json.dumps(data, separators=(',', ':')).encode('utf-8')
@@ -78,6 +81,7 @@ class JSONEncoder(Encoder):
78
81
  return {
79
82
  "id": event.id,
80
83
  "type": event.type,
84
+ "activity": event.activity,
81
85
  "time": self._format_datetime(event.time),
82
86
  "attributes": [
83
87
  self._event_attr_to_dict(name, attr)
@@ -104,21 +108,26 @@ class JSONEncoder(Encoder):
104
108
  ]
105
109
  }
106
110
 
107
- def _event_attr_to_dict(self, name: str, attr: EventAttributeValue) -> Dict[str, str]:
111
+ def _event_attr_to_dict(self, name: str, attr: EventAttributeValue) -> Dict[str, Any]:
108
112
  """Convert EventAttributeValue to dict."""
109
113
  return {
110
114
  "name": attr.name or name,
111
115
  "value": attr.value,
112
- "time": self._format_datetime(attr.time)
116
+ "type": attr.type
113
117
  }
114
118
 
115
- def _object_attr_to_dict(self, name: str, attr: ObjectAttributeValue) -> Dict[str, str]:
119
+ def _object_attr_to_dict(self, name: str, attr: ObjectAttributeValue) -> Dict[str, Any]:
116
120
  """Convert ObjectAttributeValue to dict."""
117
- return {
121
+ result = {
118
122
  "name": attr.name or name,
119
123
  "value": attr.value,
120
- "time": self._format_datetime(attr.time)
124
+ "type": attr.type
121
125
  }
126
+ if attr.time is not None:
127
+ result["time"] = self._format_datetime(attr.time)
128
+ else:
129
+ result["time"] = None
130
+ return result
122
131
 
123
132
  def _relationship_to_dict(self, qualifier: str, rel: Relationship) -> Dict[str, str]:
124
133
  """Convert Relationship to dict."""
@@ -41,7 +41,7 @@ def extract_attributes_from_params(
41
41
  func: The decorated function
42
42
  args: Positional arguments passed to function
43
43
  kwargs: Keyword arguments passed to function
44
- timestamp: Event timestamp for attribute values
44
+ timestamp: Event timestamp (not used for attribute timestamps)
45
45
 
46
46
  Returns:
47
47
  Dictionary of attribute name -> EventAttributeValue
@@ -60,8 +60,8 @@ def extract_attributes_from_params(
60
60
  if param_name in ('self', 'cls', 'bb', 'ctx', 'timebase', 'consumed'):
61
61
  continue
62
62
 
63
- # Extract attribute value
64
- attr = attribute_value_from_python(param_value, timestamp)
63
+ # Extract attribute value (time=None for event attributes)
64
+ attr = attribute_value_from_python(param_value)
65
65
  attributes[param_name] = attr
66
66
 
67
67
  except Exception as e:
@@ -79,7 +79,7 @@ def extract_attributes_from_dict(
79
79
 
80
80
  Args:
81
81
  data: Dictionary with attribute values
82
- timestamp: Event timestamp for attribute values
82
+ timestamp: Event timestamp (not used for attribute timestamps)
83
83
 
84
84
  Returns:
85
85
  Dictionary of attribute name -> EventAttributeValue
@@ -87,7 +87,7 @@ def extract_attributes_from_dict(
87
87
  attributes = {}
88
88
 
89
89
  for key, value in data.items():
90
- attr = attribute_value_from_python(value, timestamp)
90
+ attr = attribute_value_from_python(value)
91
91
  attributes[key] = attr
92
92
 
93
93
  return attributes
@@ -102,7 +102,7 @@ def extract_attributes_from_blackboard(
102
102
 
103
103
  Args:
104
104
  bb: Blackboard object
105
- timestamp: Event timestamp for attribute values
105
+ timestamp: Event timestamp (not used for attribute timestamps)
106
106
 
107
107
  Returns:
108
108
  Dictionary of attribute name -> EventAttributeValue
@@ -136,7 +136,7 @@ def extract_attributes_from_blackboard(
136
136
  attr_name = metadata.name or field_name
137
137
  else:
138
138
  attr_name = field_name
139
- attr = attribute_value_from_python(value, timestamp)
139
+ attr = attribute_value_from_python(value)
140
140
  attributes[attr_name] = attr
141
141
 
142
142
  except Exception as e:
@@ -168,13 +168,13 @@ def evaluate_computed_attributes(
168
168
  # Call the function with blackboard
169
169
  try:
170
170
  result = value(bb)
171
- attr = attribute_value_from_python(result, timestamp)
171
+ attr = attribute_value_from_python(result)
172
172
  attributes[key] = attr
173
173
  except Exception as e:
174
174
  logger.error(f"Failed to compute attribute {key}: {e}")
175
175
  else:
176
176
  # Static value
177
- attr = attribute_value_from_python(value, timestamp)
177
+ attr = attribute_value_from_python(value)
178
178
  attributes[key] = attr
179
179
 
180
180
  return attributes
@@ -289,6 +289,7 @@ def convert_to_ocel_object(
289
289
  attr = attr.__class__(
290
290
  name=key,
291
291
  value=attr.value,
292
+ type=attr.type,
292
293
  time=attr.time
293
294
  )
294
295
  attributes[key] = attr
@@ -302,6 +303,7 @@ def convert_to_ocel_object(
302
303
  attr = attr.__class__(
303
304
  name=key,
304
305
  value=attr.value,
306
+ type=attr.type,
305
307
  time=attr.time
306
308
  )
307
309
  attributes[key] = attr
@@ -359,6 +361,7 @@ def extract_objects_from_spec(
359
361
  attr = attr.__class__(
360
362
  name=key,
361
363
  value=attr.value,
364
+ type=attr.type,
362
365
  time=attr.time
363
366
  )
364
367
  attributes[key] = attr
@@ -447,12 +450,12 @@ def extract_from_rhizomorph_context(
447
450
  return attributes, objects
448
451
 
449
452
 
450
- def extract_from_enoki_context(
453
+ def extract_from_septum_context(
451
454
  ctx: Any,
452
455
  timestamp: datetime
453
456
  ) -> tuple[Dict[str, EventAttributeValue], List[Object]]:
454
457
  """
455
- Extract attributes and objects from Enoki state context.
458
+ Extract attributes and objects from Septum state context.
456
459
 
457
460
  Args:
458
461
  ctx: SharedContext