mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2025-06-28 23:35:12 +00:00
Adjust the whole framework to event handler dependency inversion
This commit is contained in:
@ -1 +1,3 @@
|
||||
from .event_handler_base import EventHandlerBase
|
||||
from .event_handler_factory import EventHandlerFactoryBase
|
||||
from .event_handler import EventHandler
|
||||
from .fsm_aware_event_handler import FSMAwareEventHandler
|
||||
|
@ -1,117 +0,0 @@
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Iterable
|
||||
|
||||
from tfw.networking import Scope
|
||||
|
||||
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
|
||||
"""
|
||||
_instances = set()
|
||||
|
||||
def __init__(self, key, scope=Scope.ZMQ):
|
||||
type(self)._instances.add(self)
|
||||
self.server_connector = self._build_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)
|
||||
|
||||
@abstractmethod
|
||||
def _build_server_connector(self):
|
||||
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 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 self.check_key(message):
|
||||
self.dispatch_handling(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
|
||||
"""
|
||||
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 send_message(self, message):
|
||||
self.server_connector.send_message(message, self.scope)
|
||||
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def stop_all_instances(cls):
|
||||
for instance in cls._instances:
|
||||
instance.stop()
|
||||
|
||||
def stop(self):
|
||||
self.server_connector.close()
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Perform cleanup actions such as releasing database
|
||||
connections and stuff like that.
|
||||
"""
|
@ -24,13 +24,21 @@ class EventHandlerBuilder:
|
||||
self._event_handler_type = event_handler_type
|
||||
|
||||
def build(self, server_connector):
|
||||
server_connector.subscribe(*self._analyzer.keys)
|
||||
event_handler = self._event_handler_type(server_connector)
|
||||
server_connector.subscribe(*self._try_get_keys(event_handler))
|
||||
event_handler.handle_event = self._analyzer.handle_event
|
||||
with suppress(AttributeError):
|
||||
event_handler.cleanup = self._analyzer.cleanup
|
||||
return event_handler
|
||||
|
||||
def _try_get_keys(self, event_handler):
|
||||
try:
|
||||
return self._analyzer.keys
|
||||
except ValueError:
|
||||
with suppress(AttributeError):
|
||||
return event_handler.keys
|
||||
raise
|
||||
|
||||
|
||||
class EventHandlerAnalyzer:
|
||||
def __init__(self, event_handler, supplied_keys):
|
||||
|
37
lib/tfw/event_handlers/fsm_aware.py
Normal file
37
lib/tfw/event_handlers/fsm_aware.py
Normal file
@ -0,0 +1,37 @@
|
||||
import logging
|
||||
|
||||
from tfw.crypto import KeyManager, verify_message
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FSMAware:
|
||||
keys = ['fsm_update']
|
||||
"""
|
||||
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 process_message(self, message):
|
||||
if message['key'] == 'fsm_update':
|
||||
if verify_message(self._auth_key, message):
|
||||
self._handle_fsm_update(message)
|
||||
|
||||
def _handle_fsm_update(self, message):
|
||||
try:
|
||||
new_state = message['current_state']
|
||||
if self.fsm_state != new_state:
|
||||
self.handle_fsm_step(message)
|
||||
self.fsm_state = new_state
|
||||
self.fsm_in_accepted_state = message['in_accepted_state']
|
||||
self.fsm_event_log.append(message)
|
||||
except KeyError:
|
||||
LOG.error('Invalid fsm_update message received!')
|
||||
|
||||
def handle_fsm_step(self, message):
|
||||
pass
|
19
lib/tfw/event_handlers/fsm_aware_event_handler.py
Normal file
19
lib/tfw/event_handlers/fsm_aware_event_handler.py
Normal file
@ -0,0 +1,19 @@
|
||||
from .event_handler import EventHandler
|
||||
from .fsm_aware import FSMAware
|
||||
|
||||
|
||||
class FSMAwareEventHandler(EventHandler, FSMAware):
|
||||
# pylint: disable=abstract-method
|
||||
"""
|
||||
Abstract base class for EventHandlers which automatically
|
||||
keep track of the state of the TFW FSM.
|
||||
"""
|
||||
def __init__(self, server_connector):
|
||||
EventHandler.__init__(self, server_connector)
|
||||
FSMAware.__init__(self)
|
||||
|
||||
def _event_callback(self, message):
|
||||
self.process_message(message)
|
||||
|
||||
def handle_fsm_step(self, message):
|
||||
self.handle_event(message, self.server_connector)
|
@ -174,13 +174,17 @@ def test_build_raises_if_no_key(test_keys):
|
||||
with pytest.raises(ValueError):
|
||||
MockEventHandlerFactory().build(eh)
|
||||
|
||||
def test_handle_event(*_):
|
||||
def handle_event(*_):
|
||||
pass
|
||||
with pytest.raises(ValueError):
|
||||
MockEventHandlerFactory().build(test_handle_event)
|
||||
MockEventHandlerFactory().build(handle_event)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
MockEventHandlerFactory().build(lambda msg, sc: None)
|
||||
|
||||
WithKeysEventHandler = EventHandler
|
||||
WithKeysEventHandler.keys = test_keys
|
||||
MockEventHandlerFactory().build(eh, event_handler_type=WithKeysEventHandler)
|
||||
|
||||
eh.keys = test_keys
|
||||
MockEventHandlerFactory().build(eh)
|
||||
|
Reference in New Issue
Block a user