diff --git a/.ruff.toml b/.ruff.toml index 11a61478..85eae346 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -58,6 +58,9 @@ ignore = [ # https://docs.astral.sh/ruff/rules/#refactor-r "PLR1711", # Useless return statement at end of function + + # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + "RUF005", # Consider {expression} instead of concatenation ] diff --git a/src/HABApp/__check_dependency_packages__.py b/src/HABApp/__check_dependency_packages__.py index d66af44b..7cc48198 100644 --- a/src/HABApp/__check_dependency_packages__.py +++ b/src/HABApp/__check_dependency_packages__.py @@ -29,7 +29,7 @@ def get_dependencies() -> list[str]: ] -def check_dependency_packages(): +def check_dependency_packages() -> None: """Imports all dependencies and reports failures""" missing: dict[str, ModuleNotFoundError] = {} diff --git a/src/HABApp/core/internals/event_bus/base_listener.py b/src/HABApp/core/internals/event_bus/base_listener.py index e854b212..3371ee45 100644 --- a/src/HABApp/core/internals/event_bus/base_listener.py +++ b/src/HABApp/core/internals/event_bus/base_listener.py @@ -1,10 +1,16 @@ +from typing import Any, Final + + class EventBusBaseListener: - def __init__(self, topic: str, **kwargs) -> None: + def __init__(self, topic: str, **kwargs: Any) -> None: super().__init__(**kwargs) - assert isinstance(topic, str) - self.topic: str = topic + if not isinstance(topic, str): + raise TypeError() + if not topic: + raise ValueError() + self.topic: Final = topic - def notify_listeners(self, event): + def notify_listeners(self, event: Any) -> None: raise NotImplementedError() def describe(self) -> str: diff --git a/src/HABApp/core/internals/event_bus/event_bus.py b/src/HABApp/core/internals/event_bus/event_bus.py index 5d530525..9a6fb7e0 100644 --- a/src/HABApp/core/internals/event_bus/event_bus.py +++ b/src/HABApp/core/internals/event_bus/event_bus.py @@ -1,6 +1,6 @@ import logging import threading -from typing import Any, TypeVar +from typing import Any from HABApp.core.const.log import TOPIC_EVENTS from HABApp.core.events import ComplexEventValue, ValueChangeEvent @@ -11,15 +11,15 @@ event_log = logging.getLogger(TOPIC_EVENTS) habapp_log = logging.getLogger('HABApp') -_TYPE_LISTENER = TypeVar('_TYPE_LISTENER', bound=EventBusBaseListener) - class EventBus: + __slots__ = ('_lock', '_listeners') + def __init__(self) -> None: self._lock = threading.Lock() - self._listeners: dict[str, list[EventBusBaseListener]] = {} + self._listeners: dict[str, tuple[EventBusBaseListener, ...]] = {} - def post_event(self, topic: str, event: Any): + def post_event(self, topic: str, event: Any) -> None: assert isinstance(topic, str), type(topic) if not isinstance(event, str): @@ -42,18 +42,21 @@ def post_event(self, topic: str, event: Any): pass # Notify all listeners - listeners = self._listeners.get(topic, None) - if listeners is not None: + if (listeners := self._listeners.get(topic, None)) is not None: for listener in listeners: listener.notify_listeners(event) return None - def add_listener(self, listener: _TYPE_LISTENER): - assert isinstance(listener, EventBusBaseListener) - assert isinstance(listener.topic, str) and listener.topic + def add_listener(self, listener: EventBusBaseListener) -> None: + if not isinstance(listener, EventBusBaseListener): + raise TypeError() + if not isinstance(topic := listener.topic, str): + raise TypeError() + if not topic: + raise ValueError() with self._lock: - item_listeners = self._listeners.setdefault(listener.topic, []) + item_listeners = self._listeners.get(topic, ()) # don't add the same listener twice if listener in item_listeners: @@ -61,16 +64,20 @@ def add_listener(self, listener: _TYPE_LISTENER): return None # add listener - item_listeners.append(listener) + self._listeners[topic] = item_listeners + (listener,) habapp_log.debug(f'Added event listener for {listener.describe()}') return None - def remove_listener(self, listener: _TYPE_LISTENER): - assert isinstance(listener, EventBusBaseListener) - assert isinstance(listener.topic, str) and listener.topic + def remove_listener(self, listener: EventBusBaseListener) -> None: + if not isinstance(listener, EventBusBaseListener): + raise TypeError() + if not isinstance(topic := listener.topic, str): + raise TypeError() + if not topic: + raise ValueError() with self._lock: - item_listeners = self._listeners.get(listener.topic, []) + item_listeners = self._listeners.get(listener.topic, ()) # print warning if we try to remove it twice if listener not in item_listeners: @@ -78,7 +85,7 @@ def remove_listener(self, listener: _TYPE_LISTENER): return None # remove listener - item_listeners.remove(listener) + self._listeners[topic] = tuple(o for o in item_listeners if o is not listener) habapp_log.debug(f'Removed event listener for {listener.describe()}') return None diff --git a/tests/test_core/test_event_bus.py b/tests/test_core/test_event_bus.py index 02c90627..50018544 100644 --- a/tests/test_core/test_event_bus.py +++ b/tests/test_core/test_event_bus.py @@ -20,18 +20,32 @@ def test_repr(sync_worker) -> None: def test_str_event(sync_worker) -> None: - event_history = [] + """Test simple event and add/remove""" + event_history1 = [] + event_history2 = [] eb = EventBus() def append_event(event) -> None: - event_history.append(event) - func = wrap_func(append_event) + event_history1.append(event) + func1 = wrap_func(append_event) - listener = EventBusListener('str_test', func, NoEventFilter()) - eb.add_listener(listener) + def append_event2(event) -> None: + event_history2.append(event) + func2 = wrap_func(append_event2) + + listener1 = EventBusListener('str_test', func1, NoEventFilter()) + eb.add_listener(listener1) + listener2 = EventBusListener('str_test', func2, NoEventFilter()) + eb.add_listener(listener2) eb.post_event('str_test', 'str_event') - assert event_history == ['str_event'] + assert event_history1 == ['str_event'] + assert event_history2 == ['str_event'] + + eb.remove_listener(listener1) + eb.post_event('str_test', 'str_event_2') + assert event_history1 == ['str_event'] + assert event_history2 == ['str_event', 'str_event_2'] def test_multiple_events(sync_worker) -> None: