Source code for colour.utilities.callback

"""
Callback Management
===================

Provide callback management functionality for event-driven systems.
"""

from __future__ import annotations

import typing
from collections import defaultdict
from dataclasses import dataclass

if typing.TYPE_CHECKING:
    from colour.hints import Any, Callable, List

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "Callback",
    "MixinCallback",
]


[docs] @dataclass class Callback: """ Represent a named callback with its associated callable function. This dataclass encapsulates a callback's identifying name and its callable function for use in event-driven callback systems. Parameters ---------- name Callback identifier used for registration and management. function Callable object to execute when the callback is triggered. """ name: str function: Callable
[docs] class MixinCallback: """ Provide callback support for attribute changes in classes. This mixin extends class functionality to enable callback registration, allowing automatic invocation when specified attributes are modified. Callbacks can transform or validate attribute values before they are set. Attributes ---------- - :attr:`~colour.utilities.MixinCallback.callbacks` - :attr:`~colour.utilities.MixinCallback.__setattr__` Methods ------- - :meth:`~colour.utilities.MixinCallback.register_callback` - :meth:`~colour.utilities.MixinCallback.unregister_callback` Examples -------- >>> class WithCallback(MixinCallback): ... def __init__(self): ... super().__init__() ... self.attribute_a = "a" >>> with_callback = WithCallback() >>> def _on_attribute_a_changed(self, name: str, value: str) -> str: ... return value.upper() >>> with_callback.register_callback( ... "attribute_a", "on_attribute_a_changed", _on_attribute_a_changed ... ) >>> with_callback.attribute_a = "a" >>> with_callback.attribute_a 'A' """
[docs] def __init__(self) -> None: super().__init__() self._callbacks: defaultdict[str, List[Callback]] = defaultdict(list)
@property def callbacks(self) -> defaultdict[str, List[Callback]]: """ Getter for the event callbacks dictionary. Returns ------- :class:`defaultdict` Dictionary mapping event names to lists of callback functions. Each key represents an event identifier, and each value contains the registered callbacks for that event. """ return self._callbacks
[docs] def __setattr__(self, name: str, value: Any) -> None: """ Set the specified value to the attribute with the specified name. Parameters ---------- name Name of the attribute to set. value Value to set the attribute with. """ if hasattr(self, "_callbacks"): for callback in self._callbacks.get(name, []): value = callback.function(self, name, value) super().__setattr__(name, value)
[docs] def register_callback(self, attribute: str, name: str, function: Callable) -> None: """ Register a callback with the specified name for the specified attribute. Parameters ---------- attribute Attribute to register the callback for. name Callback name. function Callback callable. Examples -------- >>> class WithCallback(MixinCallback): ... def __init__(self): ... super().__init__() ... self.attribute_a = "a" >>> with_callback = WithCallback() >>> with_callback.register_callback( ... "attribute_a", "callback", lambda *args: None ... ) >>> with_callback.callbacks # doctest: +SKIP defaultdict(<class 'list'>, {'attribute_a': \ [Callback(name='callback', function=<function <lambda> at 0x...>)]}) """ self._callbacks[attribute].append(Callback(name, function))
[docs] def unregister_callback(self, attribute: str, name: str) -> None: """ Unregister the callback with the specified name for the specified attribute. Parameters ---------- attribute Attribute to unregister the callback for. name Callback name. Examples -------- >>> class WithCallback(MixinCallback): ... def __init__(self): ... super().__init__() ... self.attribute_a = "a" >>> with_callback = WithCallback() >>> with_callback.register_callback( ... "attribute_a", "callback", lambda s, n, v: v ... ) >>> with_callback.callbacks # doctest: +SKIP defaultdict(<class 'list'>, {'attribute_a': \ [Callback(name='callback', function=<function <lambda> at 0x...>)]}) >>> with_callback.unregister_callback("attribute_a", "callback") >>> with_callback.callbacks defaultdict(<class 'list'>, {}) """ if self._callbacks.get(attribute) is None: # pragma: no cover return self._callbacks[attribute] = [ callback for callback in self._callbacks.get(attribute, []) if callback.name != name ] if len(self._callbacks[attribute]) == 0: self._callbacks.pop(attribute, None)