mycorrhizal 0.1.2__py3-none-any.whl → 0.2.1__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.1.dist-info/METADATA +335 -0
- mycorrhizal-0.2.1.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.1.dist-info}/WHEEL +0 -0
mycorrhizal/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "0.1
|
|
1
|
+
version = "0.2.1"
|
mycorrhizal/common/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ Common utilities and interfaces for Mycorrhizal.
|
|
|
4
4
|
This module provides shared components used across all three DSL systems:
|
|
5
5
|
- Hypha (Petri Nets)
|
|
6
6
|
- Rhizomorph (Behavior Trees)
|
|
7
|
-
-
|
|
7
|
+
- Septum (State Machines)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Import interfaces for easy access
|
|
@@ -15,7 +15,6 @@ from mycorrhizal.common.interfaces import (
|
|
|
15
15
|
BlackboardProtocol,
|
|
16
16
|
FieldMetadata,
|
|
17
17
|
InterfaceMetadata,
|
|
18
|
-
validate_implements,
|
|
19
18
|
get_interface_fields,
|
|
20
19
|
create_interface_from_model,
|
|
21
20
|
)
|
|
@@ -39,6 +38,14 @@ from mycorrhizal.common.interface_builder import (
|
|
|
39
38
|
FieldSpec,
|
|
40
39
|
)
|
|
41
40
|
|
|
41
|
+
# Import mermaid formatting utilities for easy access
|
|
42
|
+
from mycorrhizal.common.mermaid import (
|
|
43
|
+
format_state_node,
|
|
44
|
+
format_transition,
|
|
45
|
+
format_subgraph,
|
|
46
|
+
format_comment,
|
|
47
|
+
)
|
|
48
|
+
|
|
42
49
|
__all__ = [
|
|
43
50
|
# Interfaces
|
|
44
51
|
"Readable",
|
|
@@ -47,7 +54,6 @@ __all__ = [
|
|
|
47
54
|
"BlackboardProtocol",
|
|
48
55
|
"FieldMetadata",
|
|
49
56
|
"InterfaceMetadata",
|
|
50
|
-
"validate_implements",
|
|
51
57
|
"get_interface_fields",
|
|
52
58
|
"create_interface_from_model",
|
|
53
59
|
|
|
@@ -65,4 +71,10 @@ __all__ = [
|
|
|
65
71
|
# Interface Builder
|
|
66
72
|
"blackboard_interface",
|
|
67
73
|
"FieldSpec",
|
|
74
|
+
|
|
75
|
+
# Mermaid formatting
|
|
76
|
+
"format_state_node",
|
|
77
|
+
"format_transition",
|
|
78
|
+
"format_subgraph",
|
|
79
|
+
"format_comment",
|
|
68
80
|
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared cache implementations for mycorrhizal runtimes.
|
|
3
|
+
|
|
4
|
+
Provides LRU cache implementations for use across Hypha, Rhizomorph, and Septum.
|
|
5
|
+
"""
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from typing import Any, Dict, Tuple, Type, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InterfaceViewCache:
|
|
11
|
+
"""
|
|
12
|
+
LRU cache for interface views bounded by maximum size.
|
|
13
|
+
|
|
14
|
+
This cache stores interface view objects keyed by (blackboard_id, interface_type, readonly_fields).
|
|
15
|
+
Uses OrderedDict for manual LRU tracking with bounded size.
|
|
16
|
+
|
|
17
|
+
Thread-safety: Safe for single reader/writer (async environments).
|
|
18
|
+
For multi-threaded environments, external synchronization is required.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
cache = InterfaceViewCache(maxsize=256)
|
|
22
|
+
view = cache.get_or_create(bb_id, interface_type, readonly_fields, creator_func)
|
|
23
|
+
stats = cache.get_stats()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, maxsize: int = 256):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the LRU cache.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
maxsize: Maximum number of entries to cache. Defaults to 256.
|
|
32
|
+
"""
|
|
33
|
+
self._maxsize = maxsize
|
|
34
|
+
self._cache: OrderedDict[Tuple[int, Type, Tuple[str, ...]], Any] = OrderedDict()
|
|
35
|
+
self._hits = 0
|
|
36
|
+
self._misses = 0
|
|
37
|
+
|
|
38
|
+
def get_or_create(
|
|
39
|
+
self,
|
|
40
|
+
bb_id: int,
|
|
41
|
+
interface_type: Type,
|
|
42
|
+
readonly_fields: Optional[Tuple[str, ...]],
|
|
43
|
+
creator_func: callable
|
|
44
|
+
) -> Any:
|
|
45
|
+
"""
|
|
46
|
+
Get cached view or create using provided function.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
bb_id: ID of blackboard (from id() builtin)
|
|
50
|
+
interface_type: The interface protocol class
|
|
51
|
+
readonly_fields: Tuple of readonly field names
|
|
52
|
+
creator_func: Function that creates the view if not cached
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The cached or newly created interface view
|
|
56
|
+
"""
|
|
57
|
+
# Create cache key from hashable components
|
|
58
|
+
readonly_fields_tuple = tuple(sorted(readonly_fields)) if readonly_fields else ()
|
|
59
|
+
cache_key = (bb_id, interface_type, readonly_fields_tuple)
|
|
60
|
+
|
|
61
|
+
# Check cache
|
|
62
|
+
if cache_key in self._cache:
|
|
63
|
+
# Cache hit - move to end (most recently used)
|
|
64
|
+
self._cache.move_to_end(cache_key)
|
|
65
|
+
self._hits += 1
|
|
66
|
+
return self._cache[cache_key]
|
|
67
|
+
|
|
68
|
+
# Cache miss - create view and cache it
|
|
69
|
+
self._misses += 1
|
|
70
|
+
view = creator_func()
|
|
71
|
+
|
|
72
|
+
# Add to cache and move to end
|
|
73
|
+
self._cache[cache_key] = view
|
|
74
|
+
self._cache.move_to_end(cache_key)
|
|
75
|
+
|
|
76
|
+
# Evict oldest entry if over limit
|
|
77
|
+
if len(self._cache) > self._maxsize:
|
|
78
|
+
self._cache.popitem(last=False)
|
|
79
|
+
|
|
80
|
+
return view
|
|
81
|
+
|
|
82
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Get cache statistics for monitoring and debugging.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Dict with current cache size, maxsize, and hit/miss counts
|
|
88
|
+
"""
|
|
89
|
+
return {
|
|
90
|
+
'cached_views': len(self._cache),
|
|
91
|
+
'maxsize': self._maxsize,
|
|
92
|
+
'hits': self._hits,
|
|
93
|
+
'misses': self._misses,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def clear(self) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Clear all cached entries and reset statistics.
|
|
99
|
+
|
|
100
|
+
Useful for testing and memory management.
|
|
101
|
+
"""
|
|
102
|
+
self._cache.clear()
|
|
103
|
+
self._hits = 0
|
|
104
|
+
self._misses = 0
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def maxsize(self) -> int:
|
|
108
|
+
"""Get maximum cache size."""
|
|
109
|
+
return self._maxsize
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def size(self) -> int:
|
|
113
|
+
"""Get current cache size."""
|
|
114
|
+
return len(self._cache)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Common Compilation Cache for DSL Systems
|
|
4
|
+
|
|
5
|
+
This module provides a shared compilation cache for all Mycorrhizal DSL systems
|
|
6
|
+
(Rhizomorph, Septum, Hypha) to eliminate redundant runtime type inspection overhead.
|
|
7
|
+
|
|
8
|
+
The compilation cache stores pre-computed interface metadata for functions, including:
|
|
9
|
+
- Interface type detection (Protocol-based)
|
|
10
|
+
- Readonly and readwrite field extraction
|
|
11
|
+
- Parameter type analysis
|
|
12
|
+
|
|
13
|
+
This module uses type-based blackboard detection instead of parameter name matching,
|
|
14
|
+
making it more robust and type-safe.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import inspect
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import (
|
|
22
|
+
Any,
|
|
23
|
+
Callable,
|
|
24
|
+
Dict,
|
|
25
|
+
Optional,
|
|
26
|
+
Set,
|
|
27
|
+
Type,
|
|
28
|
+
get_type_hints,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ======================================================================================
|
|
33
|
+
# Compiled Metadata
|
|
34
|
+
# ======================================================================================
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class CompiledMetadata:
|
|
39
|
+
"""
|
|
40
|
+
Pre-computed interface metadata for a function.
|
|
41
|
+
|
|
42
|
+
This dataclass stores the results of type inspection so that we don't need
|
|
43
|
+
to inspect function signatures at runtime. Metadata is compiled lazily on
|
|
44
|
+
first use and then cached globally.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
has_interface: Whether the function has an interface type hint
|
|
48
|
+
interface_type: The interface Protocol type (if any)
|
|
49
|
+
readonly_fields: Set of readonly field names from the interface
|
|
50
|
+
readwrite_fields: Set of readwrite field names from the interface
|
|
51
|
+
bb_param_index: Index of the blackboard parameter in function signature
|
|
52
|
+
"""
|
|
53
|
+
has_interface: bool
|
|
54
|
+
interface_type: Optional[Type]
|
|
55
|
+
readonly_fields: Optional[Set[str]] = None
|
|
56
|
+
readwrite_fields: Optional[Set[str]] = None
|
|
57
|
+
bb_param_index: int = 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ======================================================================================
|
|
61
|
+
# Global Compilation Cache
|
|
62
|
+
# ======================================================================================
|
|
63
|
+
|
|
64
|
+
_compilation_cache: Dict[int, CompiledMetadata] = {}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _clear_compilation_cache() -> None:
|
|
68
|
+
"""Clear the global compilation cache. Useful for testing."""
|
|
69
|
+
global _compilation_cache
|
|
70
|
+
_compilation_cache.clear()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get_compilation_cache_size() -> int:
|
|
74
|
+
"""Get the current size of the compilation cache (for debugging)."""
|
|
75
|
+
return len(_compilation_cache)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ======================================================================================
|
|
79
|
+
# Compilation Functions
|
|
80
|
+
# ======================================================================================
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _compile_function_metadata(
|
|
84
|
+
func: Callable,
|
|
85
|
+
bb_param_name: str = "bb",
|
|
86
|
+
ctx_param_name: str = "ctx",
|
|
87
|
+
) -> CompiledMetadata:
|
|
88
|
+
"""
|
|
89
|
+
Compile function metadata by inspecting type hints and extracting interface information.
|
|
90
|
+
|
|
91
|
+
This function performs the expensive type inspection operations once, so that
|
|
92
|
+
the compiled metadata can be cached and reused without repeated inspection.
|
|
93
|
+
|
|
94
|
+
The function uses type-based blackboard detection:
|
|
95
|
+
- Checks if the parameter type is a Protocol with interface metadata
|
|
96
|
+
- Does NOT rely on parameter names (more type-safe)
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
func: The function to inspect
|
|
100
|
+
bb_param_name: Expected blackboard parameter name (for Rhizomorph/Hypha)
|
|
101
|
+
ctx_param_name: Expected context parameter name (for Septum)
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
CompiledMetadata with pre-extracted interface information
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
TypeError: If the function is not callable
|
|
108
|
+
NameError: If type hints reference undefined types
|
|
109
|
+
AttributeError: If type hints reference undefined attributes
|
|
110
|
+
"""
|
|
111
|
+
# Validate input
|
|
112
|
+
if not callable(func):
|
|
113
|
+
raise TypeError(f"Expected callable, got {type(func).__name__}")
|
|
114
|
+
|
|
115
|
+
# Get function name for error messages (handle callables without __name__)
|
|
116
|
+
func_name = getattr(func, '__name__', repr(func))
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
sig = inspect.signature(func)
|
|
120
|
+
|
|
121
|
+
# Try to get type hints, but handle callable objects that don't support it
|
|
122
|
+
try:
|
|
123
|
+
hints = get_type_hints(func)
|
|
124
|
+
except TypeError:
|
|
125
|
+
# Callable objects (like TransitionRuntimeWrapper) may not support get_type_hints
|
|
126
|
+
# Return metadata without interface in this case
|
|
127
|
+
return CompiledMetadata(
|
|
128
|
+
has_interface=False,
|
|
129
|
+
interface_type=None,
|
|
130
|
+
readonly_fields=None,
|
|
131
|
+
readwrite_fields=None,
|
|
132
|
+
bb_param_index=0,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
params = list(sig.parameters.values())
|
|
136
|
+
|
|
137
|
+
has_interface = False
|
|
138
|
+
interface_type = None
|
|
139
|
+
readonly_fields = None
|
|
140
|
+
readwrite_fields = None
|
|
141
|
+
bb_param_index = 0
|
|
142
|
+
|
|
143
|
+
# Find the blackboard/context parameter by TYPE, not by NAME
|
|
144
|
+
for idx, param in enumerate(params):
|
|
145
|
+
param_type = hints.get(param.name)
|
|
146
|
+
|
|
147
|
+
if param_type is None:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Check for Protocol with interface metadata
|
|
151
|
+
# This works for both direct Protocol types and generic types like SharedContext[Interface]
|
|
152
|
+
if _has_interface_metadata(param_type):
|
|
153
|
+
has_interface = True
|
|
154
|
+
interface_type = param_type
|
|
155
|
+
bb_param_index = idx
|
|
156
|
+
|
|
157
|
+
# Extract interface fields
|
|
158
|
+
# For generic types, extract the type argument
|
|
159
|
+
actual_type = _extract_generic_type_arg(param_type)
|
|
160
|
+
if actual_type and _has_interface_metadata(actual_type):
|
|
161
|
+
# For generic types, use the extracted type for the interface
|
|
162
|
+
interface_type = actual_type
|
|
163
|
+
readonly_fields = getattr(actual_type, '_readonly_fields', set())
|
|
164
|
+
readwrite_fields = getattr(actual_type, '_readwrite_fields', set())
|
|
165
|
+
else:
|
|
166
|
+
# For direct Protocol types
|
|
167
|
+
readonly_fields = getattr(param_type, '_readonly_fields', set())
|
|
168
|
+
readwrite_fields = getattr(param_type, '_readwrite_fields', set())
|
|
169
|
+
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
return CompiledMetadata(
|
|
173
|
+
has_interface=has_interface,
|
|
174
|
+
interface_type=interface_type,
|
|
175
|
+
readonly_fields=readonly_fields,
|
|
176
|
+
readwrite_fields=readwrite_fields,
|
|
177
|
+
bb_param_index=bb_param_index,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
except (TypeError, NameError, AttributeError) as e:
|
|
181
|
+
# Re-raise expected exceptions with better context
|
|
182
|
+
raise type(e)(
|
|
183
|
+
f"Failed to compile metadata for {func_name}: {e}"
|
|
184
|
+
) from e
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _has_interface_metadata(type_hint: Any) -> bool:
|
|
188
|
+
"""
|
|
189
|
+
Check if a type hint has interface metadata (readonly/readwrite fields).
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
type_hint: A type hint from get_type_hints()
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
True if the type has _readonly_fields attribute (interface metadata)
|
|
196
|
+
"""
|
|
197
|
+
# For generic types (like SharedContext[Interface]), extract the type argument
|
|
198
|
+
actual_type = _extract_generic_type_arg(type_hint)
|
|
199
|
+
if actual_type:
|
|
200
|
+
return hasattr(actual_type, '_readonly_fields')
|
|
201
|
+
|
|
202
|
+
# For direct Protocol types
|
|
203
|
+
return hasattr(type_hint, '_readonly_fields')
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _extract_generic_type_arg(type_hint: Any) -> Optional[Type]:
|
|
207
|
+
"""
|
|
208
|
+
Extract the type argument from a generic type.
|
|
209
|
+
|
|
210
|
+
For example:
|
|
211
|
+
- SharedContext[MyInterface] -> MyInterface
|
|
212
|
+
- list[MyType] -> MyType
|
|
213
|
+
- dict[str, int] -> None (not a single-type generic)
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
type_hint: A type hint from get_type_hints()
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
The extracted type argument, or None if not a single-type generic
|
|
220
|
+
"""
|
|
221
|
+
# Check if type has __args__ (generic type)
|
|
222
|
+
if not hasattr(type_hint, '__args__'):
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
args = type_hint.__args__
|
|
226
|
+
if not args or len(args) != 1:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
return args[0]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _get_compiled_metadata(func: Callable) -> CompiledMetadata:
|
|
233
|
+
"""
|
|
234
|
+
Get compiled metadata for a function, using write-through cache with EAFP pattern.
|
|
235
|
+
|
|
236
|
+
This function checks the global compilation cache first using try/except
|
|
237
|
+
(EAFP - Easier to Ask for Forgiveness than Permission), which is faster
|
|
238
|
+
than inclusion checks in Python.
|
|
239
|
+
|
|
240
|
+
If the function's metadata hasn't been compiled yet, it compiles it and
|
|
241
|
+
writes it to the cache.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
func: The function to get metadata for
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
CompiledMetadata for the function
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
TypeError: If func is not callable
|
|
251
|
+
ValueError: If type hints are malformed
|
|
252
|
+
AttributeError: If type hints reference undefined types
|
|
253
|
+
"""
|
|
254
|
+
func_id = id(func)
|
|
255
|
+
|
|
256
|
+
# EAFP pattern: try to get from cache, handle miss
|
|
257
|
+
try:
|
|
258
|
+
return _compilation_cache[func_id]
|
|
259
|
+
except KeyError:
|
|
260
|
+
# Cache miss - compile and write through
|
|
261
|
+
metadata = _compile_function_metadata(func)
|
|
262
|
+
_compilation_cache[func_id] = metadata
|
|
263
|
+
return metadata
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Automatic interface view creation for Mycorrhizal DSLs.
|
|
3
|
+
|
|
4
|
+
This module provides utilities to automatically detect interface types
|
|
5
|
+
in function signatures and create constrained views, ensuring type-safe
|
|
6
|
+
access control without requiring manual view creation.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any, Callable, Type, TypeVar, get_type_hints
|
|
9
|
+
from inspect import signature, Parameter
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
|
|
12
|
+
from mycorrhizal.common.wrappers import create_view_from_protocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Cache for interface detection to avoid repeated introspection
|
|
16
|
+
_interface_cache: dict[Type, bool] = {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_interface_type(t: Type) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
Check if a type is a blackboard interface.
|
|
22
|
+
|
|
23
|
+
Interfaces are marked by the presence of _readonly_fields and
|
|
24
|
+
_readwrite_fields attributes added by @blackboard_interface.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
t: Type to check
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if t is a blackboard interface, False otherwise
|
|
31
|
+
"""
|
|
32
|
+
if t in _interface_cache:
|
|
33
|
+
return _interface_cache[t]
|
|
34
|
+
|
|
35
|
+
result = (
|
|
36
|
+
hasattr(t, '_readonly_fields') and
|
|
37
|
+
hasattr(t, '_readwrite_fields') and
|
|
38
|
+
callable(t) # Interfaces are also protocols
|
|
39
|
+
)
|
|
40
|
+
_interface_cache[t] = result
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_interface_parameters(func: Callable) -> dict[str, Type]:
|
|
45
|
+
"""
|
|
46
|
+
Extract parameters with interface types from a function signature.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
func: Function to inspect
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dictionary mapping parameter names to their interface types
|
|
53
|
+
"""
|
|
54
|
+
interface_params = {}
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
type_hints = get_type_hints(func)
|
|
58
|
+
sig = signature(func)
|
|
59
|
+
|
|
60
|
+
for param_name, param in sig.parameters.items():
|
|
61
|
+
if param_name == 'bb':
|
|
62
|
+
# Skip the bb parameter itself (will be replaced)
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if param_name in type_hints:
|
|
66
|
+
param_type = type_hints[param_name]
|
|
67
|
+
if is_interface_type(param_type):
|
|
68
|
+
interface_params[param_name] = param_type
|
|
69
|
+
except (ValueError, TypeError):
|
|
70
|
+
# get_type_hints can fail for some functions
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
return interface_params
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def create_interface_views(
|
|
77
|
+
bb: Any,
|
|
78
|
+
func: Callable,
|
|
79
|
+
view_cache: dict[int, dict[Type, Any]] | None = None
|
|
80
|
+
) -> dict[str, tuple[Any, Type]]:
|
|
81
|
+
"""
|
|
82
|
+
Create interface views for a function based on its signature.
|
|
83
|
+
|
|
84
|
+
Inspects the function's type hints and automatically creates
|
|
85
|
+
constrained views for any parameters typed as interfaces.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
bb: The blackboard to wrap
|
|
89
|
+
func: The function whose signature will be inspected
|
|
90
|
+
view_cache: Optional cache mapping (bb_id, interface_type) -> view
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary mapping parameter names to (view, interface_type) tuples
|
|
94
|
+
"""
|
|
95
|
+
interface_views = {}
|
|
96
|
+
|
|
97
|
+
if view_cache is None:
|
|
98
|
+
view_cache = {}
|
|
99
|
+
|
|
100
|
+
bb_id = id(bb)
|
|
101
|
+
|
|
102
|
+
interface_params = get_interface_parameters(func)
|
|
103
|
+
|
|
104
|
+
for param_name, interface_type in interface_params.items():
|
|
105
|
+
# Check cache first
|
|
106
|
+
cache_key = (bb_id, interface_type)
|
|
107
|
+
if cache_key in view_cache:
|
|
108
|
+
view = view_cache[cache_key]
|
|
109
|
+
else:
|
|
110
|
+
# Create new view
|
|
111
|
+
readonly_fields = getattr(interface_type, '_readonly_fields', set())
|
|
112
|
+
view = create_view_from_protocol(
|
|
113
|
+
bb,
|
|
114
|
+
interface_type,
|
|
115
|
+
readonly_fields=readonly_fields
|
|
116
|
+
)
|
|
117
|
+
view_cache[cache_key] = view
|
|
118
|
+
|
|
119
|
+
interface_views[param_name] = (view, interface_type)
|
|
120
|
+
|
|
121
|
+
return interface_views
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def prepare_function_args(
|
|
125
|
+
bb: Any,
|
|
126
|
+
func: Callable,
|
|
127
|
+
view_cache: dict[int, dict[Type, Any]] | None = None
|
|
128
|
+
) -> dict[str, Any]:
|
|
129
|
+
"""
|
|
130
|
+
Prepare function arguments, creating interface views as needed.
|
|
131
|
+
|
|
132
|
+
This function:
|
|
133
|
+
1. Inspects the function signature for interface-typed parameters
|
|
134
|
+
2. Creates constrained views for those parameters
|
|
135
|
+
3. Returns a dict of arguments ready to pass to the function
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
bb: The blackboard
|
|
139
|
+
func: The function to call
|
|
140
|
+
view_cache: Optional cache for created views
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Dictionary of parameter names to values (views or original values)
|
|
144
|
+
"""
|
|
145
|
+
interface_views = create_interface_views(bb, func, view_cache)
|
|
146
|
+
return {name: view for name, (view, _) in interface_views.items()}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def should_wrap_with_interface(func: Callable) -> bool:
|
|
150
|
+
"""
|
|
151
|
+
Check if a function has any interface-typed parameters.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
func: Function to check
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
True if function has interface parameters, False otherwise
|
|
158
|
+
"""
|
|
159
|
+
return len(get_interface_parameters(func)) > 0
|
mycorrhizal/common/interfaces.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Blackboard Interface System
|
|
3
3
|
|
|
4
4
|
This module provides Protocol-based interfaces for type-safe, constrained access
|
|
5
|
-
to blackboard state across Mycorrhizal's three DSL systems (Hypha, Rhizomorph,
|
|
5
|
+
to blackboard state across Mycorrhizal's three DSL systems (Hypha, Rhizomorph, Septum).
|
|
6
6
|
|
|
7
7
|
Key concepts:
|
|
8
8
|
- Protocols: Structural subtyping for interface definitions
|
|
@@ -10,6 +10,7 @@ Key concepts:
|
|
|
10
10
|
- Composition: Combine multiple interfaces into larger ones
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import typing
|
|
13
14
|
from typing import Protocol, TypeVar, Generic, Any, runtime_checkable, runtime_checkable
|
|
14
15
|
from typing import Optional, get_type_hints, Union
|
|
15
16
|
from dataclasses import dataclass, field
|
|
@@ -254,56 +255,9 @@ def _extract_interface_metadata(
|
|
|
254
255
|
|
|
255
256
|
|
|
256
257
|
# ============================================================================
|
|
257
|
-
#
|
|
258
|
+
# Interface Field Extraction
|
|
258
259
|
# ============================================================================
|
|
259
260
|
|
|
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
261
|
def get_interface_fields(interface: type) -> dict[str, type]:
|
|
308
262
|
"""
|
|
309
263
|
Extract field type annotations from an interface.
|
|
@@ -405,7 +359,6 @@ __all__ = [
|
|
|
405
359
|
"InterfaceMetadata",
|
|
406
360
|
|
|
407
361
|
# Helper Functions
|
|
408
|
-
"validate_implements",
|
|
409
362
|
"get_interface_fields",
|
|
410
363
|
"create_interface_from_model",
|
|
411
364
|
"_extract_interface_metadata",
|