Skip to content

Observable

observant.observable.Observable

Bases: Generic[T], IObservable[T]

A generic observable value that notifies listeners when its value changes.

Observable is the core building block of Observant's reactive system. It wraps a single value and provides methods to get, set, and observe changes to that value.

Attributes:

Name Type Description
_value T

The current value of the observable.

_callbacks list[Callable[[T], None]]

List of callback functions to be called when the value changes.

_on_change_enabled bool

Whether callbacks are enabled.

Examples:

# Create an observable integer
counter = Observable[int](0)

# Register a callback to be notified when the value changes
counter.on_change(lambda value: print(f"Counter changed to {value}"))

# Update the value
counter.set(1)  # Prints: "Counter changed to 1"

# Get the current value
current_value = counter.get()  # Returns: 1
Source code in observant\observable.py
class Observable(Generic[T], IObservable[T]):
    """
    A generic observable value that notifies listeners when its value changes.

    Observable is the core building block of Observant's reactive system. It wraps
    a single value and provides methods to get, set, and observe changes to that value.

    Attributes:
        _value: The current value of the observable.
        _callbacks: List of callback functions to be called when the value changes.
        _on_change_enabled: Whether callbacks are enabled.

    Examples:
        ```python
        # Create an observable integer
        counter = Observable[int](0)

        # Register a callback to be notified when the value changes
        counter.on_change(lambda value: print(f"Counter changed to {value}"))

        # Update the value
        counter.set(1)  # Prints: "Counter changed to 1"

        # Get the current value
        current_value = counter.get()  # Returns: 1
        ```
    """

    _value: T
    _callbacks: list[Callable[[T], None]]
    _on_change_enabled: bool = True

    def __init__(self, value: T, *, on_change: Callable[[T], None] | None = None, on_change_enabled: bool = True) -> None:
        """
        Initialize the Observable with a value.

        Args:
            value: The initial value of the observable.
            on_change: Optional callback function to register immediately.
            on_change_enabled: Whether callbacks should be enabled initially.
        """
        print(f"DEBUG: Observable.__init__ called with value {value}")
        self._value = value
        self._callbacks = []
        self._on_change_enabled = on_change_enabled
        print("DEBUG: Observable.__init__ - Initialized with empty callbacks list")

    @override
    def get(self) -> T:
        """
        Get the current value of the observable.

        Returns:
            The current value stored in this observable.

        Examples:
            ```python
            counter = Observable[int](0)
            value = counter.get()  # Returns: 0
            ```
        """
        return self._value

    @override
    def set(self, value: T, notify: bool = True) -> None:
        """
        Set a new value for the observable and notify all registered callbacks.

        This method updates the internal value and, if notify is True and callbacks
        are enabled, calls all registered callbacks with the new value.

        Args:
            value: The new value to set.
            notify: Whether to notify the callbacks after setting the value.

        Examples:
            ```python
            counter = Observable[int](0)
            counter.on_change(lambda value: print(f"Counter changed to {value}"))

            # Update with notification
            counter.set(1)  # Prints: "Counter changed to 1"

            # Update without notification
            counter.set(2, notify=False)  # No output
            ```
        """
        print(f"DEBUG: Observable.set called with value {value}")
        self._value = value

        if not notify or not self._on_change_enabled:
            print("DEBUG: Observable.set - on_change is disabled, skipping callbacks")
            return

        print(f"DEBUG: Observable.set - Notifying {len(self._callbacks)} callbacks")
        for i, callback in enumerate(self._callbacks):
            print(f"DEBUG: Observable.set - Calling callback {i}")
            callback(value)
            print(f"DEBUG: Observable.set - Callback {i} completed")
        print("DEBUG: Observable.set - Completed")

    @override
    def on_change(self, callback: Callable[[T], None]) -> None:
        """
        Register a callback function to be called when the value changes.

        The callback will be called with the new value whenever set() is called
        with notify=True and callbacks are enabled. Callbacks are called in the
        order they were registered.

        If the same callback function is registered multiple times, it will only
        be added once.

        Args:
            callback: A function that takes the new value as its argument.

        Examples:
            ```python
            counter = Observable[int](0)

            # Register a callback
            counter.on_change(lambda value: print(f"Counter changed to {value}"))

            # Register another callback
            counter.on_change(lambda value: print(f"Counter is now {value}"))

            # Update the value
            counter.set(1)
            # Prints:
            # "Counter changed to 1"
            # "Counter is now 1"
            ```
        """
        print(f"DEBUG: Observable.on_change called, current callbacks: {len(self._callbacks)}")
        # Check if this callback is already registered to avoid duplicates
        for existing_cb in self._callbacks:
            if existing_cb == callback:
                print("DEBUG: Observable.on_change - Callback already registered, skipping")
                return

        self._callbacks.append(callback)
        print(f"DEBUG: Observable.on_change - Added callback, now have {len(self._callbacks)} callbacks")

    @override
    def enable(self) -> None:
        """
        Enable the observable to notify changes.

        After calling this method, subsequent calls to set() with notify=True
        will trigger callbacks.

        Examples:
            ```python
            counter = Observable[int](0)
            counter.on_change(lambda value: print(f"Counter changed to {value}"))

            # Disable notifications
            counter.disable()
            counter.set(1)  # No output

            # Enable notifications
            counter.enable()
            counter.set(2)  # Prints: "Counter changed to 2"
            ```
        """
        print("DEBUG: Observable.enable called")
        self._on_change_enabled = True

    @override
    def disable(self) -> None:
        """
        Disable the observable from notifying changes.

        After calling this method, subsequent calls to set() will not trigger
        callbacks, even if notify=True.

        Examples:
            ```python
            counter = Observable[int](0)
            counter.on_change(lambda value: print(f"Counter changed to {value}"))

            # Disable notifications
            counter.disable()
            counter.set(1)  # No output
            ```
        """
        print("DEBUG: Observable.disable called")
        self._on_change_enabled = False

    def __bool__(self) -> bool:
        """
        Convert the observable to a boolean.

        This allows using the observable directly in boolean contexts.

        Returns:
            The boolean value of the current value.

        Examples:
            ```python
            counter = Observable[int](0)
            if not counter:
                print("Counter is zero")  # This will print

            counter.set(1)
            if counter:
                print("Counter is non-zero")  # This will print
            ```
        """
        return bool(self.get())

    @override
    def __str__(self) -> str:
        """
        Convert the observable to a string.

        This allows using the observable directly in string contexts.

        Returns:
            The string representation of the current value.

        Examples:
            ```python
            counter = Observable[int](42)
            print(f"The counter is {counter}")  # Prints: "The counter is 42"
            ```
        """
        return str(self.get())

    @override
    def __repr__(self) -> str:
        """
        Get the representation of the observable.

        Returns:
            A string representation of the observable, including its class name
            and current value.

        Examples:
            ```python
            counter = Observable[int](42)
            repr(counter)  # Returns: "Observable(42)"
            ```
        """
        return f"{self.__class__.__name__}({self.get()!r})"

__bool__()

Convert the observable to a boolean.

This allows using the observable directly in boolean contexts.

Returns:

Type Description
bool

The boolean value of the current value.

Examples:

counter = Observable[int](0)
if not counter:
    print("Counter is zero")  # This will print

counter.set(1)
if counter:
    print("Counter is non-zero")  # This will print
Source code in observant\observable.py
def __bool__(self) -> bool:
    """
    Convert the observable to a boolean.

    This allows using the observable directly in boolean contexts.

    Returns:
        The boolean value of the current value.

    Examples:
        ```python
        counter = Observable[int](0)
        if not counter:
            print("Counter is zero")  # This will print

        counter.set(1)
        if counter:
            print("Counter is non-zero")  # This will print
        ```
    """
    return bool(self.get())

__init__(value, *, on_change=None, on_change_enabled=True)

Initialize the Observable with a value.

Parameters:

Name Type Description Default
value T

The initial value of the observable.

required
on_change Callable[[T], None] | None

Optional callback function to register immediately.

None
on_change_enabled bool

Whether callbacks should be enabled initially.

True
Source code in observant\observable.py
def __init__(self, value: T, *, on_change: Callable[[T], None] | None = None, on_change_enabled: bool = True) -> None:
    """
    Initialize the Observable with a value.

    Args:
        value: The initial value of the observable.
        on_change: Optional callback function to register immediately.
        on_change_enabled: Whether callbacks should be enabled initially.
    """
    print(f"DEBUG: Observable.__init__ called with value {value}")
    self._value = value
    self._callbacks = []
    self._on_change_enabled = on_change_enabled
    print("DEBUG: Observable.__init__ - Initialized with empty callbacks list")

__repr__()

Get the representation of the observable.

Returns:

Type Description
str

A string representation of the observable, including its class name

str

and current value.

Examples:

counter = Observable[int](42)
repr(counter)  # Returns: "Observable(42)"
Source code in observant\observable.py
@override
def __repr__(self) -> str:
    """
    Get the representation of the observable.

    Returns:
        A string representation of the observable, including its class name
        and current value.

    Examples:
        ```python
        counter = Observable[int](42)
        repr(counter)  # Returns: "Observable(42)"
        ```
    """
    return f"{self.__class__.__name__}({self.get()!r})"

__str__()

Convert the observable to a string.

This allows using the observable directly in string contexts.

Returns:

Type Description
str

The string representation of the current value.

Examples:

counter = Observable[int](42)
print(f"The counter is {counter}")  # Prints: "The counter is 42"
Source code in observant\observable.py
@override
def __str__(self) -> str:
    """
    Convert the observable to a string.

    This allows using the observable directly in string contexts.

    Returns:
        The string representation of the current value.

    Examples:
        ```python
        counter = Observable[int](42)
        print(f"The counter is {counter}")  # Prints: "The counter is 42"
        ```
    """
    return str(self.get())

disable()

Disable the observable from notifying changes.

After calling this method, subsequent calls to set() will not trigger callbacks, even if notify=True.

Examples:

counter = Observable[int](0)
counter.on_change(lambda value: print(f"Counter changed to {value}"))

# Disable notifications
counter.disable()
counter.set(1)  # No output
Source code in observant\observable.py
@override
def disable(self) -> None:
    """
    Disable the observable from notifying changes.

    After calling this method, subsequent calls to set() will not trigger
    callbacks, even if notify=True.

    Examples:
        ```python
        counter = Observable[int](0)
        counter.on_change(lambda value: print(f"Counter changed to {value}"))

        # Disable notifications
        counter.disable()
        counter.set(1)  # No output
        ```
    """
    print("DEBUG: Observable.disable called")
    self._on_change_enabled = False

enable()

Enable the observable to notify changes.

After calling this method, subsequent calls to set() with notify=True will trigger callbacks.

Examples:

counter = Observable[int](0)
counter.on_change(lambda value: print(f"Counter changed to {value}"))

# Disable notifications
counter.disable()
counter.set(1)  # No output

# Enable notifications
counter.enable()
counter.set(2)  # Prints: "Counter changed to 2"
Source code in observant\observable.py
@override
def enable(self) -> None:
    """
    Enable the observable to notify changes.

    After calling this method, subsequent calls to set() with notify=True
    will trigger callbacks.

    Examples:
        ```python
        counter = Observable[int](0)
        counter.on_change(lambda value: print(f"Counter changed to {value}"))

        # Disable notifications
        counter.disable()
        counter.set(1)  # No output

        # Enable notifications
        counter.enable()
        counter.set(2)  # Prints: "Counter changed to 2"
        ```
    """
    print("DEBUG: Observable.enable called")
    self._on_change_enabled = True

get()

Get the current value of the observable.

Returns:

Type Description
T

The current value stored in this observable.

Examples:

counter = Observable[int](0)
value = counter.get()  # Returns: 0
Source code in observant\observable.py
@override
def get(self) -> T:
    """
    Get the current value of the observable.

    Returns:
        The current value stored in this observable.

    Examples:
        ```python
        counter = Observable[int](0)
        value = counter.get()  # Returns: 0
        ```
    """
    return self._value

on_change(callback)

Register a callback function to be called when the value changes.

The callback will be called with the new value whenever set() is called with notify=True and callbacks are enabled. Callbacks are called in the order they were registered.

If the same callback function is registered multiple times, it will only be added once.

Parameters:

Name Type Description Default
callback Callable[[T], None]

A function that takes the new value as its argument.

required

Examples:

counter = Observable[int](0)

# Register a callback
counter.on_change(lambda value: print(f"Counter changed to {value}"))

# Register another callback
counter.on_change(lambda value: print(f"Counter is now {value}"))

# Update the value
counter.set(1)
# Prints:
# "Counter changed to 1"
# "Counter is now 1"
Source code in observant\observable.py
@override
def on_change(self, callback: Callable[[T], None]) -> None:
    """
    Register a callback function to be called when the value changes.

    The callback will be called with the new value whenever set() is called
    with notify=True and callbacks are enabled. Callbacks are called in the
    order they were registered.

    If the same callback function is registered multiple times, it will only
    be added once.

    Args:
        callback: A function that takes the new value as its argument.

    Examples:
        ```python
        counter = Observable[int](0)

        # Register a callback
        counter.on_change(lambda value: print(f"Counter changed to {value}"))

        # Register another callback
        counter.on_change(lambda value: print(f"Counter is now {value}"))

        # Update the value
        counter.set(1)
        # Prints:
        # "Counter changed to 1"
        # "Counter is now 1"
        ```
    """
    print(f"DEBUG: Observable.on_change called, current callbacks: {len(self._callbacks)}")
    # Check if this callback is already registered to avoid duplicates
    for existing_cb in self._callbacks:
        if existing_cb == callback:
            print("DEBUG: Observable.on_change - Callback already registered, skipping")
            return

    self._callbacks.append(callback)
    print(f"DEBUG: Observable.on_change - Added callback, now have {len(self._callbacks)} callbacks")

set(value, notify=True)

Set a new value for the observable and notify all registered callbacks.

This method updates the internal value and, if notify is True and callbacks are enabled, calls all registered callbacks with the new value.

Parameters:

Name Type Description Default
value T

The new value to set.

required
notify bool

Whether to notify the callbacks after setting the value.

True

Examples:

counter = Observable[int](0)
counter.on_change(lambda value: print(f"Counter changed to {value}"))

# Update with notification
counter.set(1)  # Prints: "Counter changed to 1"

# Update without notification
counter.set(2, notify=False)  # No output
Source code in observant\observable.py
@override
def set(self, value: T, notify: bool = True) -> None:
    """
    Set a new value for the observable and notify all registered callbacks.

    This method updates the internal value and, if notify is True and callbacks
    are enabled, calls all registered callbacks with the new value.

    Args:
        value: The new value to set.
        notify: Whether to notify the callbacks after setting the value.

    Examples:
        ```python
        counter = Observable[int](0)
        counter.on_change(lambda value: print(f"Counter changed to {value}"))

        # Update with notification
        counter.set(1)  # Prints: "Counter changed to 1"

        # Update without notification
        counter.set(2, notify=False)  # No output
        ```
    """
    print(f"DEBUG: Observable.set called with value {value}")
    self._value = value

    if not notify or not self._on_change_enabled:
        print("DEBUG: Observable.set - on_change is disabled, skipping callbacks")
        return

    print(f"DEBUG: Observable.set - Notifying {len(self._callbacks)} callbacks")
    for i, callback in enumerate(self._callbacks):
        print(f"DEBUG: Observable.set - Calling callback {i}")
        callback(value)
        print(f"DEBUG: Observable.set - Callback {i} completed")
    print("DEBUG: Observable.set - Completed")