events: add support for per-instance handlers
This commit is contained in:
parent
bd1f84fcec
commit
da7496794a
@ -40,29 +40,7 @@ which the event was fired and the second one is the event name. The rest are
|
||||
passed from :py:meth:`qubes.events.Emitter.fire_event` as described previously.
|
||||
One callable can handle more than one event.
|
||||
|
||||
The easiest way to hook an event is to invoke
|
||||
:py:meth:`qubes.events.Emitter.add_handler` classmethod.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import qubes.events
|
||||
|
||||
class MyClass(qubes.events.Emitter):
|
||||
pass
|
||||
|
||||
def event_handler(subject, event):
|
||||
if event == 'event1':
|
||||
print('Got event 1')
|
||||
elif event == 'event2':
|
||||
print('Got event 2')
|
||||
|
||||
MyClass.add_handler('event1', event_handler)
|
||||
MyClass.add_handler('event2', event_handler)
|
||||
|
||||
o = MyClass()
|
||||
o.fire_event('event1')
|
||||
|
||||
If you wish to define handler in the class definition, the best way is to use
|
||||
The easiest way to hook an event is to use
|
||||
:py:func:`qubes.events.handler` decorator.
|
||||
|
||||
.. code-block:: python
|
||||
@ -80,7 +58,10 @@ If you wish to define handler in the class definition, the best way is to use
|
||||
o = MyClass()
|
||||
o.fire_event('event1')
|
||||
|
||||
Note that your handler will be called for all instances of this class.
|
||||
|
||||
.. TODO: extensions
|
||||
.. TODO: add/remove_handler
|
||||
|
||||
|
||||
Handling events with variable signature
|
||||
@ -100,7 +81,8 @@ every other python function with variable signature.
|
||||
else:
|
||||
print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
|
||||
|
||||
qubes.Qubes.add_handler('property-set:default_netvm')
|
||||
app = qubes.Qubes()
|
||||
app.add_handler('property-set:default_netvm')
|
||||
|
||||
If you expect :py:obj:`None` to be a reasonable value of the property, you have
|
||||
a problem. One way to solve it is to invent your very own, magic
|
||||
@ -117,7 +99,8 @@ a problem. One way to solve it is to invent your very own, magic
|
||||
else:
|
||||
print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
|
||||
|
||||
qubes.Qubes.add_handler('property-set:default_netvm')
|
||||
app = qubes.Qubes()
|
||||
app.add_handler('property-set:default_netvm')
|
||||
|
||||
There is no possible way of collision other than intentionally passing this very
|
||||
object (not even passing similar featureless ``object()``), because ``is``
|
||||
|
@ -27,6 +27,8 @@ etc.
|
||||
|
||||
import collections
|
||||
|
||||
import itertools
|
||||
|
||||
|
||||
def handler(*events):
|
||||
'''Event handler decorator factory.
|
||||
@ -88,7 +90,7 @@ class EmitterMeta(type):
|
||||
continue
|
||||
|
||||
for event in attr.ha_events:
|
||||
cls.add_handler(event, attr)
|
||||
cls.__handlers__[event].add(attr)
|
||||
|
||||
|
||||
class Emitter(object, metaclass=EmitterMeta):
|
||||
@ -102,10 +104,10 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
super(Emitter, self).__init__(*args, **kwargs)
|
||||
if not hasattr(self, 'events_enabled'):
|
||||
self.events_enabled = False
|
||||
self.__handlers__ = collections.defaultdict(set)
|
||||
|
||||
|
||||
@classmethod
|
||||
def add_handler(cls, event, func):
|
||||
def add_handler(self, event, func):
|
||||
'''Add event handler to subject's class.
|
||||
|
||||
This is class method, it is invalid to call it on object instance.
|
||||
@ -115,10 +117,9 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
'''
|
||||
|
||||
# pylint: disable=no-member
|
||||
cls.__handlers__[event].add(func)
|
||||
self.__handlers__[event].add(func)
|
||||
|
||||
@classmethod
|
||||
def remove_handler(cls, event, func):
|
||||
def remove_handler(self, event, func):
|
||||
'''Remove event handler from subject's class.
|
||||
|
||||
This is class method, it is invalid to call it on object instance.
|
||||
@ -131,8 +132,7 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
'''
|
||||
|
||||
# pylint: disable=no-member
|
||||
cls.__handlers__[event].remove(func)
|
||||
|
||||
self.__handlers__[event].remove(func)
|
||||
|
||||
def _fire_event_in_order(self, order, event, kwargs):
|
||||
'''Fire event for classes in given order.
|
||||
@ -145,12 +145,14 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
return []
|
||||
|
||||
effects = []
|
||||
for cls in order:
|
||||
if not hasattr(cls, '__handlers__'):
|
||||
for i in order:
|
||||
try:
|
||||
handlers_dict = i.__handlers__
|
||||
except AttributeError:
|
||||
continue
|
||||
handlers = cls.__handlers__[event]
|
||||
if '*' in cls.__handlers__:
|
||||
handlers = cls.__handlers__['*'] | handlers
|
||||
handlers = handlers_dict.get(event, set())
|
||||
if '*' in handlers_dict:
|
||||
handlers = handlers_dict['*'] | handlers
|
||||
for func in sorted(handlers,
|
||||
key=(lambda handler: hasattr(handler, 'ha_bound')),
|
||||
reverse=True):
|
||||
@ -159,7 +161,6 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
effects.extend(effect)
|
||||
return effects
|
||||
|
||||
|
||||
def fire_event(self, event, **kwargs):
|
||||
'''Call all handlers for an event.
|
||||
|
||||
@ -178,7 +179,8 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
events.
|
||||
'''
|
||||
|
||||
return self._fire_event_in_order(reversed(self.__class__.__mro__),
|
||||
return self._fire_event_in_order(
|
||||
itertools.chain(reversed(self.__class__.__mro__), (self,)),
|
||||
event, kwargs)
|
||||
|
||||
|
||||
@ -199,5 +201,6 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
events.
|
||||
'''
|
||||
|
||||
return self._fire_event_in_order(self.__class__.__mro__,
|
||||
return self._fire_event_in_order(
|
||||
itertools.chain((self,), self.__class__.__mro__),
|
||||
event, kwargs)
|
||||
|
@ -45,11 +45,12 @@ class Extension(object):
|
||||
|
||||
if attr.ha_vm is not None:
|
||||
for event in attr.ha_events:
|
||||
attr.ha_vm.add_handler(event, attr)
|
||||
attr.ha_vm.__handlers__[event].add(attr)
|
||||
else:
|
||||
# global hook
|
||||
for event in attr.ha_events:
|
||||
qubes.Qubes.add_handler(event, attr)
|
||||
# pylint: disable=no-member
|
||||
qubes.Qubes.__handlers__[event].add(attr)
|
||||
|
||||
return cls._instance
|
||||
|
||||
|
@ -103,3 +103,35 @@ class TC_00_Emitter(qubes.tests.QubesTestCase):
|
||||
self.assertEqual(testevent_fired[0], 3)
|
||||
emitter.fire_event('bar')
|
||||
self.assertEqual(testevent_fired[0], 4)
|
||||
|
||||
def test_005_instance_handlers(self):
|
||||
class TestEmitter(qubes.events.Emitter):
|
||||
@qubes.events.handler('testevent')
|
||||
def on_testevent_1(self, event):
|
||||
yield 'testevent_1'
|
||||
|
||||
def on_testevent_2(subject, event):
|
||||
yield 'testevent_2'
|
||||
|
||||
emitter = TestEmitter()
|
||||
emitter.add_handler('testevent', on_testevent_2)
|
||||
emitter.events_enabled = True
|
||||
|
||||
emitter2 = TestEmitter()
|
||||
emitter2.events_enabled = True
|
||||
|
||||
with self.subTest('fire_event'):
|
||||
effect = emitter.fire_event('testevent')
|
||||
effect2 = emitter2.fire_event('testevent')
|
||||
self.assertEqual(list(effect),
|
||||
['testevent_1', 'testevent_2'])
|
||||
self.assertEqual(list(effect2),
|
||||
['testevent_1'])
|
||||
|
||||
with self.subTest('fire_event_pre'):
|
||||
effect = emitter.fire_event_pre('testevent')
|
||||
effect2 = emitter2.fire_event_pre('testevent')
|
||||
self.assertEqual(list(effect),
|
||||
['testevent_2', 'testevent_1'])
|
||||
self.assertEqual(list(effect2),
|
||||
['testevent_1'])
|
||||
|
Loading…
Reference in New Issue
Block a user