mycorrhizal 0.1.2__py3-none-any.whl → 0.2.1__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 (41) hide show
  1. mycorrhizal/_version.py +1 -1
  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 +11 -1
  10. mycorrhizal/hypha/core/runtime.py +242 -107
  11. mycorrhizal/mycelium/__init__.py +174 -0
  12. mycorrhizal/mycelium/core.py +619 -0
  13. mycorrhizal/mycelium/exceptions.py +30 -0
  14. mycorrhizal/mycelium/hypha_bridge.py +1143 -0
  15. mycorrhizal/mycelium/instance.py +440 -0
  16. mycorrhizal/mycelium/pn_context.py +276 -0
  17. mycorrhizal/mycelium/runner.py +165 -0
  18. mycorrhizal/mycelium/spores_integration.py +655 -0
  19. mycorrhizal/mycelium/tree_builder.py +102 -0
  20. mycorrhizal/mycelium/tree_spec.py +197 -0
  21. mycorrhizal/rhizomorph/README.md +82 -33
  22. mycorrhizal/rhizomorph/core.py +287 -119
  23. mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
  24. mycorrhizal/{enoki → septum}/core.py +326 -100
  25. mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
  26. mycorrhizal/{enoki → septum}/util.py +44 -21
  27. mycorrhizal/spores/__init__.py +3 -3
  28. mycorrhizal/spores/core.py +149 -28
  29. mycorrhizal/spores/dsl/__init__.py +8 -8
  30. mycorrhizal/spores/dsl/hypha.py +3 -15
  31. mycorrhizal/spores/dsl/rhizomorph.py +3 -11
  32. mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
  33. mycorrhizal/spores/encoder/json.py +21 -12
  34. mycorrhizal/spores/extraction.py +14 -11
  35. mycorrhizal/spores/models.py +53 -20
  36. mycorrhizal-0.2.1.dist-info/METADATA +335 -0
  37. mycorrhizal-0.2.1.dist-info/RECORD +54 -0
  38. mycorrhizal-0.1.2.dist-info/METADATA +0 -198
  39. mycorrhizal-0.1.2.dist-info/RECORD +0 -39
  40. /mycorrhizal/{enoki → septum}/__init__.py +0 -0
  41. {mycorrhizal-0.1.2.dist-info → mycorrhizal-0.2.1.dist-info}/WHEEL +0 -0
@@ -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
@@ -35,16 +35,16 @@ class EventAttributeValue:
35
35
  """
36
36
  An attribute value for an event.
37
37
 
38
- OCEL attributes include timestamps for versioning.
38
+ Event attributes don't have timestamps because the event time itself is sufficient.
39
39
 
40
40
  Attributes:
41
41
  name: The attribute name
42
42
  value: The string representation of the value
43
- time: When this attribute value was set
43
+ type: The data type ("string", "integer", "float", "boolean", "timestamp")
44
44
  """
45
45
  name: str
46
46
  value: str
47
- time: datetime
47
+ type: str
48
48
 
49
49
 
50
50
  @dataclass(frozen=True)
@@ -57,11 +57,13 @@ class ObjectAttributeValue:
57
57
  Attributes:
58
58
  name: The attribute name
59
59
  value: The string representation of the value
60
+ type: The data type ("string", "integer", "float", "boolean", "timestamp")
60
61
  time: When this attribute value was set
61
62
  """
62
63
  name: str
63
64
  value: str
64
- time: datetime
65
+ type: str
66
+ time: Optional[datetime] = None
65
67
 
66
68
 
67
69
  @dataclass
@@ -71,14 +73,16 @@ class Event:
71
73
 
72
74
  Attributes:
73
75
  id: Unique event identifier
74
- type: Event type/category
76
+ type: Event type/category (event class)
77
+ activity: Optional semantic activity label (defaults to type if null)
75
78
  time: When the event occurred
76
79
  attributes: Event attributes (name -> EventAttributeValue)
77
80
  relationships: Objects related to this event (qualifier -> Relationship)
78
81
  """
79
82
  id: str
80
83
  type: str
81
- time: datetime
84
+ activity: Optional[str] = None
85
+ time: datetime = field(default_factory=datetime.now)
82
86
  attributes: Dict[str, EventAttributeValue] = field(default_factory=dict)
83
87
  relationships: Dict[str, Relationship] = field(default_factory=dict)
84
88
 
@@ -196,23 +200,49 @@ class SporesAttr:
196
200
  name: Optional[str] = None
197
201
 
198
202
 
203
+ # ============================================================================
204
+ # Type Inference
205
+ # ============================================================================
206
+
207
+ def infer_type(value: Any) -> str:
208
+ """
209
+ Infer the OCEL type for a Python value.
210
+
211
+ Args:
212
+ value: The Python value to infer type for
213
+
214
+ Returns:
215
+ One of: "string", "integer", "float", "boolean", "timestamp"
216
+ """
217
+ if isinstance(value, bool):
218
+ return "boolean"
219
+ elif isinstance(value, int):
220
+ return "integer"
221
+ elif isinstance(value, float):
222
+ return "float"
223
+ elif isinstance(value, datetime):
224
+ return "timestamp"
225
+ else:
226
+ return "string"
227
+
228
+
199
229
  # ============================================================================
200
230
  # Attribute Value Conversion
201
231
  # ============================================================================
202
232
 
203
- def attribute_value_from_python(value: Any, time: datetime) -> EventAttributeValue:
233
+ def attribute_value_from_python(value: Any) -> EventAttributeValue:
204
234
  """
205
235
  Convert a Python value to an OCEL EventAttributeValue.
206
236
 
207
- All values are converted to strings per OCEL specification.
208
-
209
237
  Args:
210
238
  value: The Python value to convert
211
- time: The timestamp for this attribute value
212
239
 
213
240
  Returns:
214
- An EventAttributeValue with string representation
241
+ An EventAttributeValue with type information
215
242
  """
243
+ # Infer type
244
+ attr_type = infer_type(value)
245
+
216
246
  # Handle None
217
247
  if value is None:
218
248
  str_value = "null"
@@ -240,30 +270,33 @@ def attribute_value_from_python(value: Any, time: datetime) -> EventAttributeVal
240
270
  return EventAttributeValue(
241
271
  name="", # Name set by caller
242
272
  value=str_value,
243
- time=time
273
+ type=attr_type
244
274
  )
245
275
 
246
276
 
247
- def object_attribute_from_python(value: Any, time: datetime) -> ObjectAttributeValue:
277
+ def object_attribute_from_python(value: Any, time: Optional[datetime] = None) -> ObjectAttributeValue:
248
278
  """
249
279
  Convert a Python value to an OCEL ObjectAttributeValue.
250
280
 
251
- Same conversion rules as attribute_value_from_python but for objects.
252
-
253
281
  Args:
254
282
  value: The Python value to convert
255
- time: The timestamp for this attribute value
283
+ time: The timestamp for this attribute value (optional)
256
284
 
257
285
  Returns:
258
- An ObjectAttributeValue with string representation
286
+ An ObjectAttributeValue with type information
259
287
  """
260
- # Use same conversion logic
261
- event_attr = attribute_value_from_python(value, time)
288
+ # Use same type inference logic
289
+ event_attr = attribute_value_from_python(value)
290
+
291
+ # Default to current time if not provided
292
+ if time is None:
293
+ time = datetime.now()
262
294
 
263
295
  return ObjectAttributeValue(
264
296
  name=event_attr.name,
265
297
  value=event_attr.value,
266
- time=event_attr.time
298
+ type=event_attr.type,
299
+ time=time
267
300
  )
268
301
 
269
302