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,3 @@
1
+
2
+
3
+
@@ -0,0 +1,68 @@
1
+ """
2
+ Common utilities and interfaces for Mycorrhizal.
3
+
4
+ This module provides shared components used across all three DSL systems:
5
+ - Hypha (Petri Nets)
6
+ - Rhizomorph (Behavior Trees)
7
+ - Enoki (State Machines)
8
+ """
9
+
10
+ # Import interfaces for easy access
11
+ from mycorrhizal.common.interfaces import (
12
+ Readable,
13
+ Writable,
14
+ FieldAccessor,
15
+ BlackboardProtocol,
16
+ FieldMetadata,
17
+ InterfaceMetadata,
18
+ validate_implements,
19
+ get_interface_fields,
20
+ create_interface_from_model,
21
+ )
22
+
23
+ # Import wrappers for easy access
24
+ from mycorrhizal.common.wrappers import (
25
+ AccessControlError,
26
+ ReadOnlyView,
27
+ WriteOnlyView,
28
+ ConstrainedView,
29
+ CompositeView,
30
+ create_readonly_view,
31
+ create_constrained_view,
32
+ create_view_from_protocol,
33
+ View,
34
+ )
35
+
36
+ # Import interface builder for easy access
37
+ from mycorrhizal.common.interface_builder import (
38
+ blackboard_interface,
39
+ FieldSpec,
40
+ )
41
+
42
+ __all__ = [
43
+ # Interfaces
44
+ "Readable",
45
+ "Writable",
46
+ "FieldAccessor",
47
+ "BlackboardProtocol",
48
+ "FieldMetadata",
49
+ "InterfaceMetadata",
50
+ "validate_implements",
51
+ "get_interface_fields",
52
+ "create_interface_from_model",
53
+
54
+ # Wrappers
55
+ "AccessControlError",
56
+ "ReadOnlyView",
57
+ "WriteOnlyView",
58
+ "ConstrainedView",
59
+ "CompositeView",
60
+ "create_readonly_view",
61
+ "create_constrained_view",
62
+ "create_view_from_protocol",
63
+ "View",
64
+
65
+ # Interface Builder
66
+ "blackboard_interface",
67
+ "FieldSpec",
68
+ ]
@@ -0,0 +1,203 @@
1
+ """
2
+ Interface Builder DSL for Blackboard Interfaces
3
+
4
+ This module provides a declarative DSL for defining blackboard interfaces
5
+ using PEP 593 Annotated types and access control markers.
6
+
7
+ Key concepts:
8
+ - @blackboard_interface - Decorator for defining interfaces
9
+ - readonly/readwrite markers for field access control
10
+ - Annotated types for clean, Pythonic field definitions
11
+ """
12
+
13
+ from typing import Protocol, TypeVar, Generic, Type, Any, Optional, get_type_hints, Annotated, get_args
14
+ from dataclasses import dataclass
15
+ import inspect
16
+
17
+
18
+ # ============================================================================
19
+ # Type Variables
20
+ # ============================================================================
21
+
22
+ T = TypeVar('T')
23
+
24
+
25
+ # ============================================================================
26
+ # Access Control Markers
27
+ # ============================================================================
28
+
29
+ class _AccessMarker:
30
+ """Base class for access control markers"""
31
+ pass
32
+
33
+
34
+ class readonly(_AccessMarker):
35
+ """
36
+ Marker for readonly fields in blackboard interfaces.
37
+
38
+ Fields marked as readonly can be read but not modified through the interface.
39
+
40
+ Example:
41
+ ```python
42
+ @blackboard_interface
43
+ class SensorInterface:
44
+ temperature: Annotated[float, readonly] = 20.0
45
+ ```
46
+ """
47
+ pass
48
+
49
+
50
+ class readwrite(_AccessMarker):
51
+ """
52
+ Marker for readwrite fields in blackboard interfaces.
53
+
54
+ Fields marked as readwrite can be both read and modified through the interface.
55
+
56
+ Example:
57
+ ```python
58
+ @blackboard_interface
59
+ class StateInterface:
60
+ counter: Annotated[int, readwrite] = 0
61
+ ```
62
+ """
63
+ pass
64
+
65
+
66
+ # ============================================================================
67
+ # Field Metadata
68
+ # ============================================================================
69
+
70
+ @dataclass
71
+ class FieldSpec:
72
+ """Specification for a field in an interface"""
73
+ name: str
74
+ type: type
75
+ readonly: bool = False
76
+ computed: bool = False
77
+ computation: Optional[callable] = None
78
+
79
+
80
+ # ============================================================================
81
+ # Decorator: blackboard_interface
82
+ # ============================================================================
83
+
84
+ def blackboard_interface(cls: Type[T] | None = None, *, name: str | None = None) -> Any:
85
+ """
86
+ Decorator for defining blackboard interfaces using Annotated types.
87
+
88
+ Processes class annotations looking for Annotated types with readonly or
89
+ readwrite markers, and generates a Protocol class with access control metadata.
90
+
91
+ Args:
92
+ cls: The class being decorated
93
+ name: Optional name for the generated interface
94
+
95
+ Returns:
96
+ A Protocol class with type hints and metadata
97
+
98
+ Example:
99
+ ```python
100
+ from typing import Annotated
101
+ from mycorrhizal.common.interface_builder import blackboard_interface, readonly, readwrite
102
+
103
+ @blackboard_interface
104
+ class TaskInterface:
105
+ max_tasks: Annotated[int, readonly] = 10
106
+ tasks_completed: Annotated[int, readwrite] = 0
107
+ ```
108
+
109
+ Fields without Annotated markers are treated as readwrite by default.
110
+ """
111
+ def process_class(source_class: Type) -> Type:
112
+ """Process the source class and generate a Protocol-like class"""
113
+ # Get the interface name
114
+ interface_name = name or source_class.__name__
115
+
116
+ # Extract type hints
117
+ try:
118
+ hints = get_type_hints(source_class, include_extras=True)
119
+ except Exception:
120
+ hints = {}
121
+
122
+ # Determine field metadata from Annotated types
123
+ readonly_fields = set()
124
+ readwrite_fields = set()
125
+
126
+ for field_name, field_type in hints.items():
127
+ if field_name.startswith('_'):
128
+ continue
129
+
130
+ # Check if this is an Annotated type with access marker
131
+ if str(field_type).startswith('typing.Annotated['):
132
+ # Extract metadata from Annotated[type, *markers]
133
+ args = get_args(field_type)
134
+ if len(args) >= 2:
135
+ actual_type = args[0]
136
+ # Check metadata for readonly or readwrite marker
137
+ metadata = args[1:]
138
+ found_marker = False
139
+ for marker in metadata:
140
+ if isinstance(marker, type) and issubclass(marker, _AccessMarker):
141
+ if marker is readonly:
142
+ readonly_fields.add(field_name)
143
+ found_marker = True
144
+ break
145
+ elif marker is readwrite:
146
+ readwrite_fields.add(field_name)
147
+ found_marker = True
148
+ break
149
+
150
+ if not found_marker:
151
+ # No access marker found, default to readwrite
152
+ readwrite_fields.add(field_name)
153
+ else:
154
+ # Annotated with only type, no metadata
155
+ readwrite_fields.add(field_name)
156
+ else:
157
+ # Not Annotated, default to readwrite
158
+ readwrite_fields.add(field_name)
159
+
160
+ # Create a new class with Protocol-like behavior
161
+ namespace = {
162
+ '__annotations__': {},
163
+ '_readonly_fields': frozenset(readonly_fields),
164
+ '_readwrite_fields': frozenset(readwrite_fields),
165
+ '__doc__': source_class.__doc__ or f"Interface: {interface_name}",
166
+ '__module__': source_class.__module__,
167
+ '__protocol_attrs__': frozenset(), # For runtime_checkable
168
+ }
169
+
170
+ # Copy field annotations (use actual types from Annotated)
171
+ for field_name, field_type in hints.items():
172
+ if not field_name.startswith('_'):
173
+ # Extract actual type from Annotated if present
174
+ if hasattr(field_type, '__origin__') and field_type.__origin__ is Annotated:
175
+ actual_type = field_type.__args__[0]
176
+ else:
177
+ actual_type = field_type
178
+ namespace['__annotations__'][field_name] = actual_type
179
+
180
+ # Create the interface class
181
+ interface = type(interface_name, (), namespace)
182
+
183
+ return interface
184
+
185
+ # Support @blackboard_interface and @blackboard_interface()
186
+ if cls is None:
187
+ # Called with parentheses
188
+ return lambda c: process_class(c)
189
+ else:
190
+ # Called without parentheses
191
+ return process_class(cls)
192
+
193
+
194
+ # ============================================================================
195
+ # Public API Exports
196
+ # ============================================================================
197
+
198
+ __all__ = [
199
+ "blackboard_interface",
200
+ "readonly",
201
+ "readwrite",
202
+ "FieldSpec",
203
+ ]
@@ -0,0 +1,412 @@
1
+ """
2
+ Blackboard Interface System
3
+
4
+ This module provides Protocol-based interfaces for type-safe, constrained access
5
+ to blackboard state across Mycorrhizal's three DSL systems (Hypha, Rhizomorph, Enoki).
6
+
7
+ Key concepts:
8
+ - Protocols: Structural subtyping for interface definitions
9
+ - Wrappers: Runtime enforcement of access constraints
10
+ - Composition: Combine multiple interfaces into larger ones
11
+ """
12
+
13
+ from typing import Protocol, TypeVar, Generic, Any, runtime_checkable, runtime_checkable
14
+ from typing import Optional, get_type_hints, Union
15
+ from dataclasses import dataclass, field
16
+ from functools import wraps
17
+ import inspect
18
+ import types
19
+
20
+
21
+ # ============================================================================
22
+ # Type Variables
23
+ # ============================================================================
24
+
25
+ T = TypeVar('T')
26
+ P = Protocol
27
+
28
+
29
+ # ============================================================================
30
+ # Core Protocol Definitions
31
+ # ============================================================================
32
+
33
+ @runtime_checkable
34
+ class Readable(Protocol[T]):
35
+ """
36
+ Protocol for read-only access to a typed value.
37
+
38
+ This protocol provides structural subtyping - any class with a matching
39
+ `get_value()` method will satisfy this protocol.
40
+
41
+ Type Parameters:
42
+ T: The type of value being read
43
+
44
+ Example:
45
+ >>> class Config:
46
+ ... def get_value(self) -> int:
47
+ ... return 42
48
+ >>> config: Readable[int] = Config()
49
+ >>> isinstance(config, Readable)
50
+ True
51
+ """
52
+ def get_value(self) -> T:
53
+ """Get the current value"""
54
+ ...
55
+
56
+
57
+ @runtime_checkable
58
+ class Writable(Protocol[T]):
59
+ """
60
+ Protocol for write access to a typed value.
61
+
62
+ This protocol provides structural subtyping - any class with a matching
63
+ `set_value()` method will satisfy this protocol.
64
+
65
+ Type Parameters:
66
+ T: The type of value being written
67
+
68
+ Example:
69
+ >>> class Storage:
70
+ ... def set_value(self, value: int) -> None:
71
+ ... self._value = value
72
+ >>> storage: Writable[int] = Storage()
73
+ >>> isinstance(storage, Writable)
74
+ True
75
+ """
76
+ def set_value(self, value: T) -> None:
77
+ """Set a new value"""
78
+ ...
79
+
80
+
81
+ @runtime_checkable
82
+ class FieldAccessor(Readable[T], Writable[T], Protocol[T]):
83
+ """
84
+ Protocol for read/write access to a typed value.
85
+
86
+ Combines both Readable and Writable protocols for full access.
87
+
88
+ Type Parameters:
89
+ T: The type of value being accessed
90
+
91
+ Example:
92
+ >>> class Counter:
93
+ ... def get_value(self) -> int:
94
+ ... return self._count
95
+ ... def set_value(self, value: int) -> None:
96
+ ... self._count = value
97
+ >>> counter: FieldAccessor[int] = Counter()
98
+ >>> isinstance(counter, FieldAccessor)
99
+ True
100
+ """
101
+ pass
102
+
103
+
104
+ @runtime_checkable
105
+ class BlackboardProtocol(Protocol):
106
+ """
107
+ Base blackboard protocol that all blackboards should implement.
108
+
109
+ This protocol provides structural subtyping for blackboard-like objects.
110
+ Any class with matching methods will satisfy this protocol, no inheritance
111
+ required.
112
+
113
+ Required Methods:
114
+ validate(): Check if blackboard state is valid
115
+ to_dict(): Convert blackboard to dictionary representation
116
+
117
+ Example:
118
+ >>> class MyBlackboard:
119
+ ... def validate(self) -> bool:
120
+ ... return True
121
+ ... def to_dict(self) -> dict[str, Any]:
122
+ ... return {"data": 42}
123
+ >>> bb: BlackboardProtocol = MyBlackboard()
124
+ >>> isinstance(bb, BlackboardProtocol)
125
+ True
126
+ """
127
+
128
+ def validate(self) -> bool:
129
+ """
130
+ Validate the blackboard state.
131
+
132
+ Returns:
133
+ True if blackboard state is valid, False otherwise
134
+ """
135
+ ...
136
+
137
+ def to_dict(self) -> dict[str, Any]:
138
+ """
139
+ Convert blackboard to dictionary representation.
140
+
141
+ Returns:
142
+ Dictionary representation of blackboard state
143
+ """
144
+ ...
145
+
146
+
147
+ # ============================================================================
148
+ # Interface Metadata
149
+ # ============================================================================
150
+
151
+ @dataclass
152
+ class FieldMetadata:
153
+ """
154
+ Metadata for blackboard interface fields.
155
+
156
+ Attributes:
157
+ name: Field name
158
+ type: Field type annotation
159
+ readonly: Whether field is read-only
160
+ required: Whether field is required (not Optional)
161
+ """
162
+ name: str
163
+ type: type
164
+ readonly: bool = False
165
+ required: bool = True
166
+
167
+
168
+ @dataclass
169
+ class InterfaceMetadata:
170
+ """
171
+ Metadata for a blackboard interface.
172
+
173
+ Attributes:
174
+ name: Interface name
175
+ fields: Dictionary of field metadata by field name
176
+ description: Optional description of the interface
177
+ bases: Base interfaces this interface extends
178
+ """
179
+ name: str
180
+ fields: dict[str, FieldMetadata] = field(default_factory=dict)
181
+ description: Optional[str] = None
182
+ bases: tuple[type, ...] = field(default_factory=tuple)
183
+
184
+ def get_readonly_fields(self) -> set[str]:
185
+ """Get set of read-only field names"""
186
+ return {name for name, meta in self.fields.items() if meta.readonly}
187
+
188
+ def get_readwrite_fields(self) -> set[str]:
189
+ """Get set of read-write field names"""
190
+ return {name for name, meta in self.fields.items() if not meta.readonly}
191
+
192
+
193
+ def _extract_interface_metadata(
194
+ interface_class: type,
195
+ readonly_fields: set[str] | None = None,
196
+ readwrite_fields: set[str] | None = None
197
+ ) -> InterfaceMetadata:
198
+ """
199
+ Extract metadata from an interface class.
200
+
201
+ Args:
202
+ interface_class: The interface class to analyze
203
+ readonly_fields: Set of field names that are read-only
204
+ readwrite_fields: Set of field names that are read-write
205
+
206
+ Returns:
207
+ InterfaceMetadata with extracted information
208
+ """
209
+ readonly_fields = readonly_fields or set()
210
+ readwrite_fields = readwrite_fields or set()
211
+
212
+ # Get type hints from the class
213
+ try:
214
+ hints = get_type_hints(interface_class)
215
+ except Exception:
216
+ hints = {}
217
+
218
+ # Build field metadata
219
+ fields = {}
220
+ for field_name, field_type in hints.items():
221
+ # Skip private attributes and methods
222
+ if field_name.startswith('_'):
223
+ continue
224
+
225
+ # Check if field is readonly or readwrite
226
+ is_readonly = field_name in readonly_fields
227
+
228
+ # Check if field is required (not Optional)
229
+ # Handle Union types (Optional[T] is Union[T, None])
230
+ is_required = True
231
+ if hasattr(field_type, '__origin__'):
232
+ # Check for Union (which includes Optional)
233
+ if field_type.__origin__ is Union:
234
+ # Union[X, None] means Optional[X]
235
+ args = getattr(field_type, '__args__', ())
236
+ is_required = type(None) not in args
237
+ elif str(field_type).startswith('typing.Union[') or 'Optional[' in str(field_type):
238
+ # String-based fallback for edge cases
239
+ is_required = 'None' not in str(field_type)
240
+
241
+ fields[field_name] = FieldMetadata(
242
+ name=field_name,
243
+ type=field_type,
244
+ readonly=is_readonly,
245
+ required=is_required
246
+ )
247
+
248
+ return InterfaceMetadata(
249
+ name=interface_class.__name__,
250
+ fields=fields,
251
+ description=interface_class.__doc__,
252
+ bases=interface_class.__bases__
253
+ )
254
+
255
+
256
+ # ============================================================================
257
+ # Protocol Helper Functions
258
+ # ============================================================================
259
+
260
+ def validate_implements(cls: type, protocol: type) -> bool:
261
+ """
262
+ Validate that a class implements a protocol.
263
+
264
+ Uses structural subtyping to check if the class has all required
265
+ methods and attributes defined by the protocol.
266
+
267
+ Args:
268
+ cls: The class to check
269
+ protocol: The protocol to check against
270
+
271
+ Returns:
272
+ True if cls implements protocol, False otherwise
273
+
274
+ Example:
275
+ >>> class MyBlackboard:
276
+ ... def validate(self) -> bool:
277
+ ... return True
278
+ ... def to_dict(self) -> dict:
279
+ ... return {}
280
+ >>> validate_implements(MyBlackboard, BlackboardProtocol)
281
+ True
282
+ >>> validate_implements(str, BlackboardProtocol)
283
+ False
284
+ """
285
+ if not isinstance(protocol, type) or not hasattr(protocol, '__protocol_attrs__'):
286
+ # Not a protocol, try anyway with isinstance
287
+ try:
288
+ return issubclass(cls, protocol) if isinstance(cls, type) else isinstance(cls, protocol)
289
+ except TypeError:
290
+ return False
291
+
292
+ # Check if protocol is runtime_checkable
293
+ if not hasattr(protocol, '__is_protocol__'):
294
+ return False
295
+
296
+ # Use isinstance if we have an instance, otherwise check structure
297
+ try:
298
+ # For protocols, we need to check if the class has all protocol attributes
299
+ for attr in getattr(protocol, '__protocol_attrs__', []):
300
+ if not hasattr(cls, attr):
301
+ return False
302
+ return True
303
+ except Exception:
304
+ return False
305
+
306
+
307
+ def get_interface_fields(interface: type) -> dict[str, type]:
308
+ """
309
+ Extract field type annotations from an interface.
310
+
311
+ Args:
312
+ interface: The interface class to extract fields from
313
+
314
+ Returns:
315
+ Dictionary mapping field names to their types
316
+
317
+ Example:
318
+ >>> class MyInterface(Protocol):
319
+ ... counter: int
320
+ ... name: str
321
+ >>> get_interface_fields(MyInterface)
322
+ {'counter': <class 'int'>, 'name': <class 'str'>}
323
+ """
324
+ try:
325
+ hints = get_type_hints(interface)
326
+ # Filter out private attributes
327
+ return {
328
+ name: typ for name, typ in hints.items()
329
+ if not name.startswith('_')
330
+ }
331
+ except Exception:
332
+ return {}
333
+
334
+
335
+ def create_interface_from_model(model_class: type, readonly_fields: set[str] | None = None) -> type[Protocol]:
336
+ """
337
+ Create a Protocol interface from a Pydantic model or dataclass.
338
+
339
+ This is a utility function for automatically generating interfaces from
340
+ existing model classes, supporting gradual migration.
341
+
342
+ Args:
343
+ model_class: The model class to create interface from
344
+ readonly_fields: Optional set of field names that should be read-only
345
+
346
+ Returns:
347
+ A Protocol class with the same fields as the model
348
+
349
+ Example:
350
+ >>> from pydantic import BaseModel
351
+ >>> class TaskModel(BaseModel):
352
+ ... max_tasks: int
353
+ ... tasks_completed: int
354
+ >>> TaskInterface = create_interface_from_model(
355
+ ... TaskModel,
356
+ ... readonly_fields={'max_tasks'}
357
+ ... )
358
+ >>> get_interface_fields(TaskInterface)
359
+ {'max_tasks': <class 'int'>, 'tasks_completed': <class 'int'>}
360
+ """
361
+ readonly_fields = readonly_fields or set()
362
+
363
+ # Get field annotations from the model
364
+ try:
365
+ hints = get_type_hints(model_class)
366
+ except Exception:
367
+ hints = {}
368
+
369
+ # Create protocol namespace
370
+ namespace = {
371
+ '__annotations__': {},
372
+ '__doc__': f"Auto-generated interface from {model_class.__name__}",
373
+ '_readonly_fields': readonly_fields,
374
+ }
375
+
376
+ # Add field annotations
377
+ for field_name, field_type in hints.items():
378
+ if not field_name.startswith('_'):
379
+ namespace['__annotations__'][field_name] = field_type
380
+
381
+ # Create the Protocol class
382
+ interface_name = f"{model_class.__name__}Interface"
383
+ interface_class = type(interface_name, (Protocol,), namespace)
384
+
385
+ return interface_class
386
+
387
+
388
+ # ============================================================================
389
+ # Public API Exports
390
+ # ============================================================================
391
+
392
+ __all__ = [
393
+ # Type Variables
394
+ "T",
395
+ "P",
396
+
397
+ # Core Protocols
398
+ "Readable",
399
+ "Writable",
400
+ "FieldAccessor",
401
+ "BlackboardProtocol",
402
+
403
+ # Metadata
404
+ "FieldMetadata",
405
+ "InterfaceMetadata",
406
+
407
+ # Helper Functions
408
+ "validate_implements",
409
+ "get_interface_fields",
410
+ "create_interface_from_model",
411
+ "_extract_interface_metadata",
412
+ ]