# Copyright (C) 2018 Avatao.com Innovative Learning Kft. # All Rights Reserved. See LICENSE file for details. import logging from inspect import currentframe from typing import Iterable from tfw.networking import Scope LOG = logging.getLogger(__name__) class EventHandlerBase: """ Abstract base class for all Python based EventHandlers. Useful implementation template for other languages. Derived classes must implement the handle_event() method """ def __init__(self, key, server_connector, scope=Scope.ZMQ): self.server_connector = server_connector self.scope = scope self.keys = [] if isinstance(key, str): self.keys.append(key) elif isinstance(key, Iterable): self.keys = list(key) self.subscribe(*self.keys) self.server_connector.register_callback(self.event_handler_callback) @property def key(self): """ Returns the oldest key this EventHandler was subscribed to. """ return self.keys[0] def event_handler_callback(self, message): """ Callback that is invoked when receiving a message. Dispatches messages to handler methods and sends a response back in case the handler returned something. This is subscribed in __init__(). """ if not self.check_key(message): return response = self.dispatch_handling(message) if response: self.send_message(response) def send_message(self, message): self.server_connector.send_message(message, self.scope) def check_key(self, message): """ Checks whether the message is intended for this EventHandler. This is necessary because ZMQ handles PUB - SUB connetions with pattern matching (e.g. someone subscribed to 'fsm' will receive 'fsm_update' messages as well. """ if '' in self.keys: return True return message['key'] in self.keys def dispatch_handling(self, message): """ Used to dispatch messages to their specific handlers. :param message: the message received :returns: the message to send back """ return self.handle_event(message) def handle_event(self, message): """ Abstract method that implements the handling of messages. :param message: the message received :returns: the message to send back """ raise NotImplementedError def subscribe(self, *keys): """ Subscribe this EventHandler to receive events for given keys. Note that you can subscribe to the same key several times in which case you will need to unsubscribe multiple times in order to stop receiving events. :param keys: list of keys to subscribe to """ for key in keys: self.server_connector.subscribe(key) self.keys.append(key) def unsubscribe(self, *keys): """ Unsubscribe this eventhandler from the given keys. :param keys: list of keys to unsubscribe from """ for key in keys: self.server_connector.unsubscribe(key) self.keys.remove(key) def stop(self): self.server_connector.close() self.cleanup() def cleanup(self): """ Perform cleanup actions such as releasing database connections and stuff like that. """ @classmethod def get_local_instances(cls): frame = currentframe() if frame is None: raise EnvironmentError('inspect.currentframe() is not supported!') locals_values = frame.f_back.f_locals.values() return { instance for instance in locals_values if isinstance(instance, cls) }