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,399 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Spores Adapter for Hypha (Petri Nets)
|
|
4
|
+
|
|
5
|
+
Provides logging integration for Hypha transitions and places.
|
|
6
|
+
Extracts token information and automatically creates event/object logs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import functools
|
|
13
|
+
import inspect
|
|
14
|
+
import logging
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
17
|
+
|
|
18
|
+
from ...spores import (
|
|
19
|
+
spore,
|
|
20
|
+
get_config,
|
|
21
|
+
Event,
|
|
22
|
+
Object,
|
|
23
|
+
LogRecord,
|
|
24
|
+
Relationship,
|
|
25
|
+
EventAttributeValue,
|
|
26
|
+
ObjectAttributeValue,
|
|
27
|
+
generate_event_id,
|
|
28
|
+
generate_object_id,
|
|
29
|
+
attribute_value_from_python,
|
|
30
|
+
object_attribute_from_python,
|
|
31
|
+
)
|
|
32
|
+
from ...spores.extraction import (
|
|
33
|
+
extract_attributes_from_blackboard,
|
|
34
|
+
extract_objects_from_blackboard,
|
|
35
|
+
convert_to_ocel_object,
|
|
36
|
+
)
|
|
37
|
+
from ...spores.core import _send_log_record, get_object_cache
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class HyphaAdapter:
|
|
44
|
+
"""
|
|
45
|
+
Adapter for Hypha Petri net logging.
|
|
46
|
+
|
|
47
|
+
Provides decorators and helpers for logging:
|
|
48
|
+
- Transition execution with consumed/produced tokens
|
|
49
|
+
- Place token arrivals/departures
|
|
50
|
+
- Token object lifecycle tracking
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
```python
|
|
54
|
+
from mycorrhizal.spores.dsl import HyphaAdapter
|
|
55
|
+
|
|
56
|
+
adapter = HyphaAdapter()
|
|
57
|
+
|
|
58
|
+
@pn.net
|
|
59
|
+
def MyNet(builder):
|
|
60
|
+
@builder.transition()
|
|
61
|
+
@adapter.log_transition(event_type="process_item")
|
|
62
|
+
async def process(consumed, bb, timebase):
|
|
63
|
+
# Event automatically logged with:
|
|
64
|
+
# - token_count attribute
|
|
65
|
+
# - relationships to consumed tokens
|
|
66
|
+
yield {output: consumed[0]}
|
|
67
|
+
|
|
68
|
+
@builder.place(type=PlaceType.QUEUE)
|
|
69
|
+
@adapter.log_place(event_type="item_arrived")
|
|
70
|
+
def input_place(bb):
|
|
71
|
+
return None
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
"""Initialize the Hypha adapter."""
|
|
77
|
+
self._enabled = True
|
|
78
|
+
|
|
79
|
+
def enable(self):
|
|
80
|
+
"""Enable logging for this adapter."""
|
|
81
|
+
self._enabled = True
|
|
82
|
+
|
|
83
|
+
def disable(self):
|
|
84
|
+
"""Disable logging for this adapter."""
|
|
85
|
+
self._enabled = False
|
|
86
|
+
|
|
87
|
+
def log_transition(
|
|
88
|
+
self,
|
|
89
|
+
event_type: str,
|
|
90
|
+
attributes: Optional[Union[Dict[str, Any], List[str]]] = None,
|
|
91
|
+
log_inputs: bool = True,
|
|
92
|
+
log_outputs: bool = False,
|
|
93
|
+
) -> Callable:
|
|
94
|
+
"""
|
|
95
|
+
Decorator to log Hypha transition execution.
|
|
96
|
+
|
|
97
|
+
Automatically captures:
|
|
98
|
+
- Token count from consumed tokens
|
|
99
|
+
- Relationships to consumed/produced tokens
|
|
100
|
+
- Attributes from blackboard (if specified)
|
|
101
|
+
- Objects from blackboard with ObjectRef metadata
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
event_type: Type of event to log
|
|
105
|
+
attributes: Static attributes or param names to extract
|
|
106
|
+
log_inputs: Whether to log input tokens as related objects
|
|
107
|
+
log_outputs: Whether to log output tokens as related objects
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Decorator function
|
|
111
|
+
"""
|
|
112
|
+
def decorator(func: Callable) -> Callable:
|
|
113
|
+
@functools.wraps(func)
|
|
114
|
+
async def async_wrapper(consumed: List[Any], bb: Any, timebase: Any, state: Any = None):
|
|
115
|
+
# Call original transition
|
|
116
|
+
if state is not None:
|
|
117
|
+
result = func(consumed, bb, timebase, state)
|
|
118
|
+
else:
|
|
119
|
+
result = func(consumed, bb, timebase)
|
|
120
|
+
|
|
121
|
+
# Handle async generator
|
|
122
|
+
if inspect.isasyncgen(result):
|
|
123
|
+
# Log before processing
|
|
124
|
+
await _log_transition_event(
|
|
125
|
+
func, consumed, bb, timebase, event_type, attributes, log_inputs, None
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Collect outputs
|
|
129
|
+
outputs = []
|
|
130
|
+
async for yielded in result:
|
|
131
|
+
outputs.append(yielded)
|
|
132
|
+
yield yielded
|
|
133
|
+
|
|
134
|
+
# Log outputs if requested
|
|
135
|
+
if log_outputs:
|
|
136
|
+
await _log_outputs(func, outputs, bb, timebase, event_type)
|
|
137
|
+
else:
|
|
138
|
+
# Handle coroutine
|
|
139
|
+
result = await result
|
|
140
|
+
|
|
141
|
+
# Log event
|
|
142
|
+
await _log_transition_event(
|
|
143
|
+
func, consumed, bb, timebase, event_type, attributes, log_inputs, result
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Yield the result if it's not None
|
|
147
|
+
if result is not None:
|
|
148
|
+
yield result
|
|
149
|
+
|
|
150
|
+
@functools.wraps(func)
|
|
151
|
+
def sync_wrapper(consumed: List[Any], bb: Any, timebase: Any, state: Any = None):
|
|
152
|
+
# For sync transitions, log asynchronously
|
|
153
|
+
if state is not None:
|
|
154
|
+
result = func(consumed, bb, timebase, state)
|
|
155
|
+
else:
|
|
156
|
+
result = func(consumed, bb, timebase)
|
|
157
|
+
|
|
158
|
+
# Schedule logging
|
|
159
|
+
asyncio.create_task(_log_transition_event(
|
|
160
|
+
func, consumed, bb, timebase, event_type, attributes, log_inputs, result
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
# Return appropriate wrapper
|
|
166
|
+
if asyncio.iscoroutinefunction(func):
|
|
167
|
+
return async_wrapper # type: ignore
|
|
168
|
+
else:
|
|
169
|
+
return sync_wrapper # type: ignore
|
|
170
|
+
|
|
171
|
+
return decorator
|
|
172
|
+
|
|
173
|
+
def log_place(self, event_type: str = "token_arrived") -> Callable:
|
|
174
|
+
"""
|
|
175
|
+
Decorator to log place token arrivals.
|
|
176
|
+
|
|
177
|
+
Note: This requires instrumenting the place handler or using
|
|
178
|
+
a custom place runtime wrapper. For most cases, transition
|
|
179
|
+
logging provides sufficient coverage.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
event_type: Type of event to log
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Decorator function
|
|
186
|
+
"""
|
|
187
|
+
def decorator(func: Callable) -> Callable:
|
|
188
|
+
@functools.wraps(func)
|
|
189
|
+
async def async_wrapper(bb: Any, timebase: Any):
|
|
190
|
+
result = await func(bb, timebase)
|
|
191
|
+
|
|
192
|
+
# Log token arrival
|
|
193
|
+
config = get_config()
|
|
194
|
+
if config.enabled:
|
|
195
|
+
await _log_place_event(func, bb, timebase, event_type)
|
|
196
|
+
|
|
197
|
+
return result
|
|
198
|
+
|
|
199
|
+
@functools.wraps(func)
|
|
200
|
+
def sync_wrapper(bb: Any, timebase: Any):
|
|
201
|
+
result = func(bb, timebase)
|
|
202
|
+
|
|
203
|
+
# Schedule logging
|
|
204
|
+
config = get_config()
|
|
205
|
+
if config.enabled:
|
|
206
|
+
asyncio.create_task(_log_place_event(func, bb, timebase, event_type))
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
if asyncio.iscoroutinefunction(func):
|
|
211
|
+
return async_wrapper # type: ignore
|
|
212
|
+
else:
|
|
213
|
+
return sync_wrapper # type: ignore
|
|
214
|
+
|
|
215
|
+
return decorator
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
async def _log_transition_event(
|
|
219
|
+
func: Callable,
|
|
220
|
+
consumed: List[Any],
|
|
221
|
+
bb: Any,
|
|
222
|
+
timebase: Any,
|
|
223
|
+
event_type: str,
|
|
224
|
+
attributes: Optional[Union[Dict[str, Any], List[str]]],
|
|
225
|
+
log_inputs: bool,
|
|
226
|
+
result: Any,
|
|
227
|
+
) -> None:
|
|
228
|
+
"""Log a transition execution event."""
|
|
229
|
+
config = get_config()
|
|
230
|
+
if not config.enabled:
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
timestamp = datetime.now()
|
|
235
|
+
|
|
236
|
+
# Build event attributes
|
|
237
|
+
event_attrs = {}
|
|
238
|
+
|
|
239
|
+
# Add token count
|
|
240
|
+
event_attrs["token_count"] = EventAttributeValue(
|
|
241
|
+
name="token_count",
|
|
242
|
+
value=str(len(consumed)),
|
|
243
|
+
time=timestamp
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Add transition name
|
|
247
|
+
event_attrs["transition_name"] = EventAttributeValue(
|
|
248
|
+
name="transition_name",
|
|
249
|
+
value=func.__name__,
|
|
250
|
+
time=timestamp
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Extract from blackboard
|
|
254
|
+
bb_attrs = extract_attributes_from_blackboard(bb, timestamp)
|
|
255
|
+
event_attrs.update(bb_attrs)
|
|
256
|
+
|
|
257
|
+
# Build relationships from tokens
|
|
258
|
+
relationships = {}
|
|
259
|
+
event_objects = []
|
|
260
|
+
|
|
261
|
+
if log_inputs:
|
|
262
|
+
for token in consumed:
|
|
263
|
+
if token is not None:
|
|
264
|
+
obj = convert_to_ocel_object(token, qualifier="input")
|
|
265
|
+
if obj:
|
|
266
|
+
event_objects.append(obj)
|
|
267
|
+
relationships[obj.id] = Relationship(
|
|
268
|
+
object_id=obj.id,
|
|
269
|
+
qualifier="input"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Extract objects from blackboard
|
|
273
|
+
bb_objects = extract_objects_from_blackboard(bb)
|
|
274
|
+
event_objects.extend(bb_objects)
|
|
275
|
+
|
|
276
|
+
for obj in bb_objects:
|
|
277
|
+
relationships[obj.id] = Relationship(
|
|
278
|
+
object_id=obj.id,
|
|
279
|
+
qualifier="context"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Build event
|
|
283
|
+
event = Event(
|
|
284
|
+
id=generate_event_id(),
|
|
285
|
+
type=event_type,
|
|
286
|
+
time=timestamp,
|
|
287
|
+
attributes=event_attrs,
|
|
288
|
+
relationships=relationships
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Send event
|
|
292
|
+
await _send_log_record(LogRecord(event=event))
|
|
293
|
+
|
|
294
|
+
# Send objects to cache
|
|
295
|
+
cache = get_object_cache()
|
|
296
|
+
for obj in event_objects:
|
|
297
|
+
cache.contains_or_add(obj.id, obj)
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"Failed to log transition event: {e}")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
async def _log_outputs(
|
|
304
|
+
func: Callable,
|
|
305
|
+
outputs: List[Any],
|
|
306
|
+
bb: Any,
|
|
307
|
+
timebase: Any,
|
|
308
|
+
event_type: str,
|
|
309
|
+
) -> None:
|
|
310
|
+
"""Log output tokens as related objects."""
|
|
311
|
+
config = get_config()
|
|
312
|
+
if not config.enabled:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
timestamp = datetime.now()
|
|
317
|
+
event_objects = []
|
|
318
|
+
|
|
319
|
+
# Process outputs
|
|
320
|
+
for output in outputs:
|
|
321
|
+
if isinstance(output, dict):
|
|
322
|
+
# Dict of place_ref -> token
|
|
323
|
+
for place_ref, token in output.items():
|
|
324
|
+
if place_ref != '*' and token is not None:
|
|
325
|
+
obj = convert_to_ocel_object(token, qualifier="output")
|
|
326
|
+
if obj:
|
|
327
|
+
event_objects.append(obj)
|
|
328
|
+
elif isinstance(output, tuple) and len(output) == 2:
|
|
329
|
+
# (place_ref, token) tuple
|
|
330
|
+
place_ref, token = output
|
|
331
|
+
if token is not None:
|
|
332
|
+
obj = convert_to_ocel_object(token, qualifier="output")
|
|
333
|
+
if obj:
|
|
334
|
+
event_objects.append(obj)
|
|
335
|
+
|
|
336
|
+
# Send objects to cache
|
|
337
|
+
cache = get_object_cache()
|
|
338
|
+
for obj in event_objects:
|
|
339
|
+
cache.contains_or_add(obj.id, obj)
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.error(f"Failed to log outputs: {e}")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
async def _log_place_event(
|
|
346
|
+
func: Callable,
|
|
347
|
+
bb: Any,
|
|
348
|
+
timebase: Any,
|
|
349
|
+
event_type: str,
|
|
350
|
+
) -> None:
|
|
351
|
+
"""Log a place token event."""
|
|
352
|
+
config = get_config()
|
|
353
|
+
if not config.enabled:
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
timestamp = datetime.now()
|
|
358
|
+
|
|
359
|
+
# Build event attributes
|
|
360
|
+
event_attrs = {
|
|
361
|
+
"place_name": EventAttributeValue(
|
|
362
|
+
name="place_name",
|
|
363
|
+
value=func.__name__,
|
|
364
|
+
time=timestamp
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# Extract from blackboard
|
|
369
|
+
bb_attrs = extract_attributes_from_blackboard(bb, timestamp)
|
|
370
|
+
event_attrs.update(bb_attrs)
|
|
371
|
+
|
|
372
|
+
# Extract objects from blackboard
|
|
373
|
+
bb_objects = extract_objects_from_blackboard(bb)
|
|
374
|
+
relationships = {}
|
|
375
|
+
for obj in bb_objects:
|
|
376
|
+
relationships[obj.id] = Relationship(
|
|
377
|
+
object_id=obj.id,
|
|
378
|
+
qualifier="context"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Build event
|
|
382
|
+
event = Event(
|
|
383
|
+
id=generate_event_id(),
|
|
384
|
+
type=event_type,
|
|
385
|
+
time=timestamp,
|
|
386
|
+
attributes=event_attrs,
|
|
387
|
+
relationships=relationships
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Send event
|
|
391
|
+
await _send_log_record(LogRecord(event=event))
|
|
392
|
+
|
|
393
|
+
# Send objects to cache
|
|
394
|
+
cache = get_object_cache()
|
|
395
|
+
for obj in bb_objects:
|
|
396
|
+
cache.contains_or_add(obj.id, obj)
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
logger.error(f"Failed to log place event: {e}")
|