Support to register/unregister custom handlers on the bus.

This commit is contained in:
Fabio Manganiello 2023-09-17 02:29:11 +02:00
parent 2e004c1a1e
commit 0421325b26
Signed by: blacklight
GPG key ID: D90FBA7F76362774

View file

@ -1,15 +1,34 @@
from collections import defaultdict
from dataclasses import dataclass, field
import logging import logging
import threading import threading
import time import time
from queue import Queue, Empty from queue import Queue, Empty
from typing import Callable, Type from typing import Callable, Dict, Iterable, Type
from platypush.message import Message
from platypush.message.event import Event from platypush.message.event import Event
logger = logging.getLogger('platypush:bus') logger = logging.getLogger('platypush:bus')
@dataclass
class MessageHandler:
"""
Wrapper for a message callback handler.
"""
msg_type: Type[Message]
callback: Callable[[Message], None]
kwargs: dict = field(default_factory=dict)
def match(self, msg: Message) -> bool:
return isinstance(msg, self.msg_type) and all(
getattr(msg, k, None) == v for k, v in self.kwargs.items()
)
class Bus: class Bus:
""" """
Main local bus where the daemon will listen for new messages. Main local bus where the daemon will listen for new messages.
@ -21,7 +40,10 @@ class Bus:
self.bus = Queue() self.bus = Queue()
self.on_message = on_message self.on_message = on_message
self.thread_id = threading.get_ident() self.thread_id = threading.get_ident()
self.event_handlers = {} self.handlers: Dict[
Type[Message], Dict[Callable[[Message], None], MessageHandler]
] = defaultdict(dict)
self._should_stop = threading.Event() self._should_stop = threading.Event()
def post(self, msg): def post(self, msg):
@ -38,26 +60,24 @@ class Bus:
def stop(self): def stop(self):
self._should_stop.set() self._should_stop.set()
def _get_matching_handlers(
self, msg: Message
) -> Iterable[Callable[[Message], None]]:
return [
hndl.callback
for cls in type(msg).__mro__
for hndl in self.handlers.get(cls, [])
if hndl.match(msg)
]
def _msg_executor(self, msg): def _msg_executor(self, msg):
def event_handler(event: Event, handler: Callable[[Event], None]): def event_handler(event: Event, handler: Callable[[Event], None]):
logger.info('Triggering event handler %s', handler.__name__) logger.info('Triggering event handler %s', handler.__name__)
handler(event) handler(event)
def executor(): def executor():
if isinstance(msg, Event): for hndl in self._get_matching_handlers(msg):
handlers = self.event_handlers.get( threading.Thread(target=event_handler, args=(msg, hndl)).start()
type(msg),
{
*[
hndl
for event_type, hndl in self.event_handlers.items()
if isinstance(msg, event_type)
]
},
)
for hndl in handlers:
threading.Thread(target=event_handler, args=(msg, hndl))
try: try:
if self.on_message: if self.on_message:
@ -100,27 +120,25 @@ class Bus:
logger.info('Bus service stopped') logger.info('Bus service stopped')
def register_handler( def register_handler(
self, event_type: Type[Event], handler: Callable[[Event], None] self, type: Type[Message], handler: Callable[[Message], None], **kwargs
) -> Callable[[], None]: ) -> Callable[[], None]:
""" """
Register an event handler to the bus. Register a generic handler to the bus.
:param event_type: Event type to subscribe (event inheritance also works). :param type: Type of the message to subscribe to (event inheritance also works).
:param handler: Event handler - a function that takes an Event object as parameter. :param handler: Event handler - a function that takes a Message object as parameter.
:param kwargs: Extra filter on the message values.
:return: A function that can be called to remove the handler (no parameters required). :return: A function that can be called to remove the handler (no parameters required).
""" """
if event_type not in self.event_handlers: self.handlers[type][handler] = MessageHandler(type, handler, kwargs)
self.event_handlers[event_type] = set()
self.event_handlers[event_type].add(handler)
def unregister(): def unregister():
self.unregister_handler(event_type, handler) self.unregister_handler(type, handler)
return unregister return unregister
def unregister_handler( def unregister_handler(
self, event_type: Type[Event], handler: Callable[[Event], None] self, type: Type[Message], handler: Callable[[Message], None]
) -> None: ) -> None:
""" """
Remove an event handler. Remove an event handler.
@ -128,14 +146,12 @@ class Bus:
:param event_type: Event type. :param event_type: Event type.
:param handler: Existing event handler. :param handler: Existing event handler.
""" """
if event_type not in self.event_handlers: if type not in self.handlers:
return return
if handler in self.event_handlers[event_type]: self.handlers[type].pop(handler, None)
self.event_handlers[event_type].remove(handler) if len(self.handlers[type]) == 0:
del self.handlers[type]
if len(self.event_handlers[event_type]) == 0:
del self.event_handlers[event_type]
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: