dupeguru/hscommon/notify.py

89 lines
3.3 KiB
Python
Raw Normal View History

2019-09-09 19:54:28 -05:00
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
2019-09-09 19:54:28 -05:00
# http://www.gnu.org/licenses/gpl-3.0.html
"""Very simple inter-object notification system.
This module is a brain-dead simple notification system involving a :class:`Broadcaster` and a
:class:`Listener`. A listener can only listen to one broadcaster. A broadcaster can have multiple
listeners. If the listener is connected, whenever the broadcaster calls :meth:`~Broadcaster.notify`,
the method with the same name as the broadcasted message is called on the listener.
"""
from collections import defaultdict
2022-05-09 23:36:39 -05:00
from typing import Callable, DefaultDict, List
2019-09-09 19:54:28 -05:00
2019-09-09 19:54:28 -05:00
class Broadcaster:
"""Broadcasts messages that are received by all listeners."""
2019-09-09 19:54:28 -05:00
def __init__(self):
self.listeners = set()
2022-05-09 23:36:39 -05:00
def add_listener(self, listener: "Listener") -> None:
2019-09-09 19:54:28 -05:00
self.listeners.add(listener)
2022-05-09 23:36:39 -05:00
def notify(self, msg: str) -> None:
2019-09-09 19:54:28 -05:00
"""Notify all connected listeners of ``msg``.
2019-09-09 19:54:28 -05:00
That means that each listeners will have their method with the same name as ``msg`` called.
"""
for listener in self.listeners.copy(): # listeners can change during iteration
if listener in self.listeners: # disconnected during notification
2019-09-09 19:54:28 -05:00
listener.dispatch(msg)
2022-05-09 23:36:39 -05:00
def remove_listener(self, listener: "Listener") -> None:
2019-09-09 19:54:28 -05:00
self.listeners.discard(listener)
2019-09-09 19:54:28 -05:00
class Listener:
"""A listener is initialized with the broadcaster it's going to listen to. Initially, it is not connected."""
2022-05-09 23:36:39 -05:00
def __init__(self, broadcaster: Broadcaster) -> None:
2019-09-09 19:54:28 -05:00
self.broadcaster = broadcaster
2022-05-09 23:36:39 -05:00
self._bound_notifications: DefaultDict[str, List[Callable]] = defaultdict(list)
2022-05-09 23:36:39 -05:00
def bind_messages(self, messages: str, func: Callable) -> None:
2019-09-09 19:54:28 -05:00
"""Binds multiple message to the same function.
2019-09-09 19:54:28 -05:00
Often, we perform the same thing on multiple messages. Instead of having the same function
repeated again and agin in our class, we can use this method to bind multiple messages to
the same function.
"""
for message in messages:
self._bound_notifications[message].append(func)
2022-05-09 23:36:39 -05:00
def connect(self) -> None:
"""Connects the listener to its broadcaster."""
2019-09-09 19:54:28 -05:00
self.broadcaster.add_listener(self)
2022-05-09 23:36:39 -05:00
def disconnect(self) -> None:
"""Disconnects the listener from its broadcaster."""
2019-09-09 19:54:28 -05:00
self.broadcaster.remove_listener(self)
2022-05-09 23:36:39 -05:00
def dispatch(self, msg: str) -> None:
2019-09-09 19:54:28 -05:00
if msg in self._bound_notifications:
for func in self._bound_notifications[msg]:
func()
if hasattr(self, msg):
method = getattr(self, msg)
method()
2019-09-09 19:54:28 -05:00
class Repeater(Broadcaster, Listener):
REPEATED_NOTIFICATIONS = None
2022-05-09 23:36:39 -05:00
def __init__(self, broadcaster: Broadcaster) -> None:
2019-09-09 19:54:28 -05:00
Broadcaster.__init__(self)
Listener.__init__(self, broadcaster)
2022-05-09 23:36:39 -05:00
def _repeat_message(self, msg: str) -> None:
2019-09-09 19:54:28 -05:00
if not self.REPEATED_NOTIFICATIONS or msg in self.REPEATED_NOTIFICATIONS:
self.notify(msg)
2022-05-09 23:36:39 -05:00
def dispatch(self, msg: str) -> None:
2019-09-09 19:54:28 -05:00
Listener.dispatch(self, msg)
self._repeat_message(msg)