mirror of
				https://github.com/avatao-content/baseimage-tutorial-framework
				synced 2025-11-04 13:02:55 +00:00 
			
		
		
		
	Improve TFW lib layout
This commit is contained in:
		
							
								
								
									
										7
									
								
								lib/tfw/event_handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/tfw/event_handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
 | 
			
		||||
# All Rights Reserved. See LICENSE file for details.
 | 
			
		||||
 | 
			
		||||
from .event_handler_base import EventHandlerBase
 | 
			
		||||
from .frontend_event_handler_base import FrontendEventHandlerBase
 | 
			
		||||
from .boradcasting_event_handler import BroadcastingEventHandler
 | 
			
		||||
from .fsm_aware_event_handler import FSMAwareEventHandler
 | 
			
		||||
							
								
								
									
										32
									
								
								lib/tfw/event_handlers/boradcasting_event_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/tfw/event_handlers/boradcasting_event_handler.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
 | 
			
		||||
# All Rights Reserved. See LICENSE file for details.
 | 
			
		||||
 | 
			
		||||
from abc import ABC
 | 
			
		||||
 | 
			
		||||
from tfw.networking import Scope
 | 
			
		||||
from tfw.crypto import message_checksum
 | 
			
		||||
 | 
			
		||||
from .event_handler_base import EventHandlerBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BroadcastingEventHandler(EventHandlerBase, ABC):
 | 
			
		||||
    # pylint: disable=abstract-method
 | 
			
		||||
    """
 | 
			
		||||
    Abstract base class for EventHandlers which broadcast responses
 | 
			
		||||
    and intelligently ignore their own broadcasted messages they receive.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, key):
 | 
			
		||||
        super().__init__(key)
 | 
			
		||||
        self.own_message_hashes = []
 | 
			
		||||
 | 
			
		||||
    def event_handler_callback(self, message):
 | 
			
		||||
        message_hash = message_checksum(message)
 | 
			
		||||
 | 
			
		||||
        if message_hash in self.own_message_hashes:
 | 
			
		||||
            self.own_message_hashes.remove(message_hash)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        response = self.dispatch_handling(message)
 | 
			
		||||
        if response:
 | 
			
		||||
            self.own_message_hashes.append(message_checksum(response))
 | 
			
		||||
            self.server_connector.send_message(response, Scope.BROADCAST)
 | 
			
		||||
							
								
								
									
										133
									
								
								lib/tfw/event_handlers/event_handler_base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								lib/tfw/event_handlers/event_handler_base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
 | 
			
		||||
# All Rights Reserved. See LICENSE file for details.
 | 
			
		||||
 | 
			
		||||
from abc import ABC, abstractmethod
 | 
			
		||||
from inspect import currentframe
 | 
			
		||||
from typing import Iterable
 | 
			
		||||
 | 
			
		||||
from tfw.networking import ServerConnector
 | 
			
		||||
from tfw.config.logs import logging
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventHandlerBase(ABC):
 | 
			
		||||
    """
 | 
			
		||||
    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):
 | 
			
		||||
        self.server_connector = ServerConnector()
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    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.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @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)
 | 
			
		||||
        }
 | 
			
		||||
							
								
								
									
										8
									
								
								lib/tfw/event_handlers/frontend_event_handler_base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								lib/tfw/event_handlers/frontend_event_handler_base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
from tfw.networking import Scope
 | 
			
		||||
 | 
			
		||||
from .event_handler_base import EventHandlerBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FrontendEventHandlerBase(EventHandlerBase):  # pylint: disable=abstract-method
 | 
			
		||||
    def send_message(self, message):
 | 
			
		||||
        self.server_connector.send_message(message, Scope.WEBSOCKET)
 | 
			
		||||
							
								
								
									
										45
									
								
								lib/tfw/event_handlers/fsm_aware.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/tfw/event_handlers/fsm_aware.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
 | 
			
		||||
# All Rights Reserved. See LICENSE file for details.
 | 
			
		||||
 | 
			
		||||
from tfw.crypto import KeyManager, verify_message
 | 
			
		||||
from tfw.config.logs import logging
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FSMAware:
 | 
			
		||||
    """
 | 
			
		||||
    Base class for stuff that has to be aware of the framework FSM.
 | 
			
		||||
    This is done by processing 'fsm_update' messages.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.fsm_state = None
 | 
			
		||||
        self.fsm_in_accepted_state = False
 | 
			
		||||
        self.fsm_event_log = []
 | 
			
		||||
        self._auth_key = KeyManager().auth_key
 | 
			
		||||
 | 
			
		||||
    def update_fsm_data(self, message):
 | 
			
		||||
        if message['key'] == 'fsm_update' and verify_message(self._auth_key, message):
 | 
			
		||||
            self._handle_fsm_update(message)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def _handle_fsm_update(self, message):
 | 
			
		||||
        try:
 | 
			
		||||
            update_data = message['data']
 | 
			
		||||
            new_state = update_data['current_state']
 | 
			
		||||
            if self.fsm_state != new_state:
 | 
			
		||||
                self.handle_fsm_step(**update_data)
 | 
			
		||||
            self.fsm_state = new_state
 | 
			
		||||
            self.fsm_in_accepted_state = update_data['in_accepted_state']
 | 
			
		||||
            self.fsm_event_log.append(update_data)
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            LOG.error('Invalid fsm_update message received!')
 | 
			
		||||
 | 
			
		||||
    def handle_fsm_step(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Called in case the TFW FSM has stepped.
 | 
			
		||||
 | 
			
		||||
        :param kwargs: fsm_update 'data' field
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										24
									
								
								lib/tfw/event_handlers/fsm_aware_event_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/tfw/event_handlers/fsm_aware_event_handler.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
# Copyright (C) 2018 Avatao.com Innovative Learning Kft.
 | 
			
		||||
# All Rights Reserved. See LICENSE file for details.
 | 
			
		||||
 | 
			
		||||
from abc import ABC
 | 
			
		||||
 | 
			
		||||
from .event_handler_base import EventHandlerBase
 | 
			
		||||
from .fsm_aware import FSMAware
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FSMAwareEventHandler(EventHandlerBase, FSMAware, ABC):
 | 
			
		||||
    # pylint: disable=abstract-method
 | 
			
		||||
    """
 | 
			
		||||
    Abstract base class for EventHandlers which automatically
 | 
			
		||||
    keep track of the state of the TFW FSM.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, key):
 | 
			
		||||
        EventHandlerBase.__init__(self, key)
 | 
			
		||||
        FSMAware.__init__(self)
 | 
			
		||||
        self.subscribe('fsm_update')
 | 
			
		||||
 | 
			
		||||
    def dispatch_handling(self, message):
 | 
			
		||||
        if self.update_fsm_data(message):
 | 
			
		||||
            return None
 | 
			
		||||
        return super().dispatch_handling(message)
 | 
			
		||||
		Reference in New Issue
	
	Block a user