From c5962910d0764c969cf2b0e94ee6fa4486d92ebc Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Fri, 8 Apr 2016 12:35:11 +0200 Subject: [PATCH] qubes/events: Rework firing events for effect From now, the handlers should yield their values, not return. --- doc/qubes-events.rst | 40 +++++++++++++++++++++++++++++++++++++--- qubes/events.py | 2 +- qubes/tests/events.py | 24 ++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/doc/qubes-events.rst b/doc/qubes-events.rst index b190da67..3ccd3c1f 100644 --- a/doc/qubes-events.rst +++ b/doc/qubes-events.rst @@ -26,9 +26,8 @@ fires events in reverse order. It is suitable for events fired before some action is performed. You may at your own responsibility raise exceptions from such events to try to prevent such action. -Event handlers may return a value. Those values are aggregated and returned -to the caller as a list of those values. The order of this list is undefined. -:py:obj:`None` values are omitted. +Events handlers may yield values. Those values are aggregated and returned +to the caller as a list of those values. See below for details. Handling events --------------- @@ -125,6 +124,41 @@ python syntax checks object's :py:meth:`id`\ entity, which will be different for each :py:class:`object` instance. +Returning values from events +---------------------------- + +Some events may be called to collect values from the handlers. For example the +event ``is-fully-usable`` allows plugins to report a domain as not fully usable. +Such handlers, instead of returning :py:obj:`None` (which is the default when +the function does not include ``return`` statement), should return an iterable +or itself be a generator. Those values are aggregated from all handlers and +returned to the caller as list. The order of this list is undefined. + +.. code-block:: python + + import qubes.events + + class MyClass(qubes.events.Emitter): + @qubes.events.handler('event1') + def event1_handler1(self, event): + # do not return anything, equivalent to "return" and "return None" + pass + + @qubes.events.handler('event1') + def event1_handler2(self, event): + yield 'aqq' + yield 'zxc' + + @qubes.events.handler('event1') + def event1_handler3(self, event): + return ('123', '456') + + o = MyClass() + + # returns ['aqq', 'zxc', '123', '456'], possibly not in order + effect = o.fire_event('event1') + + Module contents --------------- diff --git a/qubes/events.py b/qubes/events.py index 9c39e807..cfc683a4 100644 --- a/qubes/events.py +++ b/qubes/events.py @@ -138,7 +138,7 @@ class Emitter(object): reverse=True): effect = func(self, event, *args, **kwargs) if effect is not None: - effects.append(effect) + effects.extend(effect) return effects diff --git a/qubes/tests/events.py b/qubes/tests/events.py index 3e9d2791..b720368a 100644 --- a/qubes/tests/events.py +++ b/qubes/tests/events.py @@ -58,3 +58,27 @@ class TC_00_Emitter(qubes.tests.QubesTestCase): emitter.events_enabled = True emitter.fire_event('testevent') self.assertTrue(emitter.testevent_fired) + + def test_002_fire_for_effect(self): + class TestEmitter(qubes.events.Emitter): + @qubes.events.handler('testevent') + def on_testevent_1(self, event): + pass + + @qubes.events.handler('testevent') + def on_testevent_2(self, event): + yield 'testvalue1' + yield 'testvalue2' + + @qubes.events.handler('testevent') + def on_testevent_3(self, event): + return ('testvalue3', 'testvalue4') + + emitter = TestEmitter() + emitter.events_enabled = True + emitter.fire_event('testevent') + + effect = emitter.fire_event('testevent') + + self.assertItemsEqual(effect, + ('testvalue1', 'testvalue2', 'testvalue3', 'testvalue4'))