mirror of
https://github.com/avatao-content/baseimage-tutorial-framework
synced 2024-11-22 13:01:32 +00:00
Add new EventHandler stuff as per interface segregation principle
This commit is contained in:
parent
84a28a1582
commit
f1679ffb50
27
lib/tfw/event_handlers/event_handler.py
Normal file
27
lib/tfw/event_handlers/event_handler.py
Normal file
@ -0,0 +1,27 @@
|
||||
class EventHandler:
|
||||
_instances = set()
|
||||
|
||||
def __init__(self, server_connector):
|
||||
type(self)._instances.add(self)
|
||||
self.server_connector = server_connector
|
||||
|
||||
def start(self):
|
||||
self.server_connector.register_callback(self._event_callback)
|
||||
|
||||
def _event_callback(self, message):
|
||||
self.handle_event(message, self.server_connector)
|
||||
|
||||
def handle_event(self, message, server_connector):
|
||||
raise NotImplementedError()
|
||||
|
||||
@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):
|
||||
pass
|
51
lib/tfw/event_handlers/event_handler_factory.py
Normal file
51
lib/tfw/event_handlers/event_handler_factory.py
Normal file
@ -0,0 +1,51 @@
|
||||
from contextlib import suppress
|
||||
|
||||
from .event_handler import EventHandler
|
||||
|
||||
|
||||
class EventHandlerFactoryBase:
|
||||
def build(self, event_handler, *, keys=None):
|
||||
analyzer = EventHandlerAnalyzer(event_handler, keys)
|
||||
event_handler = self._build_from_callable(analyzer)
|
||||
event_handler.start()
|
||||
return event_handler
|
||||
|
||||
def _build_from_callable(self, analyzer):
|
||||
server_connector = self._build_server_connector()
|
||||
server_connector.subscribe(analyzer.keys)
|
||||
event_handler = EventHandler(server_connector)
|
||||
event_handler.handle_event = analyzer.handle_event
|
||||
with suppress(AttributeError):
|
||||
event_handler.cleanup = analyzer.cleanup
|
||||
return event_handler
|
||||
|
||||
def _build_server_connector(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class EventHandlerAnalyzer:
|
||||
def __init__(self, event_handler, supplied_keys):
|
||||
self._event_handler = event_handler
|
||||
self._supplied_keys = supplied_keys
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
if self._supplied_keys is None:
|
||||
try:
|
||||
return self._event_handler.keys
|
||||
except AttributeError:
|
||||
raise ValueError('No keys supplied!')
|
||||
return self._supplied_keys
|
||||
|
||||
@property
|
||||
def handle_event(self):
|
||||
try:
|
||||
return self._event_handler.handle_event
|
||||
except AttributeError:
|
||||
if callable(self._event_handler):
|
||||
return self._event_handler
|
||||
raise ValueError('Object must implement handle_event or be a callable!')
|
||||
|
||||
@property
|
||||
def cleanup(self):
|
||||
return self._event_handler.cleanup
|
147
lib/tfw/event_handlers/test_event_handler.py
Normal file
147
lib/tfw/event_handlers/test_event_handler.py
Normal file
@ -0,0 +1,147 @@
|
||||
# pylint: disable=redefined-outer-name
|
||||
from secrets import token_urlsafe
|
||||
from random import randint
|
||||
|
||||
import pytest
|
||||
|
||||
from .event_handler_factory import EventHandlerFactoryBase
|
||||
from .event_handler import EventHandler
|
||||
|
||||
|
||||
class MockEventHandlerFactory(EventHandlerFactoryBase):
|
||||
def _build_server_connector(self):
|
||||
return MockServerConnector()
|
||||
|
||||
|
||||
class MockServerConnector:
|
||||
def __init__(self):
|
||||
self.keys = []
|
||||
self._on_message = None
|
||||
|
||||
def register_callback(self, callback):
|
||||
self._on_message = callback
|
||||
|
||||
def simulate_message(self, message):
|
||||
self._on_message(message)
|
||||
|
||||
def subscribe(self, keys):
|
||||
self.keys.extend(keys)
|
||||
|
||||
def unsubscribe(self, keys):
|
||||
for key in keys:
|
||||
self.keys.remove(key)
|
||||
|
||||
def send_message(self, message, scope=None):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
class MockEventHandler:
|
||||
def __init__(self):
|
||||
self.cleaned_up = False
|
||||
|
||||
def handle_event(self, message, server_connector):
|
||||
pass
|
||||
|
||||
def cleanup(self):
|
||||
self.cleaned_up = True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_msg():
|
||||
yield token_urlsafe(randint(16, 64))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_keys():
|
||||
yield [
|
||||
token_urlsafe(randint(2, 8))
|
||||
for _ in range(randint(16, 32))
|
||||
]
|
||||
|
||||
|
||||
def test_build_from_object(test_keys, test_msg):
|
||||
mock_eh = MockEventHandler()
|
||||
def test_handle_event(message, server_connector):
|
||||
raise RuntimeError(message, server_connector.keys)
|
||||
mock_eh.handle_event = test_handle_event
|
||||
eh = MockEventHandlerFactory().build(mock_eh, keys=test_keys)
|
||||
|
||||
with pytest.raises(RuntimeError) as err:
|
||||
eh.server_connector.simulate_message(test_msg)
|
||||
msg, keys = err.args
|
||||
assert msg == test_msg
|
||||
assert keys == test_keys
|
||||
assert not mock_eh.cleaned_up
|
||||
eh.stop()
|
||||
assert mock_eh.cleaned_up
|
||||
|
||||
|
||||
def test_build_from_object_with_keys(test_keys):
|
||||
mock_eh = MockEventHandler()
|
||||
mock_eh.keys = test_keys # pylint: disable=attribute-defined-outside-init
|
||||
eh = MockEventHandlerFactory().build(mock_eh)
|
||||
|
||||
assert not mock_eh.cleaned_up
|
||||
EventHandler.stop_all_instances()
|
||||
assert mock_eh.cleaned_up
|
||||
assert eh.server_connector.keys == test_keys
|
||||
|
||||
|
||||
def test_build_from_callable(test_keys, test_msg):
|
||||
class SomeCallable:
|
||||
def __init__(self):
|
||||
self.message = None
|
||||
self.cleaned_up = False
|
||||
def __call__(self, message, server_connector):
|
||||
self.message = message
|
||||
def cleanup(self):
|
||||
self.cleaned_up = True
|
||||
|
||||
mock_eh = SomeCallable()
|
||||
eh = MockEventHandlerFactory().build(mock_eh, keys=test_keys)
|
||||
|
||||
assert eh.server_connector.keys == test_keys
|
||||
assert not mock_eh.message
|
||||
eh.server_connector.simulate_message(test_msg)
|
||||
assert mock_eh.message == test_msg
|
||||
assert not mock_eh.cleaned_up
|
||||
eh.stop()
|
||||
assert mock_eh.cleaned_up
|
||||
|
||||
|
||||
def test_build_from_function(test_keys, test_msg):
|
||||
def some_function(message, server_connector):
|
||||
raise RuntimeError(message, server_connector.keys)
|
||||
eh = MockEventHandlerFactory().build(some_function, keys=test_keys)
|
||||
|
||||
assert eh.server_connector.keys == test_keys
|
||||
with pytest.raises(RuntimeError) as err:
|
||||
eh.server_connector.simulate_message(test_msg)
|
||||
msg, keys = err.args
|
||||
assert msg == test_msg
|
||||
assert keys == test_keys
|
||||
|
||||
|
||||
def test_build_from_lambda(test_keys, test_msg):
|
||||
def assert_messages_equal(msg):
|
||||
assert msg == test_msg
|
||||
fun = lambda msg, sc: assert_messages_equal(msg)
|
||||
eh = MockEventHandlerFactory().build(fun, keys=test_keys)
|
||||
eh.server_connector.simulate_message(test_msg)
|
||||
|
||||
|
||||
def test_build_raises_if_no_key():
|
||||
eh = MockEventHandler()
|
||||
with pytest.raises(ValueError):
|
||||
MockEventHandlerFactory().build(eh)
|
||||
|
||||
def test_handle_event(*_):
|
||||
pass
|
||||
with pytest.raises(ValueError):
|
||||
MockEventHandlerFactory().build(test_handle_event)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
MockEventHandlerFactory().build(lambda msg, sc: None)
|
Loading…
Reference in New Issue
Block a user