Skip to content

Common Utilities API Reference

Timebase

mycorrhizal.common.timebase

Blackboard Interfaces

mycorrhizal.common.interfaces

Blackboard Interface System

This module provides Protocol-based interfaces for type-safe, constrained access to blackboard state across Mycorrhizal's three DSL systems (Hypha, Rhizomorph, Septum).

Key concepts: - Protocols: Structural subtyping for interface definitions - Wrappers: Runtime enforcement of access constraints - Composition: Combine multiple interfaces into larger ones

Readable

Bases: Protocol[T]

Protocol for read-only access to a typed value.

This protocol provides structural subtyping - any class with a matching get_value() method will satisfy this protocol.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of value being read

required
Example

class Config: ... def get_value(self) -> int: ... return 42 config: Readable[int] = Config() isinstance(config, Readable) True

get_value

get_value() -> T

Get the current value

Source code in src/mycorrhizal/common/interfaces.py
def get_value(self) -> T:
    """Get the current value"""
    ...

Writable

Bases: Protocol[T]

Protocol for write access to a typed value.

This protocol provides structural subtyping - any class with a matching set_value() method will satisfy this protocol.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of value being written

required
Example

class Storage: ... def set_value(self, value: int) -> None: ... self._value = value storage: Writable[int] = Storage() isinstance(storage, Writable) True

set_value

set_value(value: T) -> None

Set a new value

Source code in src/mycorrhizal/common/interfaces.py
def set_value(self, value: T) -> None:
    """Set a new value"""
    ...

FieldAccessor

Bases: Readable[T], Writable[T], Protocol[T]

Protocol for read/write access to a typed value.

Combines both Readable and Writable protocols for full access.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of value being accessed

required
Example

class Counter: ... def get_value(self) -> int: ... return self._count ... def set_value(self, value: int) -> None: ... self._count = value counter: FieldAccessor[int] = Counter() isinstance(counter, FieldAccessor) True

BlackboardProtocol

Bases: Protocol

Base blackboard protocol that all blackboards should implement.

This protocol provides structural subtyping for blackboard-like objects. Any class with matching methods will satisfy this protocol, no inheritance required.

Required Methods

validate(): Check if blackboard state is valid to_dict(): Convert blackboard to dictionary representation

Example

class MyBlackboard: ... def validate(self) -> bool: ... return True ... def to_dict(self) -> dict[str, Any]: ... return {"data": 42} bb: BlackboardProtocol = MyBlackboard() isinstance(bb, BlackboardProtocol) True

validate

validate() -> bool

Validate the blackboard state.

Returns:

Type Description
bool

True if blackboard state is valid, False otherwise

Source code in src/mycorrhizal/common/interfaces.py
def validate(self) -> bool:
    """
    Validate the blackboard state.

    Returns:
        True if blackboard state is valid, False otherwise
    """
    ...

to_dict

to_dict() -> dict[str, Any]

Convert blackboard to dictionary representation.

Returns:

Type Description
dict[str, Any]

Dictionary representation of blackboard state

Source code in src/mycorrhizal/common/interfaces.py
def to_dict(self) -> dict[str, Any]:
    """
    Convert blackboard to dictionary representation.

    Returns:
        Dictionary representation of blackboard state
    """
    ...

FieldMetadata dataclass

FieldMetadata(name: str, type: type, readonly: bool = False, required: bool = True)

Metadata for blackboard interface fields.

Attributes:

Name Type Description
name str

Field name

type type

Field type annotation

readonly bool

Whether field is read-only

required bool

Whether field is required (not Optional)

InterfaceMetadata dataclass

InterfaceMetadata(name: str, fields: dict[str, FieldMetadata] = dict(), description: Optional[str] = None, bases: tuple[type, ...] = tuple())

Metadata for a blackboard interface.

Attributes:

Name Type Description
name str

Interface name

fields dict[str, FieldMetadata]

Dictionary of field metadata by field name

description Optional[str]

Optional description of the interface

bases tuple[type, ...]

Base interfaces this interface extends

get_readonly_fields

get_readonly_fields() -> set[str]

Get set of read-only field names

Source code in src/mycorrhizal/common/interfaces.py
def get_readonly_fields(self) -> set[str]:
    """Get set of read-only field names"""
    return {name for name, meta in self.fields.items() if meta.readonly}

get_readwrite_fields

get_readwrite_fields() -> set[str]

Get set of read-write field names

Source code in src/mycorrhizal/common/interfaces.py
def get_readwrite_fields(self) -> set[str]:
    """Get set of read-write field names"""
    return {name for name, meta in self.fields.items() if not meta.readonly}

_extract_interface_metadata

_extract_interface_metadata(interface_class: type, readonly_fields: set[str] | None = None, readwrite_fields: set[str] | None = None) -> InterfaceMetadata

Extract metadata from an interface class.

Parameters:

Name Type Description Default
interface_class type

The interface class to analyze

required
readonly_fields set[str] | None

Set of field names that are read-only

None
readwrite_fields set[str] | None

Set of field names that are read-write

None

Returns:

Type Description
InterfaceMetadata

InterfaceMetadata with extracted information

Source code in src/mycorrhizal/common/interfaces.py
def _extract_interface_metadata(
    interface_class: type,
    readonly_fields: set[str] | None = None,
    readwrite_fields: set[str] | None = None
) -> InterfaceMetadata:
    """
    Extract metadata from an interface class.

    Args:
        interface_class: The interface class to analyze
        readonly_fields: Set of field names that are read-only
        readwrite_fields: Set of field names that are read-write

    Returns:
        InterfaceMetadata with extracted information
    """
    readonly_fields = readonly_fields or set()
    readwrite_fields = readwrite_fields or set()

    # Get type hints from the class
    try:
        hints = get_type_hints(interface_class)
    except Exception:
        hints = {}

    # Build field metadata
    fields = {}
    for field_name, field_type in hints.items():
        # Skip private attributes and methods
        if field_name.startswith('_'):
            continue

        # Check if field is readonly or readwrite
        is_readonly = field_name in readonly_fields

        # Check if field is required (not Optional)
        # Handle Union types (Optional[T] is Union[T, None])
        is_required = True
        if hasattr(field_type, '__origin__'):
            # Check for Union (which includes Optional)
            if field_type.__origin__ is Union:
                # Union[X, None] means Optional[X]
                args = getattr(field_type, '__args__', ())
                is_required = type(None) not in args
        elif str(field_type).startswith('typing.Union[') or 'Optional[' in str(field_type):
            # String-based fallback for edge cases
            is_required = 'None' not in str(field_type)

        fields[field_name] = FieldMetadata(
            name=field_name,
            type=field_type,
            readonly=is_readonly,
            required=is_required
        )

    return InterfaceMetadata(
        name=interface_class.__name__,
        fields=fields,
        description=interface_class.__doc__,
        bases=interface_class.__bases__
    )

get_interface_fields

get_interface_fields(interface: type) -> dict[str, type]

Extract field type annotations from an interface.

Parameters:

Name Type Description Default
interface type

The interface class to extract fields from

required

Returns:

Type Description
dict[str, type]

Dictionary mapping field names to their types

Example

class MyInterface(Protocol): ... counter: int ... name: str get_interface_fields(MyInterface)

Source code in src/mycorrhizal/common/interfaces.py
def get_interface_fields(interface: type) -> dict[str, type]:
    """
    Extract field type annotations from an interface.

    Args:
        interface: The interface class to extract fields from

    Returns:
        Dictionary mapping field names to their types

    Example:
        >>> class MyInterface(Protocol):
        ...     counter: int
        ...     name: str
        >>> get_interface_fields(MyInterface)
        {'counter': <class 'int'>, 'name': <class 'str'>}
    """
    try:
        hints = get_type_hints(interface)
        # Filter out private attributes
        return {
            name: typ for name, typ in hints.items()
            if not name.startswith('_')
        }
    except Exception:
        return {}

create_interface_from_model

create_interface_from_model(model_class: type, readonly_fields: set[str] | None = None) -> type[Protocol]

Create a Protocol interface from a Pydantic model or dataclass.

This is a utility function for automatically generating interfaces from existing model classes, supporting gradual migration.

Parameters:

Name Type Description Default
model_class type

The model class to create interface from

required
readonly_fields set[str] | None

Optional set of field names that should be read-only

None

Returns:

Type Description
type[Protocol]

A Protocol class with the same fields as the model

Example

from pydantic import BaseModel class TaskModel(BaseModel): ... max_tasks: int ... tasks_completed: int TaskInterface = create_interface_from_model( ... TaskModel, ... readonly_fields={'max_tasks'} ... ) get_interface_fields(TaskInterface)

Source code in src/mycorrhizal/common/interfaces.py
def create_interface_from_model(model_class: type, readonly_fields: set[str] | None = None) -> type[Protocol]:
    """
    Create a Protocol interface from a Pydantic model or dataclass.

    This is a utility function for automatically generating interfaces from
    existing model classes, supporting gradual migration.

    Args:
        model_class: The model class to create interface from
        readonly_fields: Optional set of field names that should be read-only

    Returns:
        A Protocol class with the same fields as the model

    Example:
        >>> from pydantic import BaseModel
        >>> class TaskModel(BaseModel):
        ...     max_tasks: int
        ...     tasks_completed: int
        >>> TaskInterface = create_interface_from_model(
        ...     TaskModel,
        ...     readonly_fields={'max_tasks'}
        ... )
        >>> get_interface_fields(TaskInterface)
        {'max_tasks': <class 'int'>, 'tasks_completed': <class 'int'>}
    """
    readonly_fields = readonly_fields or set()

    # Get field annotations from the model
    try:
        hints = get_type_hints(model_class)
    except Exception:
        hints = {}

    # Create protocol namespace
    namespace = {
        '__annotations__': {},
        '__doc__': f"Auto-generated interface from {model_class.__name__}",
        '_readonly_fields': readonly_fields,
    }

    # Add field annotations
    for field_name, field_type in hints.items():
        if not field_name.startswith('_'):
            namespace['__annotations__'][field_name] = field_type

    # Create the Protocol class
    interface_name = f"{model_class.__name__}Interface"
    interface_class = type(interface_name, (Protocol,), namespace)

    return interface_class

Runtime Wrappers

mycorrhizal.common.wrappers

Blackboard Wrapper Classes for Constrained Access

This module provides wrapper classes that enforce interface constraints at runtime, enabling type-safe, constrained access to blackboard state.

Key concepts: - ReadOnlyView: Enforces read-only access to specified fields - WriteOnlyView: Enforces write-only access to specified fields - ConstrainedView: Provides access to subset of fields with permissions - CompositeView: Combines multiple views into single interface

AccessControlError

AccessControlError(message: str, field_name: str, operation: str)

Bases: Exception

Raised when access control is violated

Source code in src/mycorrhizal/common/wrappers.py
def __init__(self, message: str, field_name: str, operation: str):
    self.field_name = field_name
    self.operation = operation
    super().__init__(message)

BaseWrapper

BaseWrapper(bb: Any, allowed_fields: Set[str], private_attrs: Optional[Dict[str, Any]] = None)

Base class for all wrapper classes.

Provides common functionality for attribute access interception and error handling.

Initialize the wrapper.

Parameters:

Name Type Description Default
bb Any

The underlying blackboard object

required
allowed_fields Set[str]

Set of field names that can be accessed

required
private_attrs Optional[Dict[str, Any]]

Optional dict of private attributes for the wrapper itself

None
Source code in src/mycorrhizal/common/wrappers.py
def __init__(
    self,
    bb: Any,
    allowed_fields: Set[str],
    private_attrs: Optional[Dict[str, Any]] = None
):
    """
    Initialize the wrapper.

    Args:
        bb: The underlying blackboard object
        allowed_fields: Set of field names that can be accessed
        private_attrs: Optional dict of private attributes for the wrapper itself
    """
    object.__setattr__(self, '_bb', bb)
    object.__setattr__(self, '_allowed_fields', allowed_fields)
    object.__setattr__(self, '_private_attrs', private_attrs or {})

__getattr__

__getattr__(name: str) -> Any

Get attribute from underlying blackboard

Source code in src/mycorrhizal/common/wrappers.py
def __getattr__(self, name: str) -> Any:
    """Get attribute from underlying blackboard"""
    if name in object.__getattribute__(self, '_allowed_fields'):
        bb = object.__getattribute__(self, '_bb')
        try:
            return getattr(bb, name)
        except AttributeError:
            # Re-raise with more helpful message
            raise AttributeError(
                f"Field '{name}' does not exist on the underlying blackboard"
            ) from None
    raise AttributeError(
        f"Field '{name}' not accessible through this interface"
    )

__setattr__

__setattr__(name: str, value: Any) -> None

Set attribute on underlying blackboard (to be overridden by subclasses)

Source code in src/mycorrhizal/common/wrappers.py
def __setattr__(self, name: str, value: Any) -> None:
    """Set attribute on underlying blackboard (to be overridden by subclasses)"""
    # Default implementation allows all subclasses to override
    if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs'):
        object.__setattr__(self, name, value)
    else:
        raise AttributeError(
            f"Cannot set field '{name}' through this interface"
        )

__dir__

__dir__() -> list[str]

Return list of accessible attributes

Source code in src/mycorrhizal/common/wrappers.py
def __dir__(self) -> list[str]:
    """Return list of accessible attributes"""
    allowed = object.__getattribute__(self, '_allowed_fields')
    return sorted(list(allowed))

__repr__

__repr__() -> str

Return string representation

Source code in src/mycorrhizal/common/wrappers.py
def __repr__(self) -> str:
    """Return string representation"""
    cls_name = self.__class__.__name__
    allowed = object.__getattribute__(self, '_allowed_fields')
    return f"<{cls_name} fields={sorted(allowed)}>"

ReadOnlyView

ReadOnlyView(bb: Any, allowed_fields: Set[str], private_attrs: Optional[Dict[str, Any]] = None)

Bases: BaseWrapper, Generic[T]

Read-only view of specific blackboard fields.

Provides read access to specified fields while preventing any modifications. Useful for creating safe, immutable views of shared state.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of the interface being viewed

required
Example

class Blackboard: ... def init(self): ... self.config_value = 42 ... self.mutable_state = 0 bb = Blackboard() view = ReadOnlyView(bb, {'config_value'}) view.config_value # Read works 42 view.config_value = 100 # Write raises AttributeError: Field 'config_value' is read-only

Source code in src/mycorrhizal/common/wrappers.py
def __init__(
    self,
    bb: Any,
    allowed_fields: Set[str],
    private_attrs: Optional[Dict[str, Any]] = None
):
    """
    Initialize the wrapper.

    Args:
        bb: The underlying blackboard object
        allowed_fields: Set of field names that can be accessed
        private_attrs: Optional dict of private attributes for the wrapper itself
    """
    object.__setattr__(self, '_bb', bb)
    object.__setattr__(self, '_allowed_fields', allowed_fields)
    object.__setattr__(self, '_private_attrs', private_attrs or {})

__setattr__

__setattr__(name: str, value: Any) -> None

Prevent all modifications

Source code in src/mycorrhizal/common/wrappers.py
def __setattr__(self, name: str, value: Any) -> None:
    """Prevent all modifications"""
    if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs'):
        object.__setattr__(self, name, value)
    else:
        raise AccessControlError(
            f"Field '{name}' is read-only and cannot be modified",
            field_name=name,
            operation='write'
        )

WriteOnlyView

WriteOnlyView(bb: Any, allowed_fields: Set[str], private_attrs: Optional[Dict[str, Any]] = None)

Bases: BaseWrapper, Generic[T]

Write-only view of specific blackboard fields.

Provides write access to specified fields while preventing reads. Useful for output interfaces and sinks.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of the interface being viewed

required
Example

class Blackboard: ... def init(self): ... self.output_value = None bb = Blackboard() view = WriteOnlyView(bb, {'output_value'}) view.output_value = 42 # Write works value = view.output_value # Read raises AttributeError: Field 'output_value' is write-only (cannot read)

Source code in src/mycorrhizal/common/wrappers.py
def __init__(
    self,
    bb: Any,
    allowed_fields: Set[str],
    private_attrs: Optional[Dict[str, Any]] = None
):
    """
    Initialize the wrapper.

    Args:
        bb: The underlying blackboard object
        allowed_fields: Set of field names that can be accessed
        private_attrs: Optional dict of private attributes for the wrapper itself
    """
    object.__setattr__(self, '_bb', bb)
    object.__setattr__(self, '_allowed_fields', allowed_fields)
    object.__setattr__(self, '_private_attrs', private_attrs or {})

__getattr__

__getattr__(name: str) -> Any

Prevent reads

Source code in src/mycorrhizal/common/wrappers.py
def __getattr__(self, name: str) -> Any:
    """Prevent reads"""
    allowed = object.__getattribute__(self, '_allowed_fields')
    if name in allowed:
        raise AccessControlError(
            f"Field '{name}' is write-only and cannot be read",
            field_name=name,
            operation='read'
        )
    raise AttributeError(
        f"Field '{name}' not accessible through this interface"
    )

__setattr__

__setattr__(name: str, value: Any) -> None

Allow writes to specified fields

Source code in src/mycorrhizal/common/wrappers.py
def __setattr__(self, name: str, value: Any) -> None:
    """Allow writes to specified fields"""
    if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs'):
        object.__setattr__(self, name, value)
    elif name in object.__getattribute__(self, '_allowed_fields'):
        bb = object.__getattribute__(self, '_bb')
        setattr(bb, name, value)
    else:
        raise AttributeError(
            f"Field '{name}' not accessible through this interface"
        )

ConstrainedView

ConstrainedView(bb: Any, allowed_fields: Set[str], readonly_fields: Set[str] | None = None, private_attrs: Optional[Dict[str, Any]] = None)

Bases: BaseWrapper, Generic[T, P]

Constrained access view for interface-based access.

Provides access to a subset of fields with specified permissions. Read-only fields cannot be modified, read-write fields can be both read and written.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of the interface being viewed

required
P

The Protocol type that defines the interface

required
Example

from mycorrhizal.common.wrappers import ConstrainedView class Blackboard: ... def init(self): ... self.max_tasks = 10 ... self.tasks_completed = 0 bb = Blackboard() view = ConstrainedView( ... bb, ... allowed_fields={'max_tasks', 'tasks_completed'}, ... readonly_fields={'max_tasks'} ... ) view.max_tasks # Read works 10 view.max_tasks = 20 # Write raises (read-only) AttributeError: Field 'max_tasks' is read-only view.tasks_completed = 5 # Write works (read-write) view.tasks_completed # Read works 5

Initialize the constrained view.

Parameters:

Name Type Description Default
bb Any

The underlying blackboard object

required
allowed_fields Set[str]

Set of field names that can be accessed

required
readonly_fields Set[str] | None

Set of field names that are read-only

None
private_attrs Optional[Dict[str, Any]]

Optional dict of private attributes for the wrapper itself

None
Source code in src/mycorrhizal/common/wrappers.py
def __init__(
    self,
    bb: Any,
    allowed_fields: Set[str],
    readonly_fields: Set[str] | None = None,
    private_attrs: Optional[Dict[str, Any]] = None
):
    """
    Initialize the constrained view.

    Args:
        bb: The underlying blackboard object
        allowed_fields: Set of field names that can be accessed
        readonly_fields: Set of field names that are read-only
        private_attrs: Optional dict of private attributes for the wrapper itself
    """
    readonly_fields = readonly_fields or set()
    super().__init__(bb, allowed_fields, private_attrs)
    object.__setattr__(self, '_readonly_fields', readonly_fields)

__getattr__

__getattr__(name: str) -> Any

Get attribute from underlying blackboard

Source code in src/mycorrhizal/common/wrappers.py
def __getattr__(self, name: str) -> Any:
    """Get attribute from underlying blackboard"""
    allowed = object.__getattribute__(self, '_allowed_fields')
    if name in allowed:
        bb = object.__getattribute__(self, '_bb')
        return getattr(bb, name)
    raise AttributeError(
        f"Field '{name}' not accessible through this interface"
    )

__setattr__

__setattr__(name: str, value: Any) -> None

Set attribute with read-only enforcement

Source code in src/mycorrhizal/common/wrappers.py
def __setattr__(self, name: str, value: Any) -> None:
    """Set attribute with read-only enforcement"""
    if name.startswith('_') or name in ('_bb', '_allowed_fields', '_readonly_fields', '_private_attrs'):
        object.__setattr__(self, name, value)
        return

    allowed = object.__getattribute__(self, '_allowed_fields')
    if name not in allowed:
        raise AttributeError(
            f"Field '{name}' not accessible through this interface"
        )

    readonly = object.__getattribute__(self, '_readonly_fields')
    if name in readonly:
        raise AccessControlError(
            f"Field '{name}' is read-only and cannot be modified",
            field_name=name,
            operation='write'
        )

    bb = object.__getattribute__(self, '_bb')
    setattr(bb, name, value)

CompositeView

CompositeView(bb: Any, views: list[BaseWrapper])

Bases: BaseWrapper, Generic[T]

Combines multiple views into a single interface.

Allows composing multiple constrained views into one, enabling complex access patterns by combining simpler views.

Class Type Parameters:

Name Bound or Constraints Description Default
T

The type of the composite interface

required
Example

config_view = ReadOnlyView(bb, {'max_tasks'}) state_view = ConstrainedView(bb, {'tasks_completed'}, readonly_fields=set()) composite = CompositeView.combine([config_view, state_view]) composite.max_tasks # Read from config_view 10 composite.tasks_completed = 5 # Write through state_view composite.max_tasks = 20 # Raises (read-only) AttributeError: Field 'max_tasks' is read-only

Initialize composite view from multiple views.

Parameters:

Name Type Description Default
bb Any

The underlying blackboard object

required
views list[BaseWrapper]

List of wrapper objects to compose

required
Source code in src/mycorrhizal/common/wrappers.py
def __init__(self, bb: Any, views: list[BaseWrapper]):
    """
    Initialize composite view from multiple views.

    Args:
        bb: The underlying blackboard object
        views: List of wrapper objects to compose
    """
    # Collect all allowed fields from all views
    allowed_fields: Set[str] = set()
    private_attrs: Dict[str, Any] = {'_views': views}

    for view in views:
        allowed_fields.update(view._allowed_fields)

    super().__init__(bb, allowed_fields, private_attrs)
    object.__setattr__(self, '_views', views)

combine classmethod

combine(views: list[BaseWrapper]) -> CompositeView[T]

Combine multiple views into a composite view.

Parameters:

Name Type Description Default
views list[BaseWrapper]

List of wrapper objects to combine

required

Returns:

Type Description
CompositeView[T]

A new CompositeView that combines all input views

Raises:

Type Description
ValueError

If views reference different blackboards

Source code in src/mycorrhizal/common/wrappers.py
@classmethod
def combine(cls, views: list[BaseWrapper]) -> 'CompositeView[T]':
    """
    Combine multiple views into a composite view.

    Args:
        views: List of wrapper objects to combine

    Returns:
        A new CompositeView that combines all input views

    Raises:
        ValueError: If views reference different blackboards
    """
    if not views:
        raise ValueError("Cannot combine empty list of views")

    # Get the blackboard from the first view
    bb = views[0]._bb

    # Verify all views reference the same blackboard
    for view in views:
        if view._bb is not bb:
            raise ValueError("All views must reference the same blackboard")

    return cls(bb, views)

__getattr__

__getattr__(name: str) -> Any

Delegate read to first view that has this field

Source code in src/mycorrhizal/common/wrappers.py
def __getattr__(self, name: str) -> Any:
    """Delegate read to first view that has this field"""
    views = object.__getattribute__(self, '_views')
    for view in views:
        if name in view._allowed_fields:
            return getattr(view, name)

    raise AttributeError(
        f"Field '{name}' not accessible through any composed view"
    )

__setattr__

__setattr__(name: str, value: Any) -> None

Delegate write to first view that has this field

Source code in src/mycorrhizal/common/wrappers.py
def __setattr__(self, name: str, value: Any) -> None:
    """Delegate write to first view that has this field"""
    if name.startswith('_') or name in ('_bb', '_allowed_fields', '_private_attrs', '_views'):
        object.__setattr__(self, name, value)
        return

    views = object.__getattribute__(self, '_views')
    for view in views:
        if name in view._allowed_fields:
            setattr(view, name, value)
            return

    raise AttributeError(
        f"Field '{name}' not accessible through any composed view"
    )

create_readonly_view

create_readonly_view(bb: Any, fields: tuple[str, ...]) -> ReadOnlyView

Create a read-only view.

Parameters:

Name Type Description Default
bb Any

The blackboard to wrap

required
fields tuple[str, ...]

Tuple of field names for read-only access

required

Returns:

Type Description
ReadOnlyView

A ReadOnlyView instance

Example

view = create_readonly_view(bb, ('config', 'max_tasks'))

Source code in src/mycorrhizal/common/wrappers.py
def create_readonly_view(bb: Any, fields: tuple[str, ...]) -> ReadOnlyView:
    """
    Create a read-only view.

    Args:
        bb: The blackboard to wrap
        fields: Tuple of field names for read-only access

    Returns:
        A ReadOnlyView instance

    Example:
        >>> view = create_readonly_view(bb, ('config', 'max_tasks'))
    """
    return ReadOnlyView(bb, set(fields))

create_constrained_view

create_constrained_view(bb: Any, allowed_fields: tuple[str, ...], readonly_fields: tuple[str, ...] = ()) -> ConstrainedView

Create a constrained view.

Parameters:

Name Type Description Default
bb Any

The blackboard to wrap

required
allowed_fields tuple[str, ...]

Tuple of field names that can be accessed

required
readonly_fields tuple[str, ...]

Tuple of field names that are read-only

()

Returns:

Type Description
ConstrainedView

A ConstrainedView instance

Example

view = create_constrained_view( ... bb, ... ('max_tasks', 'tasks_completed'), ... ('max_tasks',) ... )

Source code in src/mycorrhizal/common/wrappers.py
def create_constrained_view(
    bb: Any,
    allowed_fields: tuple[str, ...],
    readonly_fields: tuple[str, ...] = ()
) -> ConstrainedView:
    """
    Create a constrained view.

    Args:
        bb: The blackboard to wrap
        allowed_fields: Tuple of field names that can be accessed
        readonly_fields: Tuple of field names that are read-only

    Returns:
        A ConstrainedView instance

    Example:
        >>> view = create_constrained_view(
        ...     bb,
        ...     ('max_tasks', 'tasks_completed'),
        ...     ('max_tasks',)
        ... )
    """
    return ConstrainedView(bb, set(allowed_fields), set(readonly_fields))

create_view_from_protocol

create_view_from_protocol(bb: Any, protocol: Type[P], readonly_fields: Set[str] | None = None) -> ConstrainedView

Create a constrained view from a Protocol interface.

Extracts field information from the Protocol and creates a view that enforces the interface constraints.

Parameters:

Name Type Description Default
bb Any

The blackboard to wrap

required
protocol Type[P]

The Protocol class defining the interface

required
readonly_fields Set[str] | None

Optional set of field names that should be read-only. If not provided, will try to extract from protocol metadata.

None

Returns:

Type Description
ConstrainedView

A ConstrainedView instance

Example

class TaskInterface(Protocol): ... max_tasks: int ... tasks_completed: int view = create_view_from_protocol(bb, TaskInterface, {'max_tasks'})

Source code in src/mycorrhizal/common/wrappers.py
def create_view_from_protocol(
    bb: Any,
    protocol: Type[P],
    readonly_fields: Set[str] | None = None
) -> ConstrainedView:
    """
    Create a constrained view from a Protocol interface.

    Extracts field information from the Protocol and creates a view
    that enforces the interface constraints.

    Args:
        bb: The blackboard to wrap
        protocol: The Protocol class defining the interface
        readonly_fields: Optional set of field names that should be read-only.
                       If not provided, will try to extract from protocol metadata.

    Returns:
        A ConstrainedView instance

    Example:
        >>> class TaskInterface(Protocol):
        ...     max_tasks: int
        ...     tasks_completed: int
        >>> view = create_view_from_protocol(bb, TaskInterface, {'max_tasks'})
    """
    # Extract fields from protocol
    fields = get_interface_fields(protocol)
    allowed_fields = set(fields.keys())

    # If readonly_fields not provided, try to extract from protocol
    if readonly_fields is None:
        readonly_fields = getattr(protocol, '_readonly_fields', set())

    return ConstrainedView(bb, allowed_fields, readonly_fields)

View

View(bb: Any, protocol: Type[P]) -> Any

Factory function for creating typed views from protocols.

This provides a clean, type-safe API for creating constrained views with full type hint support.

Parameters:

Name Type Description Default
bb Any

The blackboard to wrap

required
protocol Type[P]

The Protocol class defining the interface

required

Returns:

Type Description
Any

A wrapper that implements the protocol

Example

class ConfigInterface(Protocol): ... max_tasks: int ... interval: float view: ConfigInterface = View(bb, ConfigInterface)

Type checker knows view has max_tasks and interval

Source code in src/mycorrhizal/common/wrappers.py
def View(bb: Any, protocol: Type[P]) -> Any:
    """
    Factory function for creating typed views from protocols.

    This provides a clean, type-safe API for creating constrained views
    with full type hint support.

    Args:
        bb: The blackboard to wrap
        protocol: The Protocol class defining the interface

    Returns:
        A wrapper that implements the protocol

    Example:
        >>> class ConfigInterface(Protocol):
        ...     max_tasks: int
        ...     interval: float
        >>> view: ConfigInterface = View(bb, ConfigInterface)
        >>> # Type checker knows view has max_tasks and interval
    """
    return create_view_from_protocol(bb, protocol)

Interface Builder

mycorrhizal.common.interface_builder

Interface Builder DSL for Blackboard Interfaces

This module provides a declarative DSL for defining blackboard interfaces using PEP 593 Annotated types and access control markers.

Key concepts: - @blackboard_interface - Decorator for defining interfaces - readonly/readwrite markers for field access control - Annotated types for clean, Pythonic field definitions

_AccessMarker

Base class for access control markers

readonly

Bases: _AccessMarker

Marker for readonly fields in blackboard interfaces.

Fields marked as readonly can be read but not modified through the interface.

Example
@blackboard_interface
class SensorInterface:
    temperature: Annotated[float, readonly] = 20.0

readwrite

Bases: _AccessMarker

Marker for readwrite fields in blackboard interfaces.

Fields marked as readwrite can be both read and modified through the interface.

Example
@blackboard_interface
class StateInterface:
    counter: Annotated[int, readwrite] = 0

FieldSpec dataclass

FieldSpec(name: str, type: type, readonly: bool = False, computed: bool = False, computation: Optional[callable] = None)

Specification for a field in an interface

blackboard_interface

blackboard_interface(cls: Type[T] | None = None, *, name: str | None = None) -> Any

Decorator for defining blackboard interfaces using Annotated types.

Processes class annotations looking for Annotated types with readonly or readwrite markers, and generates a Protocol class with access control metadata.

Parameters:

Name Type Description Default
cls Type[T] | None

The class being decorated

None
name str | None

Optional name for the generated interface

None

Returns:

Type Description
Any

A Protocol class with type hints and metadata

Example
from typing import Annotated
from mycorrhizal.common.interface_builder import blackboard_interface, readonly, readwrite

@blackboard_interface
class TaskInterface:
    max_tasks: Annotated[int, readonly] = 10
    tasks_completed: Annotated[int, readwrite] = 0

Fields without Annotated markers are treated as readwrite by default.

Source code in src/mycorrhizal/common/interface_builder.py
def blackboard_interface(cls: Type[T] | None = None, *, name: str | None = None) -> Any:
    """
    Decorator for defining blackboard interfaces using Annotated types.

    Processes class annotations looking for Annotated types with readonly or
    readwrite markers, and generates a Protocol class with access control metadata.

    Args:
        cls: The class being decorated
        name: Optional name for the generated interface

    Returns:
        A Protocol class with type hints and metadata

    Example:
        ```python
        from typing import Annotated
        from mycorrhizal.common.interface_builder import blackboard_interface, readonly, readwrite

        @blackboard_interface
        class TaskInterface:
            max_tasks: Annotated[int, readonly] = 10
            tasks_completed: Annotated[int, readwrite] = 0
        ```

    Fields without Annotated markers are treated as readwrite by default.
    """
    def process_class(source_class: Type) -> Type:
        """Process the source class and generate a Protocol-like class"""
        # Get the interface name
        interface_name = name or source_class.__name__

        # Extract type hints
        try:
            hints = get_type_hints(source_class, include_extras=True)
        except Exception:
            hints = {}

        # Determine field metadata from Annotated types
        readonly_fields = set()
        readwrite_fields = set()

        for field_name, field_type in hints.items():
            if field_name.startswith('_'):
                continue

            # Check if this is an Annotated type with access marker
            if str(field_type).startswith('typing.Annotated['):
                # Extract metadata from Annotated[type, *markers]
                args = get_args(field_type)
                if len(args) >= 2:
                    actual_type = args[0]
                    # Check metadata for readonly or readwrite marker
                    metadata = args[1:]
                    found_marker = False
                    for marker in metadata:
                        if isinstance(marker, type) and issubclass(marker, _AccessMarker):
                            if marker is readonly:
                                readonly_fields.add(field_name)
                                found_marker = True
                                break
                            elif marker is readwrite:
                                readwrite_fields.add(field_name)
                                found_marker = True
                                break

                    if not found_marker:
                        # No access marker found, default to readwrite
                        readwrite_fields.add(field_name)
                else:
                    # Annotated with only type, no metadata
                    readwrite_fields.add(field_name)
            else:
                # Not Annotated, default to readwrite
                readwrite_fields.add(field_name)

        # Create a new class with Protocol-like behavior
        namespace = {
            '__annotations__': {},
            '_readonly_fields': frozenset(readonly_fields),
            '_readwrite_fields': frozenset(readwrite_fields),
            '__doc__': source_class.__doc__ or f"Interface: {interface_name}",
            '__module__': source_class.__module__,
            '__protocol_attrs__': frozenset(),  # For runtime_checkable
        }

        # Copy field annotations (use actual types from Annotated)
        for field_name, field_type in hints.items():
            if not field_name.startswith('_'):
                # Extract actual type from Annotated if present
                if hasattr(field_type, '__origin__') and field_type.__origin__ is Annotated:
                    actual_type = field_type.__args__[0]
                else:
                    actual_type = field_type
                namespace['__annotations__'][field_name] = actual_type

        # Create the interface class
        interface = type(interface_name, (), namespace)

        return interface

    # Support @blackboard_interface and @blackboard_interface()
    if cls is None:
        # Called with parentheses
        return lambda c: process_class(c)
    else:
        # Called without parentheses
        return process_class(cls)