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,99 @@
1
+ from abc import ABC, abstractmethod
2
+ from datetime import datetime, timezone
3
+ from time import monotonic
4
+ import asyncio
5
+
6
+
7
+ class Timebase(ABC):
8
+ @abstractmethod
9
+ def now(self) -> float:
10
+ pass
11
+
12
+ @abstractmethod
13
+ async def sleep(self, duration: float):
14
+ pass
15
+
16
+ def advance(self):
17
+ pass
18
+
19
+ def reset(self):
20
+ pass
21
+
22
+ def set(self, val: float):
23
+ pass
24
+
25
+
26
+ class WallClock(Timebase):
27
+ def now(self) -> float:
28
+ return datetime.now().timestamp()
29
+
30
+ async def sleep(self, duration: float):
31
+ await asyncio.sleep(duration)
32
+
33
+
34
+ class UTCClock(Timebase):
35
+ def now(self) -> float:
36
+ return datetime.now(timezone.utc).timestamp()
37
+
38
+ async def sleep(self, duration: float):
39
+ await asyncio.sleep(duration)
40
+
41
+
42
+ class MonotonicClock(Timebase):
43
+ def now(self) -> float:
44
+ return monotonic()
45
+
46
+ async def sleep(self, duration: float):
47
+ await asyncio.sleep(duration)
48
+
49
+
50
+ class CycleClock(Timebase):
51
+ def __init__(self, stepsize: float = 1):
52
+ super().__init__()
53
+ self.cycles: float = 0
54
+ self._stepsize = stepsize
55
+ self._advance_evt = asyncio.Event()
56
+
57
+ async def sleep(self, duration: float):
58
+ target = self.cycles + duration
59
+ while self.cycles < target:
60
+ await self._advance_evt.wait()
61
+
62
+ def now(self) -> float:
63
+ return self.cycles
64
+
65
+ def advance(self):
66
+ self.cycles += self._stepsize
67
+
68
+ # Pulse the event to wake up sleepers
69
+ self._advance_evt.set()
70
+ self._advance_evt.clear()
71
+
72
+ def reset(self):
73
+ self.cycles = 0
74
+
75
+
76
+ class DictatedClock(Timebase):
77
+ def __init__(self, initial: float):
78
+ super().__init__()
79
+ self.value = initial
80
+ self._initial = initial
81
+ self._set_evt = asyncio.Event()
82
+
83
+ async def sleep(self, duration: float):
84
+ target = self.value + duration
85
+ while self.value < target:
86
+ await self._set_evt.wait()
87
+
88
+ def now(self) -> float:
89
+ return self.value
90
+
91
+ def reset(self):
92
+ self.value = self._initial
93
+
94
+ def set(self, val: float):
95
+ self.value = val
96
+
97
+ # Pulse the event to wake up sleepers
98
+ self._set_evt.set()
99
+ self._set_evt.clear()
@@ -0,0 +1,532 @@
1
+ """
2
+ Blackboard Wrapper Classes for Constrained Access
3
+
4
+ This module provides wrapper classes that enforce interface constraints at runtime,
5
+ enabling type-safe, constrained access to blackboard state.
6
+
7
+ Key concepts:
8
+ - ReadOnlyView: Enforces read-only access to specified fields
9
+ - WriteOnlyView: Enforces write-only access to specified fields
10
+ - ConstrainedView: Provides access to subset of fields with permissions
11
+ - CompositeView: Combines multiple views into single interface
12
+ """
13
+
14
+ from typing import TypeVar, Generic, Type, Protocol, Any, Set, Dict, Optional
15
+ from dataclasses import dataclass, field
16
+
17
+ from mycorrhizal.common.interfaces import (
18
+ InterfaceMetadata,
19
+ _extract_interface_metadata,
20
+ get_interface_fields,
21
+ )
22
+
23
+
24
+ # ============================================================================
25
+ # Type Variables
26
+ # ============================================================================
27
+
28
+ T = TypeVar('T')
29
+ P = TypeVar('P')
30
+
31
+
32
+ # ============================================================================
33
+ # Exceptions
34
+ # ============================================================================
35
+
36
+ class AccessControlError(Exception):
37
+ """Raised when access control is violated"""
38
+
39
+ def __init__(self, message: str, field_name: str, operation: str):
40
+ self.field_name = field_name
41
+ self.operation = operation
42
+ super().__init__(message)
43
+
44
+
45
+ # ============================================================================
46
+ # Base Wrapper Class
47
+ # ============================================================================
48
+
49
+ class BaseWrapper:
50
+ """
51
+ Base class for all wrapper classes.
52
+
53
+ Provides common functionality for attribute access interception
54
+ and error handling.
55
+ """
56
+
57
+ __slots__ = ('_bb', '_allowed_fields', '_private_attrs')
58
+
59
+ def __init__(
60
+ self,
61
+ bb: Any,
62
+ allowed_fields: Set[str],
63
+ private_attrs: Optional[Dict[str, Any]] = None
64
+ ):
65
+ """
66
+ Initialize the wrapper.
67
+
68
+ Args:
69
+ bb: The underlying blackboard object
70
+ allowed_fields: Set of field names that can be accessed
71
+ private_attrs: Optional dict of private attributes for the wrapper itself
72
+ """
73
+ object.__setattr__(self, '_bb', bb)
74
+ object.__setattr__(self, '_allowed_fields', allowed_fields)
75
+ object.__setattr__(self, '_private_attrs', private_attrs or {})
76
+
77
+ def __getattr__(self, name: str) -> Any:
78
+ """Get attribute from underlying blackboard"""
79
+ if name in object.__getattribute__(self, '_allowed_fields'):
80
+ bb = object.__getattribute__(self, '_bb')
81
+ try:
82
+ return getattr(bb, name)
83
+ except AttributeError:
84
+ # Re-raise with more helpful message
85
+ raise AttributeError(
86
+ f"Field '{name}' does not exist on the underlying blackboard"
87
+ ) from None
88
+ raise AttributeError(
89
+ f"Field '{name}' not accessible through this interface"
90
+ )
91
+
92
+ def __setattr__(self, name: str, value: Any) -> None:
93
+ """Set attribute on underlying blackboard (to be overridden by subclasses)"""
94
+ # Default implementation allows all subclasses to override
95
+ if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs'):
96
+ object.__setattr__(self, name, value)
97
+ else:
98
+ raise AttributeError(
99
+ f"Cannot set field '{name}' through this interface"
100
+ )
101
+
102
+ def __dir__(self) -> list[str]:
103
+ """Return list of accessible attributes"""
104
+ allowed = object.__getattribute__(self, '_allowed_fields')
105
+ return sorted(list(allowed))
106
+
107
+ def __repr__(self) -> str:
108
+ """Return string representation"""
109
+ cls_name = self.__class__.__name__
110
+ allowed = object.__getattribute__(self, '_allowed_fields')
111
+ return f"<{cls_name} fields={sorted(allowed)}>"
112
+
113
+
114
+ # ============================================================================
115
+ # Read-Only View
116
+ # ============================================================================
117
+
118
+ class ReadOnlyView(BaseWrapper, Generic[T]):
119
+ """
120
+ Read-only view of specific blackboard fields.
121
+
122
+ Provides read access to specified fields while preventing any modifications.
123
+ Useful for creating safe, immutable views of shared state.
124
+
125
+ Type Parameters:
126
+ T: The type of the interface being viewed
127
+
128
+ Example:
129
+ >>> class Blackboard:
130
+ ... def __init__(self):
131
+ ... self.config_value = 42
132
+ ... self.mutable_state = 0
133
+ >>> bb = Blackboard()
134
+ >>> view = ReadOnlyView(bb, {'config_value'})
135
+ >>> view.config_value # Read works
136
+ 42
137
+ >>> view.config_value = 100 # Write raises
138
+ AttributeError: Field 'config_value' is read-only
139
+ """
140
+
141
+ __slots__ = ()
142
+
143
+ def __setattr__(self, name: str, value: Any) -> None:
144
+ """Prevent all modifications"""
145
+ if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs'):
146
+ object.__setattr__(self, name, value)
147
+ else:
148
+ raise AccessControlError(
149
+ f"Field '{name}' is read-only and cannot be modified",
150
+ field_name=name,
151
+ operation='write'
152
+ )
153
+
154
+
155
+ # ============================================================================
156
+ # Write-Only View
157
+ # ============================================================================
158
+
159
+ class WriteOnlyView(BaseWrapper, Generic[T]):
160
+ """
161
+ Write-only view of specific blackboard fields.
162
+
163
+ Provides write access to specified fields while preventing reads.
164
+ Useful for output interfaces and sinks.
165
+
166
+ Type Parameters:
167
+ T: The type of the interface being viewed
168
+
169
+ Example:
170
+ >>> class Blackboard:
171
+ ... def __init__(self):
172
+ ... self.output_value = None
173
+ >>> bb = Blackboard()
174
+ >>> view = WriteOnlyView(bb, {'output_value'})
175
+ >>> view.output_value = 42 # Write works
176
+ >>> value = view.output_value # Read raises
177
+ AttributeError: Field 'output_value' is write-only (cannot read)
178
+ """
179
+
180
+ __slots__ = ()
181
+
182
+ def __getattr__(self, name: str) -> Any:
183
+ """Prevent reads"""
184
+ allowed = object.__getattribute__(self, '_allowed_fields')
185
+ if name in allowed:
186
+ raise AccessControlError(
187
+ f"Field '{name}' is write-only and cannot be read",
188
+ field_name=name,
189
+ operation='read'
190
+ )
191
+ raise AttributeError(
192
+ f"Field '{name}' not accessible through this interface"
193
+ )
194
+
195
+ def __setattr__(self, name: str, value: Any) -> None:
196
+ """Allow writes to specified fields"""
197
+ if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs'):
198
+ object.__setattr__(self, name, value)
199
+ elif name in object.__getattribute__(self, '_allowed_fields'):
200
+ bb = object.__getattribute__(self, '_bb')
201
+ setattr(bb, name, value)
202
+ else:
203
+ raise AttributeError(
204
+ f"Field '{name}' not accessible through this interface"
205
+ )
206
+
207
+
208
+ # ============================================================================
209
+ # Constrained View
210
+ # ============================================================================
211
+
212
+ class ConstrainedView(BaseWrapper, Generic[T, P]):
213
+ """
214
+ Constrained access view for interface-based access.
215
+
216
+ Provides access to a subset of fields with specified permissions.
217
+ Read-only fields cannot be modified, read-write fields can be both read and written.
218
+
219
+ Type Parameters:
220
+ T: The type of the interface being viewed
221
+ P: The Protocol type that defines the interface
222
+
223
+ Example:
224
+ >>> from mycorrhizal.common.wrappers import ConstrainedView
225
+ >>> class Blackboard:
226
+ ... def __init__(self):
227
+ ... self.max_tasks = 10
228
+ ... self.tasks_completed = 0
229
+ >>> bb = Blackboard()
230
+ >>> view = ConstrainedView(
231
+ ... bb,
232
+ ... allowed_fields={'max_tasks', 'tasks_completed'},
233
+ ... readonly_fields={'max_tasks'}
234
+ ... )
235
+ >>> view.max_tasks # Read works
236
+ 10
237
+ >>> view.max_tasks = 20 # Write raises (read-only)
238
+ AttributeError: Field 'max_tasks' is read-only
239
+ >>> view.tasks_completed = 5 # Write works (read-write)
240
+ >>> view.tasks_completed # Read works
241
+ 5
242
+ """
243
+
244
+ __slots__ = ('_readonly_fields',)
245
+
246
+ def __init__(
247
+ self,
248
+ bb: Any,
249
+ allowed_fields: Set[str],
250
+ readonly_fields: Set[str] | None = None,
251
+ private_attrs: Optional[Dict[str, Any]] = None
252
+ ):
253
+ """
254
+ Initialize the constrained view.
255
+
256
+ Args:
257
+ bb: The underlying blackboard object
258
+ allowed_fields: Set of field names that can be accessed
259
+ readonly_fields: Set of field names that are read-only
260
+ private_attrs: Optional dict of private attributes for the wrapper itself
261
+ """
262
+ readonly_fields = readonly_fields or set()
263
+ super().__init__(bb, allowed_fields, private_attrs)
264
+ object.__setattr__(self, '_readonly_fields', readonly_fields)
265
+
266
+ def __getattr__(self, name: str) -> Any:
267
+ """Get attribute from underlying blackboard"""
268
+ allowed = object.__getattribute__(self, '_allowed_fields')
269
+ if name in allowed:
270
+ bb = object.__getattribute__(self, '_bb')
271
+ return getattr(bb, name)
272
+ raise AttributeError(
273
+ f"Field '{name}' not accessible through this interface"
274
+ )
275
+
276
+ def __setattr__(self, name: str, value: Any) -> None:
277
+ """Set attribute with read-only enforcement"""
278
+ if name.startswith('_') or name in ('_bb', '_allowed_fields', '_readonly_fields', '_private_attrs'):
279
+ object.__setattr__(self, name, value)
280
+ return
281
+
282
+ allowed = object.__getattribute__(self, '_allowed_fields')
283
+ if name not in allowed:
284
+ raise AttributeError(
285
+ f"Field '{name}' not accessible through this interface"
286
+ )
287
+
288
+ readonly = object.__getattribute__(self, '_readonly_fields')
289
+ if name in readonly:
290
+ raise AccessControlError(
291
+ f"Field '{name}' is read-only and cannot be modified",
292
+ field_name=name,
293
+ operation='write'
294
+ )
295
+
296
+ bb = object.__getattribute__(self, '_bb')
297
+ setattr(bb, name, value)
298
+
299
+
300
+ # ============================================================================
301
+ # Composite View
302
+ # ============================================================================
303
+
304
+ class CompositeView(BaseWrapper, Generic[T]):
305
+ """
306
+ Combines multiple views into a single interface.
307
+
308
+ Allows composing multiple constrained views into one, enabling
309
+ complex access patterns by combining simpler views.
310
+
311
+ Type Parameters:
312
+ T: The type of the composite interface
313
+
314
+ Example:
315
+ >>> config_view = ReadOnlyView(bb, {'max_tasks'})
316
+ >>> state_view = ConstrainedView(bb, {'tasks_completed'}, readonly_fields=set())
317
+ >>> composite = CompositeView.combine([config_view, state_view])
318
+ >>> composite.max_tasks # Read from config_view
319
+ 10
320
+ >>> composite.tasks_completed = 5 # Write through state_view
321
+ >>> composite.max_tasks = 20 # Raises (read-only)
322
+ AttributeError: Field 'max_tasks' is read-only
323
+ """
324
+
325
+ __slots__ = ('_views',)
326
+
327
+ def __init__(self, bb: Any, views: list[BaseWrapper]):
328
+ """
329
+ Initialize composite view from multiple views.
330
+
331
+ Args:
332
+ bb: The underlying blackboard object
333
+ views: List of wrapper objects to compose
334
+ """
335
+ # Collect all allowed fields from all views
336
+ allowed_fields: Set[str] = set()
337
+ private_attrs: Dict[str, Any] = {'_views': views}
338
+
339
+ for view in views:
340
+ allowed_fields.update(view._allowed_fields)
341
+
342
+ super().__init__(bb, allowed_fields, private_attrs)
343
+ object.__setattr__(self, '_views', views)
344
+
345
+ @classmethod
346
+ def combine(cls, views: list[BaseWrapper]) -> 'CompositeView[T]':
347
+ """
348
+ Combine multiple views into a composite view.
349
+
350
+ Args:
351
+ views: List of wrapper objects to combine
352
+
353
+ Returns:
354
+ A new CompositeView that combines all input views
355
+
356
+ Raises:
357
+ ValueError: If views reference different blackboards
358
+ """
359
+ if not views:
360
+ raise ValueError("Cannot combine empty list of views")
361
+
362
+ # Get the blackboard from the first view
363
+ bb = views[0]._bb
364
+
365
+ # Verify all views reference the same blackboard
366
+ for view in views:
367
+ if view._bb is not bb:
368
+ raise ValueError("All views must reference the same blackboard")
369
+
370
+ return cls(bb, views)
371
+
372
+ def __getattr__(self, name: str) -> Any:
373
+ """Delegate read to first view that has this field"""
374
+ views = object.__getattribute__(self, '_views')
375
+ for view in views:
376
+ if name in view._allowed_fields:
377
+ return getattr(view, name)
378
+
379
+ raise AttributeError(
380
+ f"Field '{name}' not accessible through any composed view"
381
+ )
382
+
383
+ def __setattr__(self, name: str, value: Any) -> None:
384
+ """Delegate write to first view that has this field"""
385
+ if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs', '_views'):
386
+ object.__setattr__(self, name, value)
387
+ return
388
+
389
+ views = object.__getattribute__(self, '_views')
390
+ for view in views:
391
+ if name in view._allowed_fields:
392
+ setattr(view, name, value)
393
+ return
394
+
395
+ raise AttributeError(
396
+ f"Field '{name}' not accessible through any composed view"
397
+ )
398
+
399
+
400
+ # ============================================================================
401
+ # Factory Functions
402
+ # ============================================================================
403
+
404
+ def create_readonly_view(bb: Any, fields: tuple[str, ...]) -> ReadOnlyView:
405
+ """
406
+ Create a read-only view.
407
+
408
+ Args:
409
+ bb: The blackboard to wrap
410
+ fields: Tuple of field names for read-only access
411
+
412
+ Returns:
413
+ A ReadOnlyView instance
414
+
415
+ Example:
416
+ >>> view = create_readonly_view(bb, ('config', 'max_tasks'))
417
+ """
418
+ return ReadOnlyView(bb, set(fields))
419
+
420
+
421
+ def create_constrained_view(
422
+ bb: Any,
423
+ allowed_fields: tuple[str, ...],
424
+ readonly_fields: tuple[str, ...] = ()
425
+ ) -> ConstrainedView:
426
+ """
427
+ Create a constrained view.
428
+
429
+ Args:
430
+ bb: The blackboard to wrap
431
+ allowed_fields: Tuple of field names that can be accessed
432
+ readonly_fields: Tuple of field names that are read-only
433
+
434
+ Returns:
435
+ A ConstrainedView instance
436
+
437
+ Example:
438
+ >>> view = create_constrained_view(
439
+ ... bb,
440
+ ... ('max_tasks', 'tasks_completed'),
441
+ ... ('max_tasks',)
442
+ ... )
443
+ """
444
+ return ConstrainedView(bb, set(allowed_fields), set(readonly_fields))
445
+
446
+
447
+ def create_view_from_protocol(
448
+ bb: Any,
449
+ protocol: Type[P],
450
+ readonly_fields: Set[str] | None = None
451
+ ) -> ConstrainedView:
452
+ """
453
+ Create a constrained view from a Protocol interface.
454
+
455
+ Extracts field information from the Protocol and creates a view
456
+ that enforces the interface constraints.
457
+
458
+ Args:
459
+ bb: The blackboard to wrap
460
+ protocol: The Protocol class defining the interface
461
+ readonly_fields: Optional set of field names that should be read-only.
462
+ If not provided, will try to extract from protocol metadata.
463
+
464
+ Returns:
465
+ A ConstrainedView instance
466
+
467
+ Example:
468
+ >>> class TaskInterface(Protocol):
469
+ ... max_tasks: int
470
+ ... tasks_completed: int
471
+ >>> view = create_view_from_protocol(bb, TaskInterface, {'max_tasks'})
472
+ """
473
+ # Extract fields from protocol
474
+ fields = get_interface_fields(protocol)
475
+ allowed_fields = set(fields.keys())
476
+
477
+ # If readonly_fields not provided, try to extract from protocol
478
+ if readonly_fields is None:
479
+ readonly_fields = getattr(protocol, '_readonly_fields', set())
480
+
481
+ return ConstrainedView(bb, allowed_fields, readonly_fields)
482
+
483
+
484
+ # ============================================================================
485
+ # View Factory for Protocol-based Views
486
+ # ============================================================================
487
+
488
+ def View(bb: Any, protocol: Type[P]) -> Any:
489
+ """
490
+ Factory function for creating typed views from protocols.
491
+
492
+ This provides a clean, type-safe API for creating constrained views
493
+ with full type hint support.
494
+
495
+ Args:
496
+ bb: The blackboard to wrap
497
+ protocol: The Protocol class defining the interface
498
+
499
+ Returns:
500
+ A wrapper that implements the protocol
501
+
502
+ Example:
503
+ >>> class ConfigInterface(Protocol):
504
+ ... max_tasks: int
505
+ ... interval: float
506
+ >>> view: ConfigInterface = View(bb, ConfigInterface)
507
+ >>> # Type checker knows view has max_tasks and interval
508
+ """
509
+ return create_view_from_protocol(bb, protocol)
510
+
511
+
512
+ # ============================================================================
513
+ # Public API Exports
514
+ # ============================================================================
515
+
516
+ __all__ = [
517
+ # Exceptions
518
+ "AccessControlError",
519
+
520
+ # Wrapper Classes
521
+ "BaseWrapper",
522
+ "ReadOnlyView",
523
+ "WriteOnlyView",
524
+ "ConstrainedView",
525
+ "CompositeView",
526
+
527
+ # Factory Functions
528
+ "create_readonly_view",
529
+ "create_constrained_view",
530
+ "create_view_from_protocol",
531
+ "View",
532
+ ]
File without changes