mycorrhizal 0.1.0__py3-none-any.whl → 0.1.2__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 -0
- mycorrhizal/hypha/core/builder.py +45 -7
- mycorrhizal/hypha/core/specs.py +19 -3
- mycorrhizal/rhizomorph/core.py +62 -4
- mycorrhizal/spores/__init__.py +69 -16
- mycorrhizal/spores/core.py +786 -75
- mycorrhizal/spores/models.py +22 -0
- mycorrhizal/spores/transport/__init__.py +9 -2
- mycorrhizal/spores/transport/base.py +36 -17
- mycorrhizal/spores/transport/file.py +126 -0
- {mycorrhizal-0.1.0.dist-info → mycorrhizal-0.1.2.dist-info}/METADATA +1 -1
- {mycorrhizal-0.1.0.dist-info → mycorrhizal-0.1.2.dist-info}/RECORD +13 -11
- {mycorrhizal-0.1.0.dist-info → mycorrhizal-0.1.2.dist-info}/WHEEL +0 -0
mycorrhizal/spores/models.py
CHANGED
|
@@ -174,6 +174,28 @@ class EventAttr:
|
|
|
174
174
|
name: Optional[str] = None
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
@dataclass(frozen=True)
|
|
178
|
+
class SporesAttr:
|
|
179
|
+
"""
|
|
180
|
+
Metadata annotation for object attributes in OCEL object logging.
|
|
181
|
+
|
|
182
|
+
Used with typing.Annotated to mark fields that should be logged when an object is logged:
|
|
183
|
+
|
|
184
|
+
class Order(BaseModel):
|
|
185
|
+
id: str
|
|
186
|
+
status: Annotated[str, SporesAttr] # Logged
|
|
187
|
+
total: Annotated[float, SporesAttr] # Logged
|
|
188
|
+
items: list[dict] # Not marked - not logged
|
|
189
|
+
|
|
190
|
+
When using @spore.log_event(relationships={...}), fields marked with SporesAttr
|
|
191
|
+
are automatically logged. If attributes list is provided, it overrides SporesAttr.
|
|
192
|
+
|
|
193
|
+
Attributes:
|
|
194
|
+
name: Optional custom name for the attribute (defaults to field name)
|
|
195
|
+
"""
|
|
196
|
+
name: Optional[str] = None
|
|
197
|
+
|
|
198
|
+
|
|
177
199
|
# ============================================================================
|
|
178
200
|
# Attribute Value Conversion
|
|
179
201
|
# ============================================================================
|
|
@@ -5,6 +5,13 @@ Spores Transports
|
|
|
5
5
|
Transports for sending OCEL LogRecords to various destinations.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .base import Transport
|
|
8
|
+
from .base import SyncTransport, AsyncTransport, Transport
|
|
9
|
+
from .file import SyncFileTransport, AsyncFileTransport
|
|
9
10
|
|
|
10
|
-
__all__ = [
|
|
11
|
+
__all__ = [
|
|
12
|
+
'SyncTransport',
|
|
13
|
+
'AsyncTransport',
|
|
14
|
+
'Transport', # Backward compatibility alias for AsyncTransport
|
|
15
|
+
'SyncFileTransport',
|
|
16
|
+
'AsyncFileTransport',
|
|
17
|
+
]
|
|
@@ -2,26 +2,28 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Spores Transport Interface
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Protocols for transporting encoded LogRecords.
|
|
6
|
+
Separate sync and async protocols for clarity.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
10
|
-
from
|
|
11
|
-
from typing import Protocol, Callable, Optional
|
|
11
|
+
from typing import Protocol
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class SyncTransport(Protocol):
|
|
15
15
|
"""
|
|
16
|
-
Protocol for transports
|
|
16
|
+
Protocol for synchronous transports (blocking I/O).
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
Sync transports use blocking send() - they write data immediately
|
|
19
|
+
and block until complete. Use these with get_spore_sync().
|
|
20
|
+
|
|
21
|
+
Examples: file writes, blocking HTTP requests, etc.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
def send(self, data: bytes, content_type: str) -> None:
|
|
23
25
|
"""
|
|
24
|
-
Send encoded data to the transport destination.
|
|
26
|
+
Send encoded data to the transport destination (blocking).
|
|
25
27
|
|
|
26
28
|
Args:
|
|
27
29
|
data: The encoded log record data
|
|
@@ -29,18 +31,35 @@ class Transport(Protocol):
|
|
|
29
31
|
"""
|
|
30
32
|
...
|
|
31
33
|
|
|
32
|
-
def
|
|
33
|
-
"""
|
|
34
|
-
|
|
34
|
+
def close(self) -> None:
|
|
35
|
+
"""Close the transport and release resources."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AsyncTransport(Protocol):
|
|
40
|
+
"""
|
|
41
|
+
Protocol for asynchronous transports (async I/O).
|
|
42
|
+
|
|
43
|
+
Async transports use non-blocking async send() - they await completion.
|
|
44
|
+
Use these with get_spore_async().
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
Examples: async file writes, async HTTP requests, message queues, etc.
|
|
47
|
+
"""
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
async def send(self, data: bytes, content_type: str) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Send encoded data to the transport destination (async).
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data: The encoded log record data
|
|
55
|
+
content_type: MIME content type (e.g., "application/json")
|
|
41
56
|
"""
|
|
42
57
|
...
|
|
43
58
|
|
|
44
|
-
def close(self) -> None:
|
|
59
|
+
async def close(self) -> None:
|
|
45
60
|
"""Close the transport and release resources."""
|
|
46
61
|
...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Backward compatibility alias
|
|
65
|
+
Transport = AsyncTransport
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Spores File Transports
|
|
4
|
+
|
|
5
|
+
File-based transports for OCEL log records.
|
|
6
|
+
Includes both synchronous (blocking I/O) and asynchronous (async I/O) implementations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import threading
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SyncFileTransport:
|
|
18
|
+
"""
|
|
19
|
+
Synchronous file transport using blocking I/O.
|
|
20
|
+
|
|
21
|
+
Writes log records to a file in JSONL format (one JSON object per line).
|
|
22
|
+
Thread-safe with a lock for concurrent writes.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, filepath: str | Path):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the file transport.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
filepath: Path to the log file. Will be created if it doesn't exist.
|
|
31
|
+
"""
|
|
32
|
+
self.filepath = Path(filepath)
|
|
33
|
+
self.filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
self._file = open(self.filepath, 'a', encoding='utf-8')
|
|
35
|
+
self._lock = threading.Lock()
|
|
36
|
+
|
|
37
|
+
def send(self, data: bytes, content_type: str) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Write data to file (blocking call).
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
data: Encoded log record data
|
|
43
|
+
content_type: MIME content type (e.g., "application/json")
|
|
44
|
+
"""
|
|
45
|
+
with self._lock:
|
|
46
|
+
self._file.write(data.decode('utf-8') + '\n')
|
|
47
|
+
self._file.flush()
|
|
48
|
+
|
|
49
|
+
def close(self) -> None:
|
|
50
|
+
"""Close the file handle."""
|
|
51
|
+
with self._lock:
|
|
52
|
+
if self._file and not self._file.closed:
|
|
53
|
+
self._file.close()
|
|
54
|
+
self._file = None
|
|
55
|
+
|
|
56
|
+
def __enter__(self):
|
|
57
|
+
"""Context manager support."""
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
61
|
+
"""Context manager support."""
|
|
62
|
+
self.close()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AsyncFileTransport:
|
|
66
|
+
"""
|
|
67
|
+
Asynchronous file transport using async I/O.
|
|
68
|
+
|
|
69
|
+
Writes log records to a file in JSONL format (one JSON object per line).
|
|
70
|
+
Uses asyncio.to_thread() for non-blocking async file I/O.
|
|
71
|
+
|
|
72
|
+
This is the async version of SyncFileTransport - use it with get_spore_async().
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, filepath: str | Path):
|
|
76
|
+
"""
|
|
77
|
+
Initialize the async file transport.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
filepath: Path to the log file. Will be created if it doesn't exist.
|
|
81
|
+
"""
|
|
82
|
+
self.filepath = Path(filepath)
|
|
83
|
+
self.filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
# Open file in append mode, will be managed by asyncio.to_thread
|
|
85
|
+
self._file = open(self.filepath, 'a', encoding='utf-8')
|
|
86
|
+
self._lock = asyncio.Lock()
|
|
87
|
+
|
|
88
|
+
async def send(self, data: bytes, content_type: str) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Write data to file asynchronously.
|
|
91
|
+
|
|
92
|
+
Uses asyncio.to_thread() to run blocking I/O in a thread pool,
|
|
93
|
+
avoiding the need for external dependencies like aiofiles.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
data: Encoded log record data
|
|
97
|
+
content_type: MIME content type (e.g., "application/json")
|
|
98
|
+
"""
|
|
99
|
+
async with self._lock:
|
|
100
|
+
# Run blocking file I/O in thread pool
|
|
101
|
+
await asyncio.to_thread(self._write_sync, data)
|
|
102
|
+
|
|
103
|
+
def _write_sync(self, data: bytes) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Synchronous write helper - runs in thread pool.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
data: Encoded log record data
|
|
109
|
+
"""
|
|
110
|
+
self._file.write(data.decode('utf-8') + '\n')
|
|
111
|
+
self._file.flush()
|
|
112
|
+
|
|
113
|
+
async def close(self) -> None:
|
|
114
|
+
"""Close the file handle asynchronously."""
|
|
115
|
+
async with self._lock:
|
|
116
|
+
if self._file and not self._file.closed:
|
|
117
|
+
await asyncio.to_thread(self._file.close)
|
|
118
|
+
self._file = None
|
|
119
|
+
|
|
120
|
+
async def __aenter__(self):
|
|
121
|
+
"""Async context manager support."""
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
125
|
+
"""Async context manager support."""
|
|
126
|
+
await self.close()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
mycorrhizal/__init__.py,sha256=ajz1GSNU9xYVrFEDSz6Xwg7amWQ_yvW75tQa1ZvRIWc,3
|
|
2
|
+
mycorrhizal/_version.py,sha256=08TK2sYzQF2Wqm6gpxZEgWYYF9ZjbInObIzS2R2yxiE,18
|
|
2
3
|
mycorrhizal/common/__init__.py,sha256=vAL2NZFFVcoGoPlIREKRG0mrgx1prVTGTlWBopJ76Sw,1450
|
|
3
4
|
mycorrhizal/common/interface_builder.py,sha256=rBJzduqZ6EjD9MNGO669BcjkM9mYvsnMG0F1kd1wWaQ,6895
|
|
4
5
|
mycorrhizal/common/interfaces.py,sha256=fWw6bm2ejFmKUnl2pSTiSEwTP0Id8Kowg827_Mqir-s,12261
|
|
@@ -11,18 +12,18 @@ mycorrhizal/enoki/util.py,sha256=oJ9vmHrbUdX99Rkh7bBeUHFzM5sS3yrtXOTQDA1524I,843
|
|
|
11
12
|
mycorrhizal/hypha/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
13
|
mycorrhizal/hypha/util.py,sha256=tpvjWXTdGhtVUycQElcQg4usOy-tSEqwE9KEni8R30s,906
|
|
13
14
|
mycorrhizal/hypha/core/__init__.py,sha256=o6BE3fhRKjVfyIWwtNTO64kPt0udlZGyXr-ZuN_ZzF8,2518
|
|
14
|
-
mycorrhizal/hypha/core/builder.py,sha256=
|
|
15
|
+
mycorrhizal/hypha/core/builder.py,sha256=GyjDWG3ZTO_0OIhJNrty4-SaJ9RGsNIzHyOQBCNKDfc,16912
|
|
15
16
|
mycorrhizal/hypha/core/runtime.py,sha256=K7EDxPTYu9lyHoolBGrrOTws7f1xmbnU2SBVVLnoD3g,34848
|
|
16
|
-
mycorrhizal/hypha/core/specs.py,sha256
|
|
17
|
+
mycorrhizal/hypha/core/specs.py,sha256=uWvW9x7-bSoSENIUJkYMIkJ3KxsgE9mB9PyRLPrOEjk,8429
|
|
17
18
|
mycorrhizal/rhizomorph/README.md,sha256=g-G8d00VgMOPriggsNveH7HqLhnJ9HlvoIzrwmUEy_M,8134
|
|
18
19
|
mycorrhizal/rhizomorph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
mycorrhizal/rhizomorph/core.py,sha256=
|
|
20
|
+
mycorrhizal/rhizomorph/core.py,sha256=FrV7S6coulaeqhYfHmNE9x6tmlGFBYcqO04zOdpwNNs,60816
|
|
20
21
|
mycorrhizal/rhizomorph/util.py,sha256=dUgKAWX5RNtzFK01mGkz10tJyeTMCqyyfnZ8Mi97LkY,1280
|
|
21
|
-
mycorrhizal/spores/__init__.py,sha256=
|
|
22
|
+
mycorrhizal/spores/__init__.py,sha256=VI0y_723Fo1la-nJGJweBWc8PKvpd_VmmT9EBtVrEog,4763
|
|
22
23
|
mycorrhizal/spores/cache.py,sha256=0FuWHKudgyC7vlrlcJ_kmRKr7OAJuC2gxRURReMlqF4,6119
|
|
23
|
-
mycorrhizal/spores/core.py,sha256=
|
|
24
|
+
mycorrhizal/spores/core.py,sha256=KGv3X6w6pz5RiUD__7MMrYUXvDwGUfWFU_eCvDcQSUg,38624
|
|
24
25
|
mycorrhizal/spores/extraction.py,sha256=YnUGABcVgZVSDCXyOQewWG62NAkdievJrXPIA_eJkxg,14918
|
|
25
|
-
mycorrhizal/spores/models.py,sha256=
|
|
26
|
+
mycorrhizal/spores/models.py,sha256=Gm_7jwPNgWhYCK5XDIUiN0DfDp4kJEkSbqxpwPdlsJE,8712
|
|
26
27
|
mycorrhizal/spores/dsl/__init__.py,sha256=bkxHTkaPrDWUlU1pjYlGJOM-772M5_k7R4aZ8HaxMNM,1153
|
|
27
28
|
mycorrhizal/spores/dsl/enoki.py,sha256=eVYNBp3ESZ7lK91vE8H5JWkXX14pOtf6cdAWSLiqAsE,15506
|
|
28
29
|
mycorrhizal/spores/dsl/hypha.py,sha256=vcJimWbZT2MmGhj8Lbn3izF1PO9HVJe1YQi5P3EiZ6o,11959
|
|
@@ -30,8 +31,9 @@ mycorrhizal/spores/dsl/rhizomorph.py,sha256=ChG2fRQD9DR90dLKLYSQR72asC-7eLccvBwZ
|
|
|
30
31
|
mycorrhizal/spores/encoder/__init__.py,sha256=hpp1W4xwQUG1N1zdrea0coBPtMf-WYSE77Q780TinP8,204
|
|
31
32
|
mycorrhizal/spores/encoder/base.py,sha256=xFKk-f5_W_HNrOgNXyquUwhcrcz28LmeT5Y5enDVrwU,870
|
|
32
33
|
mycorrhizal/spores/encoder/json.py,sha256=dyQSTXfG0VQPSNt9ed8VEgMwxFUPnLtV6u_bSRuEnNY,4747
|
|
33
|
-
mycorrhizal/spores/transport/__init__.py,sha256
|
|
34
|
-
mycorrhizal/spores/transport/base.py,sha256=
|
|
35
|
-
mycorrhizal
|
|
36
|
-
mycorrhizal-0.1.
|
|
37
|
-
mycorrhizal-0.1.
|
|
34
|
+
mycorrhizal/spores/transport/__init__.py,sha256=Tw6glCXsPK2pzsLPbb9vKOCvvT9vh-ql-xUaulKBAw0,407
|
|
35
|
+
mycorrhizal/spores/transport/base.py,sha256=OsyWx1J8Ig7Y7idUoI9oS-LkONwQAxAP47jL_5OVRjA,1668
|
|
36
|
+
mycorrhizal/spores/transport/file.py,sha256=xcMPxAGiv5q-GfcNKsF_3a-2-yxlDkhHusZli1Fsqa0,3833
|
|
37
|
+
mycorrhizal-0.1.2.dist-info/METADATA,sha256=c_xR3AeCwrC8jhbnimBcfu_7jmFItM8UdgAR2Wi8RYQ,4895
|
|
38
|
+
mycorrhizal-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
39
|
+
mycorrhizal-0.1.2.dist-info/RECORD,,
|
|
File without changes
|