Skip to content

Commit

Permalink
Implement alternative callback support with dict and strong opinion a…
Browse files Browse the repository at this point in the history
…bout attribute names.
  • Loading branch information
KelSolaar committed May 1, 2023
1 parent ae82e07 commit 3e75551
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 31 deletions.
4 changes: 3 additions & 1 deletion colour/colorimetry/spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,9 @@ def _on_domain_changed(

return value

self.register_callback("on_domain_changed", _on_domain_changed)
self.register_callback(
"_domain", "on_domain_changed", _on_domain_changed
)

@property
def display_name(self) -> str:
Expand Down
64 changes: 43 additions & 21 deletions colour/utilities/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

from __future__ import annotations

from collections import defaultdict
from dataclasses import dataclass

from colour.hints import (
Any,
Callable,
List,
)

__author__ = "Colour Developers"
Expand Down Expand Up @@ -67,11 +69,9 @@ class MixinCallback:
...
>>> with_callback = WithCallback()
>>> def _on_attribute_a_changed(self, name: str, value: str) -> str:
... if name == "attribute_a":
... value = value.upper()
... return value
... return value.upper()
>>> with_callback.register_callback(
... "on_attribute_a_changed", _on_attribute_a_changed
... "attribute_a", "on_attribute_a_changed", _on_attribute_a_changed
... )
>>> with_callback.attribute_a = "a"
>>> with_callback.attribute_a
Expand All @@ -81,16 +81,16 @@ class MixinCallback:
def __init__(self) -> None:
super().__init__()

self._callbacks: list = []
self._callbacks: defaultdict[str, List[Callback]] = defaultdict(list)

@property
def callbacks(self) -> list:
def callbacks(self) -> defaultdict[str, List[Callback]]:
"""
Getter property for the callbacks.
Returns
-------
:class:`list`
:class:`defaultdict`
Callbacks.
"""

Expand All @@ -109,17 +109,21 @@ def __setattr__(self, name: str, value: Any) -> None:
"""

if hasattr(self, "_callbacks"):
for callback in self._callbacks:
for callback in self._callbacks.get(name, []):
value = callback.function(self, name, value)

super().__setattr__(name, value)

def register_callback(self, name: str, function: Callable) -> None:
def register_callback(
self, attribute: str, name: str, function: Callable
) -> None:
"""
Register the callback with given name.
Register the callback with given name for given attribute.
Parameters
----------
attribute
Attribute to register the callback for.
name
Callback name.
function
Expand All @@ -130,21 +134,27 @@ def register_callback(self, name: str, function: Callable) -> None:
>>> class WithCallback(MixinCallback):
... def __init__(self):
... super().__init__()
... self.attribute_a = "a"
...
>>> with_callback = WithCallback()
>>> with_callback.register_callback("callback", lambda *args: None)
>>> with_callback.register_callback(
... "attribute_a", "callback", lambda *args: None
... )
>>> with_callback.callbacks # doctest: +SKIP
[Callback(name='callback', function=<function <lambda> at 0x10fcf3420>)]
defaultdict(<class 'list'>, {'attribute_a': \
[Callback(name='callback', function=<function <lambda> at 0x...>)]})
"""

self._callbacks.append(Callback(name, function))
self._callbacks[attribute].append(Callback(name, function))

def unregister_callback(self, name: str) -> None:
def unregister_callback(self, attribute: str, name: str) -> None:
"""
Unregister the callback with given name.
Unregister the callback with given name for given attribute.
Parameters
----------
attribute
Attribute to unregister the callback for.
name
Callback name.
Expand All @@ -153,16 +163,28 @@ def unregister_callback(self, name: str) -> None:
>>> class WithCallback(MixinCallback):
... def __init__(self):
... super().__init__()
... self.attribute_a = "a"
...
>>> with_callback = WithCallback()
>>> with_callback.register_callback("callback", lambda s, n, v: v)
>>> with_callback.register_callback(
... "attribute_a", "callback", lambda s, n, v: v
... )
>>> with_callback.callbacks # doctest: +SKIP
[Callback(name='callback', function=<function <lambda> at 0x10fcf3420>)]
>>> with_callback.unregister_callback("callback")
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'>, {})
"""

self._callbacks = [
callback for callback in self._callbacks if callback.name != name
if self._callbacks.get(attribute) is None:
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)
23 changes: 14 additions & 9 deletions colour/utilities/tests/test_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,12 @@ def __init__(self):
def _on_attribute_a_changed(self, name: str, value: str) -> str:
"""Transform *self._attribute_a* to uppercase."""

if name == "attribute_a":
value = value.upper()
value = value.upper()

if getattr(self, name) != "a":
raise RuntimeError(
'"self" was not able to retrieve class instance value!'
)
if getattr(self, name) != "a":
raise RuntimeError(
'"self" was not able to retrieve class instance value!'
)

return value

Expand Down Expand Up @@ -80,7 +79,9 @@ def test_register_callback(self):
"""

self._with_callback.register_callback(
"on_attribute_a_changed", self._on_attribute_a_changed
"attribute_a",
"on_attribute_a_changed",
self._on_attribute_a_changed,
)

self._with_callback.attribute_a = "a"
Expand All @@ -95,11 +96,15 @@ def test_unregister_callback(self):

if len(self._with_callback.callbacks) == 0:
self._with_callback.register_callback(
"on_attribute_a_changed", self._on_attribute_a_changed
"attribute_a",
"on_attribute_a_changed",
self._on_attribute_a_changed,
)

self.assertEqual(len(self._with_callback.callbacks), 1)
self._with_callback.unregister_callback("on_attribute_a_changed")
self._with_callback.unregister_callback(
"attribute_a", "on_attribute_a_changed"
)
self.assertEqual(len(self._with_callback.callbacks), 0)
self._with_callback.attribute_a = "a"
self.assertEqual(self._with_callback.attribute_a, "a")
Expand Down

0 comments on commit 3e75551

Please sign in to comment.