mycorrhizal 0.1.2__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.
- mycorrhizal/_version.py +1 -1
- mycorrhizal/common/__init__.py +15 -3
- mycorrhizal/common/cache.py +114 -0
- mycorrhizal/common/compilation.py +263 -0
- mycorrhizal/common/interface_detection.py +159 -0
- mycorrhizal/common/interfaces.py +3 -50
- mycorrhizal/common/mermaid.py +124 -0
- mycorrhizal/common/wrappers.py +1 -1
- mycorrhizal/hypha/core/builder.py +11 -1
- mycorrhizal/hypha/core/runtime.py +242 -107
- mycorrhizal/mycelium/__init__.py +174 -0
- mycorrhizal/mycelium/core.py +619 -0
- mycorrhizal/mycelium/exceptions.py +30 -0
- mycorrhizal/mycelium/hypha_bridge.py +1143 -0
- mycorrhizal/mycelium/instance.py +440 -0
- mycorrhizal/mycelium/pn_context.py +276 -0
- mycorrhizal/mycelium/runner.py +165 -0
- mycorrhizal/mycelium/spores_integration.py +655 -0
- mycorrhizal/mycelium/tree_builder.py +102 -0
- mycorrhizal/mycelium/tree_spec.py +197 -0
- mycorrhizal/rhizomorph/README.md +82 -33
- mycorrhizal/rhizomorph/core.py +287 -119
- mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
- mycorrhizal/{enoki → septum}/core.py +326 -100
- mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
- mycorrhizal/{enoki → septum}/util.py +44 -21
- mycorrhizal/spores/__init__.py +3 -3
- mycorrhizal/spores/core.py +149 -28
- mycorrhizal/spores/dsl/__init__.py +8 -8
- mycorrhizal/spores/dsl/hypha.py +3 -15
- mycorrhizal/spores/dsl/rhizomorph.py +3 -11
- mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
- mycorrhizal/spores/encoder/json.py +21 -12
- mycorrhizal/spores/extraction.py +14 -11
- mycorrhizal/spores/models.py +53 -20
- mycorrhizal-0.2.0.dist-info/METADATA +335 -0
- mycorrhizal-0.2.0.dist-info/RECORD +54 -0
- mycorrhizal-0.1.2.dist-info/METADATA +0 -198
- mycorrhizal-0.1.2.dist-info/RECORD +0 -39
- /mycorrhizal/{enoki → septum}/__init__.py +0 -0
- {mycorrhizal-0.1.2.dist-info → mycorrhizal-0.2.0.dist-info}/WHEEL +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Spores Adapter for
|
|
3
|
+
Spores Adapter for Septum (State Machines)
|
|
4
4
|
|
|
5
|
-
Provides logging integration for
|
|
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 ...
|
|
32
|
+
from ...septum.core import SharedContext, TransitionType
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
logger = logging.getLogger(__name__)
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
class
|
|
38
|
+
class SeptumAdapter:
|
|
38
39
|
"""
|
|
39
|
-
Adapter for
|
|
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
|
|
50
|
+
from mycorrhizal.spores.dsl import SeptumAdapter
|
|
50
51
|
|
|
51
|
-
adapter =
|
|
52
|
+
adapter = SeptumAdapter()
|
|
52
53
|
|
|
53
|
-
@
|
|
54
|
+
@septum.state()
|
|
54
55
|
def MyState():
|
|
55
|
-
@
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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"] =
|
|
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"] =
|
|
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"] =
|
|
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":
|
|
297
|
-
|
|
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"] =
|
|
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] =
|
|
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] =
|
|
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":
|
|
439
|
-
|
|
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"] =
|
|
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] =
|
|
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] =
|
|
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
|
-
"
|
|
30
|
+
"activity": null,
|
|
31
|
+
"time": "2025-01-11T12:34:56.789000000Z",
|
|
31
32
|
"attributes": [
|
|
32
|
-
{"name": "priority", "value": "high", "
|
|
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", "
|
|
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,
|
|
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
|
-
"
|
|
116
|
+
"type": attr.type
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
def _object_attr_to_dict(self, name: str, attr: ObjectAttributeValue) -> Dict[str,
|
|
119
|
+
def _object_attr_to_dict(self, name: str, attr: ObjectAttributeValue) -> Dict[str, Any]:
|
|
116
120
|
"""Convert ObjectAttributeValue to dict."""
|
|
117
|
-
|
|
121
|
+
result = {
|
|
118
122
|
"name": attr.name or name,
|
|
119
123
|
"value": attr.value,
|
|
120
|
-
"
|
|
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."""
|
mycorrhizal/spores/extraction.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
458
|
+
Extract attributes and objects from Septum state context.
|
|
456
459
|
|
|
457
460
|
Args:
|
|
458
461
|
ctx: SharedContext
|
mycorrhizal/spores/models.py
CHANGED
|
@@ -35,16 +35,16 @@ class EventAttributeValue:
|
|
|
35
35
|
"""
|
|
36
36
|
An attribute value for an event.
|
|
37
37
|
|
|
38
|
-
|
|
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
|
-
|
|
43
|
+
type: The data type ("string", "integer", "float", "boolean", "timestamp")
|
|
44
44
|
"""
|
|
45
45
|
name: str
|
|
46
46
|
value: str
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
286
|
+
An ObjectAttributeValue with type information
|
|
259
287
|
"""
|
|
260
|
-
# Use same
|
|
261
|
-
event_attr = attribute_value_from_python(value
|
|
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
|
-
|
|
298
|
+
type=event_attr.type,
|
|
299
|
+
time=time
|
|
267
300
|
)
|
|
268
301
|
|
|
269
302
|
|