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.
|
passed from :py:meth:`qubes.events.Emitter.fire_event` as described previously.
|
||||||
One callable can handle more than one event.
|
One callable can handle more than one event.
|
||||||
|
|
||||||
The easiest way to hook an event is to invoke
|
The easiest way to hook an event is to use
|
||||||
: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
|
|
||||||
:py:func:`qubes.events.handler` decorator.
|
:py:func:`qubes.events.handler` decorator.
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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 = MyClass()
|
||||||
o.fire_event('event1')
|
o.fire_event('event1')
|
||||||
|
|
||||||
|
Note that your handler will be called for all instances of this class.
|
||||||
|
|
||||||
.. TODO: extensions
|
.. TODO: extensions
|
||||||
|
.. TODO: add/remove_handler
|
||||||
|
|
||||||
|
|
||||||
Handling events with variable signature
|
Handling events with variable signature
|
||||||
@ -100,7 +81,8 @@ every other python function with variable signature.
|
|||||||
else:
|
else:
|
||||||
print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
|
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
|
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
|
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:
|
else:
|
||||||
print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
|
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
|
There is no possible way of collision other than intentionally passing this very
|
||||||
object (not even passing similar featureless ``object()``), because ``is``
|
object (not even passing similar featureless ``object()``), because ``is``
|
||||||
|
@ -27,6 +27,8 @@ etc.
|
|||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
def handler(*events):
|
def handler(*events):
|
||||||
'''Event handler decorator factory.
|
'''Event handler decorator factory.
|
||||||
@ -88,7 +90,7 @@ class EmitterMeta(type):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for event in attr.ha_events:
|
for event in attr.ha_events:
|
||||||
cls.add_handler(event, attr)
|
cls.__handlers__[event].add(attr)
|
||||||
|
|
||||||
|
|
||||||
class Emitter(object, metaclass=EmitterMeta):
|
class Emitter(object, metaclass=EmitterMeta):
|
||||||
@ -102,10 +104,10 @@ class Emitter(object, metaclass=EmitterMeta):
|
|||||||
super(Emitter, self).__init__(*args, **kwargs)
|
super(Emitter, self).__init__(*args, **kwargs)
|
||||||
if not hasattr(self, 'events_enabled'):
|
if not hasattr(self, 'events_enabled'):
|
||||||
self.events_enabled = False
|
self.events_enabled = False
|
||||||
|
self.__handlers__ = collections.defaultdict(set)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
def add_handler(self, event, func):
|
||||||
def add_handler(cls, event, func):
|
|
||||||
'''Add event handler to subject's class.
|
'''Add event handler to subject's class.
|
||||||
|
|
||||||
This is class method, it is invalid to call it on object instance.
|
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
|
# pylint: disable=no-member
|
||||||
cls.__handlers__[event].add(func)
|
self.__handlers__[event].add(func)
|
||||||
|
|
||||||
@classmethod
|
def remove_handler(self, event, func):
|
||||||
def remove_handler(cls, event, func):
|
|
||||||
'''Remove event handler from subject's class.
|
'''Remove event handler from subject's class.
|
||||||
|
|
||||||
This is class method, it is invalid to call it on object instance.
|
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
|
# pylint: disable=no-member
|
||||||
cls.__handlers__[event].remove(func)
|
self.__handlers__[event].remove(func)
|
||||||
|
|
||||||
|
|
||||||
def _fire_event_in_order(self, order, event, kwargs):
|
def _fire_event_in_order(self, order, event, kwargs):
|
||||||
'''Fire event for classes in given order.
|
'''Fire event for classes in given order.
|
||||||
@ -145,12 +145,14 @@ class Emitter(object, metaclass=EmitterMeta):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
effects = []
|
effects = []
|
||||||
for cls in order:
|
for i in order:
|
||||||
if not hasattr(cls, '__handlers__'):
|
try:
|
||||||
|
handlers_dict = i.__handlers__
|
||||||
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
handlers = cls.__handlers__[event]
|
handlers = handlers_dict.get(event, set())
|
||||||
if '*' in cls.__handlers__:
|
if '*' in handlers_dict:
|
||||||
handlers = cls.__handlers__['*'] | handlers
|
handlers = handlers_dict['*'] | handlers
|
||||||
for func in sorted(handlers,
|
for func in sorted(handlers,
|
||||||
key=(lambda handler: hasattr(handler, 'ha_bound')),
|
key=(lambda handler: hasattr(handler, 'ha_bound')),
|
||||||
reverse=True):
|
reverse=True):
|
||||||
@ -159,7 +161,6 @@ class Emitter(object, metaclass=EmitterMeta):
|
|||||||
effects.extend(effect)
|
effects.extend(effect)
|
||||||
return effects
|
return effects
|
||||||
|
|
||||||
|
|
||||||
def fire_event(self, event, **kwargs):
|
def fire_event(self, event, **kwargs):
|
||||||
'''Call all handlers for an event.
|
'''Call all handlers for an event.
|
||||||
|
|
||||||
@ -178,7 +179,8 @@ class Emitter(object, metaclass=EmitterMeta):
|
|||||||
events.
|
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)
|
event, kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -199,5 +201,6 @@ class Emitter(object, metaclass=EmitterMeta):
|
|||||||
events.
|
events.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return self._fire_event_in_order(self.__class__.__mro__,
|
return self._fire_event_in_order(
|
||||||
|
itertools.chain((self,), self.__class__.__mro__),
|
||||||
event, kwargs)
|
event, kwargs)
|
||||||
|
@ -45,11 +45,12 @@ class Extension(object):
|
|||||||
|
|
||||||
if attr.ha_vm is not None:
|
if attr.ha_vm is not None:
|
||||||
for event in attr.ha_events:
|
for event in attr.ha_events:
|
||||||
attr.ha_vm.add_handler(event, attr)
|
attr.ha_vm.__handlers__[event].add(attr)
|
||||||
else:
|
else:
|
||||||
# global hook
|
# global hook
|
||||||
for event in attr.ha_events:
|
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
|
return cls._instance
|
||||||
|
|
||||||
|
@ -103,3 +103,35 @@ class TC_00_Emitter(qubes.tests.QubesTestCase):
|
|||||||
self.assertEqual(testevent_fired[0], 3)
|
self.assertEqual(testevent_fired[0], 3)
|
||||||
emitter.fire_event('bar')
|
emitter.fire_event('bar')
|
||||||
self.assertEqual(testevent_fired[0], 4)
|
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