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.
Files changed (37) hide show
  1. mycorrhizal/__init__.py +3 -0
  2. mycorrhizal/common/__init__.py +68 -0
  3. mycorrhizal/common/interface_builder.py +203 -0
  4. mycorrhizal/common/interfaces.py +412 -0
  5. mycorrhizal/common/timebase.py +99 -0
  6. mycorrhizal/common/wrappers.py +532 -0
  7. mycorrhizal/enoki/__init__.py +0 -0
  8. mycorrhizal/enoki/core.py +1545 -0
  9. mycorrhizal/enoki/testing_utils.py +529 -0
  10. mycorrhizal/enoki/util.py +220 -0
  11. mycorrhizal/hypha/__init__.py +0 -0
  12. mycorrhizal/hypha/core/__init__.py +107 -0
  13. mycorrhizal/hypha/core/builder.py +404 -0
  14. mycorrhizal/hypha/core/runtime.py +890 -0
  15. mycorrhizal/hypha/core/specs.py +234 -0
  16. mycorrhizal/hypha/util.py +38 -0
  17. mycorrhizal/rhizomorph/README.md +220 -0
  18. mycorrhizal/rhizomorph/__init__.py +0 -0
  19. mycorrhizal/rhizomorph/core.py +1729 -0
  20. mycorrhizal/rhizomorph/util.py +45 -0
  21. mycorrhizal/spores/__init__.py +124 -0
  22. mycorrhizal/spores/cache.py +208 -0
  23. mycorrhizal/spores/core.py +419 -0
  24. mycorrhizal/spores/dsl/__init__.py +48 -0
  25. mycorrhizal/spores/dsl/enoki.py +514 -0
  26. mycorrhizal/spores/dsl/hypha.py +399 -0
  27. mycorrhizal/spores/dsl/rhizomorph.py +351 -0
  28. mycorrhizal/spores/encoder/__init__.py +11 -0
  29. mycorrhizal/spores/encoder/base.py +42 -0
  30. mycorrhizal/spores/encoder/json.py +159 -0
  31. mycorrhizal/spores/extraction.py +484 -0
  32. mycorrhizal/spores/models.py +288 -0
  33. mycorrhizal/spores/transport/__init__.py +10 -0
  34. mycorrhizal/spores/transport/base.py +46 -0
  35. mycorrhizal-0.1.0.dist-info/METADATA +198 -0
  36. mycorrhizal-0.1.0.dist-info/RECORD +37 -0
  37. mycorrhizal-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Spores Data Models
4
+
5
+ OCEL-compatible data models for event and object logging.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from typing import Dict, Optional, Union, Any
13
+ from enum import Enum
14
+
15
+
16
+ # ============================================================================
17
+ # OCEL Data Models
18
+ # ============================================================================
19
+
20
+ @dataclass(frozen=True)
21
+ class Relationship:
22
+ """
23
+ A relationship from an event to an object.
24
+
25
+ Attributes:
26
+ object_id: The ID of the related object
27
+ qualifier: How the object relates to the event (e.g., "input", "output", "actor")
28
+ """
29
+ object_id: str
30
+ qualifier: str
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class EventAttributeValue:
35
+ """
36
+ An attribute value for an event.
37
+
38
+ OCEL attributes include timestamps for versioning.
39
+
40
+ Attributes:
41
+ name: The attribute name
42
+ value: The string representation of the value
43
+ time: When this attribute value was set
44
+ """
45
+ name: str
46
+ value: str
47
+ time: datetime
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class ObjectAttributeValue:
52
+ """
53
+ An attribute value for an object.
54
+
55
+ OCEL attributes include timestamps for versioning.
56
+
57
+ Attributes:
58
+ name: The attribute name
59
+ value: The string representation of the value
60
+ time: When this attribute value was set
61
+ """
62
+ name: str
63
+ value: str
64
+ time: datetime
65
+
66
+
67
+ @dataclass
68
+ class Event:
69
+ """
70
+ An OCEL event representing something that happened.
71
+
72
+ Attributes:
73
+ id: Unique event identifier
74
+ type: Event type/category
75
+ time: When the event occurred
76
+ attributes: Event attributes (name -> EventAttributeValue)
77
+ relationships: Objects related to this event (qualifier -> Relationship)
78
+ """
79
+ id: str
80
+ type: str
81
+ time: datetime
82
+ attributes: Dict[str, EventAttributeValue] = field(default_factory=dict)
83
+ relationships: Dict[str, Relationship] = field(default_factory=dict)
84
+
85
+
86
+ @dataclass
87
+ class Object:
88
+ """
89
+ An OCEL object representing an entity in the system.
90
+
91
+ Attributes:
92
+ id: Unique object identifier
93
+ type: Object type/category
94
+ attributes: Object attributes (name -> ObjectAttributeValue)
95
+ relationships: Other objects related to this object (qualifier -> Relationship)
96
+ """
97
+ id: str
98
+ type: str
99
+ attributes: Dict[str, ObjectAttributeValue] = field(default_factory=dict)
100
+ relationships: Dict[str, Relationship] = field(default_factory=dict)
101
+
102
+
103
+ @dataclass
104
+ class LogRecord:
105
+ """
106
+ A log record containing either an event or an object (not both).
107
+
108
+ This is the union type used for streaming to OCEL consumers.
109
+
110
+ Attributes:
111
+ event: An event record (if logging an event)
112
+ object: An object record (if logging an object)
113
+
114
+ Note:
115
+ Exactly one of event or object must be set.
116
+ """
117
+ event: Optional[Event] = None
118
+ object: Optional[Object] = None
119
+
120
+ def __post_init__(self):
121
+ """Validate that exactly one of event or object is set."""
122
+ if self.event is None and self.object is None:
123
+ raise ValueError("LogRecord must have either event or object set")
124
+ if self.event is not None and self.object is not None:
125
+ raise ValueError("LogRecord cannot have both event and object set")
126
+
127
+
128
+ # ============================================================================
129
+ # Spores-Specific Metadata
130
+ # ============================================================================
131
+
132
+ class ObjectScope(Enum):
133
+ """Scope for object associations with events."""
134
+ GLOBAL = "global" # Included in every event
135
+ EVENT = "event" # Only when explicitly requested
136
+ AUTO = "auto" # Automatically detected from context
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class ObjectRef:
141
+ """
142
+ Metadata annotation for object references in blackboard interfaces.
143
+
144
+ Used with typing.Annotated to mark fields as OCEL objects:
145
+
146
+ robot: Annotated[Robot, ObjectRef(qualifier="actor", scope=ObjectScope.GLOBAL)]
147
+
148
+ Attributes:
149
+ qualifier: How the object relates to events ("input", "output", "actor", etc.)
150
+ scope: When to include this object in events
151
+ """
152
+ qualifier: str
153
+ scope: Union[ObjectScope, str] = ObjectScope.EVENT
154
+
155
+ def __post_init__(self):
156
+ """Convert string scope to ObjectScope enum if needed."""
157
+ if isinstance(self.scope, str):
158
+ object.__setattr__(self, 'scope', ObjectScope(self.scope))
159
+
160
+
161
+ @dataclass(frozen=True)
162
+ class EventAttr:
163
+ """
164
+ Metadata annotation for event attributes in blackboard interfaces.
165
+
166
+ Used with typing.Annotated to mark fields that should be logged as event attributes:
167
+
168
+ mission_id: Annotated[str, EventAttr]
169
+ battery_level: Annotated[int, EventAttr]
170
+
171
+ Attributes:
172
+ name: Optional custom name for the attribute (defaults to field name)
173
+ """
174
+ name: Optional[str] = None
175
+
176
+
177
+ # ============================================================================
178
+ # Attribute Value Conversion
179
+ # ============================================================================
180
+
181
+ def attribute_value_from_python(value: Any, time: datetime) -> EventAttributeValue:
182
+ """
183
+ Convert a Python value to an OCEL EventAttributeValue.
184
+
185
+ All values are converted to strings per OCEL specification.
186
+
187
+ Args:
188
+ value: The Python value to convert
189
+ time: The timestamp for this attribute value
190
+
191
+ Returns:
192
+ An EventAttributeValue with string representation
193
+ """
194
+ # Handle None
195
+ if value is None:
196
+ str_value = "null"
197
+
198
+ # Handle enums (use name)
199
+ elif isinstance(value, Enum):
200
+ str_value = value.name
201
+
202
+ # Handle datetime (ISO format)
203
+ elif isinstance(value, datetime):
204
+ str_value = value.isoformat()
205
+
206
+ # Handle bool (lowercase true/false)
207
+ elif isinstance(value, bool):
208
+ str_value = "true" if value else "false"
209
+
210
+ # Handle primitives
211
+ elif isinstance(value, (str, int, float)):
212
+ str_value = str(value)
213
+
214
+ # Everything else: convert to string
215
+ else:
216
+ str_value = str(value)
217
+
218
+ return EventAttributeValue(
219
+ name="", # Name set by caller
220
+ value=str_value,
221
+ time=time
222
+ )
223
+
224
+
225
+ def object_attribute_from_python(value: Any, time: datetime) -> ObjectAttributeValue:
226
+ """
227
+ Convert a Python value to an OCEL ObjectAttributeValue.
228
+
229
+ Same conversion rules as attribute_value_from_python but for objects.
230
+
231
+ Args:
232
+ value: The Python value to convert
233
+ time: The timestamp for this attribute value
234
+
235
+ Returns:
236
+ An ObjectAttributeValue with string representation
237
+ """
238
+ # Use same conversion logic
239
+ event_attr = attribute_value_from_python(value, time)
240
+
241
+ return ObjectAttributeValue(
242
+ name=event_attr.name,
243
+ value=event_attr.value,
244
+ time=event_attr.time
245
+ )
246
+
247
+
248
+ # ============================================================================
249
+ # Event ID Generation
250
+ # ============================================================================
251
+
252
+ def generate_event_id() -> str:
253
+ """
254
+ Generate a unique event ID.
255
+
256
+ Simple implementation using counter. Could be enhanced with UUIDs
257
+ or more sophisticated schemes.
258
+
259
+ Returns:
260
+ A unique event identifier
261
+ """
262
+ import uuid
263
+ return f"evt-{uuid.uuid4()}"
264
+
265
+
266
+ def generate_object_id(obj: Any) -> str:
267
+ """
268
+ Generate an object ID from an object.
269
+
270
+ Tries to use obj.id if available, otherwise generates a UUID.
271
+
272
+ Args:
273
+ obj: The object to generate an ID for
274
+
275
+ Returns:
276
+ A unique object identifier
277
+ """
278
+ # Try to get id attribute
279
+ if hasattr(obj, 'id'):
280
+ return str(obj.id)
281
+
282
+ # Try Pydantic model's field
283
+ if hasattr(obj, '__dict__') and 'id' in obj.__dict__:
284
+ return str(obj.__dict__['id'])
285
+
286
+ # Generate UUID-based ID
287
+ import uuid
288
+ return f"obj-{uuid.uuid4()}"
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Spores Transports
4
+
5
+ Transports for sending OCEL LogRecords to various destinations.
6
+ """
7
+
8
+ from .base import Transport
9
+
10
+ __all__ = ['Transport']
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Spores Transport Interface
4
+
5
+ Abstract base class for transporting encoded LogRecords.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Protocol, Callable, Optional
12
+
13
+
14
+ class Transport(Protocol):
15
+ """
16
+ Protocol for transports that send encoded LogRecords.
17
+
18
+ Transports handle the actual delivery of log records to consumers
19
+ (e.g., HTTP POST, files, message queues, etc.).
20
+ """
21
+
22
+ async def send(self, data: bytes, content_type: str) -> None:
23
+ """
24
+ Send encoded data to the transport destination.
25
+
26
+ Args:
27
+ data: The encoded log record data
28
+ content_type: MIME content type (e.g., "application/json")
29
+ """
30
+ ...
31
+
32
+ def is_async(self) -> bool:
33
+ """
34
+ Return True if this transport is async (non-blocking).
35
+
36
+ Async transports don't block the caller when sending data.
37
+ They typically use background threads, queues, or async I/O.
38
+
39
+ Returns:
40
+ True if transport is async, False if synchronous
41
+ """
42
+ ...
43
+
44
+ def close(self) -> None:
45
+ """Close the transport and release resources."""
46
+ ...
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: mycorrhizal
3
+ Version: 0.1.0
4
+ Summary: Utilities and DSLs for modelling and implementing safe, performant, structured systems
5
+ Author-email: Jeff Ciesielski <jeffciesielski@gmail.com>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: pydantic>=2.11.7
8
+ Requires-Dist: typeguard==4.4.4
9
+ Description-Content-Type: text/markdown
10
+
11
+ # Mycorrhizal
12
+
13
+ A Python library for building safe, structured, concurrent, event-driven systems.
14
+
15
+ ## Overview
16
+
17
+ Mycorrhizal provides four domain-specific languages (DSLs) for modeling and implementing different aspects of complex systems:
18
+
19
+ * **Hypha** - Colored Petri nets for workflow modeling and orchestration
20
+ * **Rhizomorph** - Behavior trees for decision-making and control logic
21
+ * **Enoki** - Finite state machines for stateful components
22
+ * **Spores** - Event and object logging for observability and process mining
23
+
24
+ Each DSL can be used independently or combined to build sophisticated systems. All modules share common infrastructure for state management (blackboards) and time abstraction, enabling seamless composition.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install mycorrhizal
30
+ ```
31
+
32
+ Requires Python 3.10 or later.
33
+
34
+ ## Quick Start
35
+
36
+ ### Behavior Tree (Rhizomorph)
37
+
38
+ Define behavior trees using a decorator-based DSL:
39
+
40
+ ```python
41
+ from mycorrhizal.rhizomorph.core import bt, Runner, Status
42
+
43
+ @bt.tree
44
+ def ThreatResponse():
45
+ @bt.action
46
+ async def assess_threat(bb) -> Status:
47
+ # Analyze threat level
48
+ return Status.SUCCESS if bb.threat_level > 5 else Status.FAILURE
49
+
50
+ @bt.action
51
+ async def engage_countermeasures(bb) -> Status:
52
+ # Respond to threat
53
+ return Status.SUCCESS
54
+
55
+ @bt.root
56
+ @bt.sequence
57
+ def root():
58
+ yield assess_threat
59
+ yield engage_countermeasures
60
+
61
+ # Run the behavior tree
62
+ runner = Runner(ThreatResponse, bb=blackboard)
63
+ await runner.tick_until_complete()
64
+ ```
65
+
66
+ ### Petri Net (Hypha)
67
+
68
+ Model workflows with colored Petri nets:
69
+
70
+ ```python
71
+ from mycorrhizal.hypha.core import pn, Runner, PlaceType
72
+
73
+ @pn.net
74
+ def ProcessingNet(builder):
75
+ # Define places
76
+ pending = builder.place("pending", type=PlaceType.QUEUE)
77
+ processed = builder.place("processed", type=PlaceType.QUEUE)
78
+
79
+ # Define transitions
80
+ @builder.transition()
81
+ async def process(consumed, bb, timebase):
82
+ for token in consumed:
83
+ result = await handle(token)
84
+ yield {processed: result}
85
+
86
+ # Wire the net
87
+ builder.arc(pending, process).arc(processed)
88
+
89
+ # Run the Petri net
90
+ runner = Runner(ProcessingNet, bb=blackboard)
91
+ await runner.start(timebase)
92
+ ```
93
+
94
+ ### Finite State Machine (Enoki)
95
+
96
+ Build stateful components with FSMs:
97
+
98
+ ```python
99
+ from mycorrhizal.enoki.core import enoki, StateMachine, LabeledTransition
100
+
101
+ @enoki.state()
102
+ def IdleState():
103
+ @enoki.on_state
104
+ async def on_state(ctx):
105
+ if ctx.msg == "start":
106
+ return Events.START
107
+ return None
108
+
109
+ @enoki.transitions
110
+ def transitions():
111
+ return [
112
+ LabeledTransition(Events.START, ProcessingState),
113
+ ]
114
+
115
+ # Create and run the FSM
116
+ fsm = StateMachine(initial_state=IdleState, common_data={})
117
+ await fsm.initialize()
118
+ fsm.send_message("start")
119
+ await fsm.tick()
120
+ ```
121
+
122
+ ## Key Features
123
+
124
+ ### Shared Infrastructure
125
+
126
+ All DSLs use common building blocks:
127
+
128
+ * **Blackboards** - Typed shared state using Pydantic models
129
+ * **Interfaces** - Decorator-based access control for blackboard fields
130
+ * **Timebase** - Abstract time for simulation and testing
131
+
132
+ ### Composition
133
+
134
+ Combine DSLs to model complex systems:
135
+
136
+ * Embed behavior trees in Petri net transitions
137
+ * Run state machines within behavior tree actions
138
+ * Use Petri nets to orchestrate FSM-based components
139
+
140
+ ### Observability
141
+
142
+ The Spores module provides OCEL-compliant logging:
143
+
144
+ * Automatic event extraction from DSL execution
145
+ * Object lifecycle tracking
146
+ * Transport layer for custom backends
147
+
148
+ ## Examples
149
+
150
+ The repository contains comprehensive examples:
151
+
152
+ * `examples/hypha/` - Petri net patterns
153
+ * `examples/rhizomorph/` - Behavior tree patterns
154
+ * `examples/enoki/` - State machine patterns
155
+ * `examples/spores/` - Event logging integration
156
+ * `examples/interfaces/` - Type-safe blackboard access
157
+
158
+ Run examples with:
159
+
160
+ ```bash
161
+ uv run python examples/hypha/minimal_hypha_demo.py
162
+ ```
163
+
164
+ See [examples/README.md](examples/README.md) for a complete guide.
165
+
166
+ ## Documentation
167
+
168
+ Full API documentation is available at [docs link here].
169
+
170
+ ## Development
171
+
172
+ ```bash
173
+ # Install dependencies
174
+ uv pip install -e ".[dev]"
175
+
176
+ # Run tests
177
+ pytest
178
+
179
+ # Run with coverage
180
+ pytest --cov=src/mycorrhizal --cov-report=html
181
+ ```
182
+
183
+ ## Project Status
184
+
185
+ This is a 0.1.0 release. The core APIs are stable and well-tested, but some features are still in development:
186
+
187
+ * Current: Four DSLs with decorator-based syntax
188
+ * Current: Comprehensive examples and tests
189
+ * Planned: Cross-DSL interoperability layer
190
+ * Planned: Enhanced composition patterns
191
+
192
+ ## License
193
+
194
+ MIT
195
+
196
+ ## Author
197
+
198
+ Jeff Ciesielski
@@ -0,0 +1,37 @@
1
+ mycorrhizal/__init__.py,sha256=ajz1GSNU9xYVrFEDSz6Xwg7amWQ_yvW75tQa1ZvRIWc,3
2
+ mycorrhizal/common/__init__.py,sha256=vAL2NZFFVcoGoPlIREKRG0mrgx1prVTGTlWBopJ76Sw,1450
3
+ mycorrhizal/common/interface_builder.py,sha256=rBJzduqZ6EjD9MNGO669BcjkM9mYvsnMG0F1kd1wWaQ,6895
4
+ mycorrhizal/common/interfaces.py,sha256=fWw6bm2ejFmKUnl2pSTiSEwTP0Id8Kowg827_Mqir-s,12261
5
+ mycorrhizal/common/timebase.py,sha256=h8OymApwr_EvpRsfRJb6E5YosHg3jfqToOGAfMlL-Qw,2229
6
+ mycorrhizal/common/wrappers.py,sha256=hdj_1OgWe9RNqGlEkcklQ2j0FFn-AE9h5A55udYTr4o,17794
7
+ mycorrhizal/enoki/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ mycorrhizal/enoki/core.py,sha256=17oV8AGTI5aY299nAm3Jtc5iarImT2WPbMIwYE5LRsA,53697
9
+ mycorrhizal/enoki/testing_utils.py,sha256=S19p0nZ0UTfQm-hPHFAWqN9LAdfjL9uQKMFx_gaices,17307
10
+ mycorrhizal/enoki/util.py,sha256=oJ9vmHrbUdX99Rkh7bBeUHFzM5sS3yrtXOTQDA1524I,8434
11
+ mycorrhizal/hypha/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ mycorrhizal/hypha/util.py,sha256=tpvjWXTdGhtVUycQElcQg4usOy-tSEqwE9KEni8R30s,906
13
+ mycorrhizal/hypha/core/__init__.py,sha256=o6BE3fhRKjVfyIWwtNTO64kPt0udlZGyXr-ZuN_ZzF8,2518
14
+ mycorrhizal/hypha/core/builder.py,sha256=3oXG235bllLql8v2IiIa9K7ska_e4cHc8tvGGWwWZzA,15210
15
+ mycorrhizal/hypha/core/runtime.py,sha256=K7EDxPTYu9lyHoolBGrrOTws7f1xmbnU2SBVVLnoD3g,34848
16
+ mycorrhizal/hypha/core/specs.py,sha256=-MH4-ast3uxIWns3VpYV9vC_vqfjLCbvj61nbAOImn8,7882
17
+ mycorrhizal/rhizomorph/README.md,sha256=g-G8d00VgMOPriggsNveH7HqLhnJ9HlvoIzrwmUEy_M,8134
18
+ mycorrhizal/rhizomorph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ mycorrhizal/rhizomorph/core.py,sha256=BmgTbnzF2fzYhjQK4iAuO85LHCsN9-dKGok6U8bDv-g,58668
20
+ mycorrhizal/rhizomorph/util.py,sha256=dUgKAWX5RNtzFK01mGkz10tJyeTMCqyyfnZ8Mi97LkY,1280
21
+ mycorrhizal/spores/__init__.py,sha256=y-KPzgUMVXJKjClgQXmsykdWNe2g_U42pWRvdmmCqbg,2836
22
+ mycorrhizal/spores/cache.py,sha256=0FuWHKudgyC7vlrlcJ_kmRKr7OAJuC2gxRURReMlqF4,6119
23
+ mycorrhizal/spores/core.py,sha256=mfo1dQtDeyyYedIiaQ16YPhcBpIe-4wgzWXiMCjIj1I,12310
24
+ mycorrhizal/spores/extraction.py,sha256=YnUGABcVgZVSDCXyOQewWG62NAkdievJrXPIA_eJkxg,14918
25
+ mycorrhizal/spores/models.py,sha256=P5hlBaKmK6pCyLxBwGgPB4U9ef29_9fEoNEbYXJLV6g,7964
26
+ mycorrhizal/spores/dsl/__init__.py,sha256=bkxHTkaPrDWUlU1pjYlGJOM-772M5_k7R4aZ8HaxMNM,1153
27
+ mycorrhizal/spores/dsl/enoki.py,sha256=eVYNBp3ESZ7lK91vE8H5JWkXX14pOtf6cdAWSLiqAsE,15506
28
+ mycorrhizal/spores/dsl/hypha.py,sha256=vcJimWbZT2MmGhj8Lbn3izF1PO9HVJe1YQi5P3EiZ6o,11959
29
+ mycorrhizal/spores/dsl/rhizomorph.py,sha256=ChG2fRQD9DR90dLKLYSQR72asC-7eLccvBwZtz8C8kw,9898
30
+ mycorrhizal/spores/encoder/__init__.py,sha256=hpp1W4xwQUG1N1zdrea0coBPtMf-WYSE77Q780TinP8,204
31
+ mycorrhizal/spores/encoder/base.py,sha256=xFKk-f5_W_HNrOgNXyquUwhcrcz28LmeT5Y5enDVrwU,870
32
+ mycorrhizal/spores/encoder/json.py,sha256=dyQSTXfG0VQPSNt9ed8VEgMwxFUPnLtV6u_bSRuEnNY,4747
33
+ mycorrhizal/spores/transport/__init__.py,sha256=-QYmAwqaTbhrgEXr16qSYWx2f6J22d0SklPlm_Y8RWo,168
34
+ mycorrhizal/spores/transport/base.py,sha256=lJ2sLlXd-ZDeMn3ujSoYTneUB3Zwlz0mpWh5yxKK6Lw,1201
35
+ mycorrhizal-0.1.0.dist-info/METADATA,sha256=fKifHtnUMVeIKv13_kukZ6rtotKusA3m20VSXRr9qK0,4895
36
+ mycorrhizal-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
+ mycorrhizal-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any