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.
Files changed (41) hide show
  1. mycorrhizal/_version.py +1 -1
  2. mycorrhizal/common/__init__.py +15 -3
  3. mycorrhizal/common/cache.py +114 -0
  4. mycorrhizal/common/compilation.py +263 -0
  5. mycorrhizal/common/interface_detection.py +159 -0
  6. mycorrhizal/common/interfaces.py +3 -50
  7. mycorrhizal/common/mermaid.py +124 -0
  8. mycorrhizal/common/wrappers.py +1 -1
  9. mycorrhizal/hypha/core/builder.py +11 -1
  10. mycorrhizal/hypha/core/runtime.py +242 -107
  11. mycorrhizal/mycelium/__init__.py +174 -0
  12. mycorrhizal/mycelium/core.py +619 -0
  13. mycorrhizal/mycelium/exceptions.py +30 -0
  14. mycorrhizal/mycelium/hypha_bridge.py +1143 -0
  15. mycorrhizal/mycelium/instance.py +440 -0
  16. mycorrhizal/mycelium/pn_context.py +276 -0
  17. mycorrhizal/mycelium/runner.py +165 -0
  18. mycorrhizal/mycelium/spores_integration.py +655 -0
  19. mycorrhizal/mycelium/tree_builder.py +102 -0
  20. mycorrhizal/mycelium/tree_spec.py +197 -0
  21. mycorrhizal/rhizomorph/README.md +82 -33
  22. mycorrhizal/rhizomorph/core.py +287 -119
  23. mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
  24. mycorrhizal/{enoki → septum}/core.py +326 -100
  25. mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
  26. mycorrhizal/{enoki → septum}/util.py +44 -21
  27. mycorrhizal/spores/__init__.py +3 -3
  28. mycorrhizal/spores/core.py +149 -28
  29. mycorrhizal/spores/dsl/__init__.py +8 -8
  30. mycorrhizal/spores/dsl/hypha.py +3 -15
  31. mycorrhizal/spores/dsl/rhizomorph.py +3 -11
  32. mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
  33. mycorrhizal/spores/encoder/json.py +21 -12
  34. mycorrhizal/spores/extraction.py +14 -11
  35. mycorrhizal/spores/models.py +53 -20
  36. mycorrhizal-0.2.1.dist-info/METADATA +335 -0
  37. mycorrhizal-0.2.1.dist-info/RECORD +54 -0
  38. mycorrhizal-0.1.2.dist-info/METADATA +0 -198
  39. mycorrhizal-0.1.2.dist-info/RECORD +0 -39
  40. /mycorrhizal/{enoki → septum}/__init__.py +0 -0
  41. {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.2"
1
+ version = "0.2.1"
@@ -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
- - Enoki (State Machines)
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
@@ -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, Enoki).
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
- # Protocol Helper Functions
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",