qubes-events.rst 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. :py:mod:`qubes.events` -- Qubes events
  2. ======================================
  3. Some objects in qubes (most notably domains) emit events. You may hook them and
  4. execute your code when particular event is fired. Events in qubes are added
  5. class-wide -- it is not possible to add event handler to one instance only, you
  6. have to add handler for whole class.
  7. Firing events
  8. -------------
  9. Events are fired by calling :py:meth:`qubes.events.Emitter.fire_event`. The
  10. first argument is event name (a string). You can fire any event you wish, the
  11. names are not checked in any way, however each class' documentation tells what
  12. standard events will be fired on it. When firing an event, caller may specify
  13. some optional keyword arguments. Those are dependent on the particular event in
  14. question -- they are passed as-is to handlers.
  15. Event handlers are fired in reverse method resolution order, that is, first for
  16. parent class and then for it's child. For each class, first are called handlers
  17. defined in it's source, then handlers from extensions and last the callers added
  18. manually.
  19. The :py:meth:`qubes.events.Emitter.fire_event` method have keyword argument
  20. `pre_event`, which fires events in reverse order. It is suitable for events
  21. fired before some action is performed. You may at your own responsibility raise
  22. exceptions from such events to try to prevent such action.
  23. Events handlers may yield values. Those values are aggregated and returned
  24. to the caller as a list of those values. See below for details.
  25. Handling events
  26. ---------------
  27. There are several ways to handle events. In all cases you supply a callable
  28. (most likely function or method) that will be called when someone fires the
  29. event. The first argument passed to the callable will be the object instance on
  30. which the event was fired and the second one is the event name. The rest are
  31. passed from :py:meth:`qubes.events.Emitter.fire_event` as described previously.
  32. One callable can handle more than one event.
  33. The easiest way to hook an event is to use
  34. :py:func:`qubes.events.handler` decorator.
  35. .. code-block:: python
  36. import qubes.events
  37. class MyClass(qubes.events.Emitter):
  38. @qubes.events.handler('event1', 'event2')
  39. def event_handler(self, event):
  40. if event == 'event1':
  41. print('Got event 1')
  42. elif event == 'event2':
  43. print('Got event 2')
  44. o = MyClass()
  45. o.fire_event('event1')
  46. Note that your handler will be called for all instances of this class.
  47. .. TODO: extensions
  48. .. TODO: add/remove_handler
  49. .. TODO: wildcards (property-set:*)
  50. Handling events with variable signature
  51. ---------------------------------------
  52. Some events are specified with variable signature (i.e. they may have different
  53. number of arguments on each call to handlers). You can write handlers just like
  54. every other python function with variable signature.
  55. .. code-block:: python
  56. import qubes
  57. def on_property_change(subject, event, name, newvalue, oldvalue=None):
  58. if oldvalue is None:
  59. print('Property {} initialised to {!r}'.format(name, newvalue))
  60. else:
  61. print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
  62. app = qubes.Qubes()
  63. app.add_handler('property-set:default_netvm')
  64. If you expect :py:obj:`None` to be a reasonable value of the property, you have
  65. a problem. One way to solve it is to invent your very own, magic
  66. :py:class:`object` instance.
  67. .. code-block:: python
  68. import qubes
  69. MAGIC_NO_VALUE = object()
  70. def on_property_change(subject, event, name, newvalue, oldvalue=MAGIC_NO_VALUE):
  71. if oldvalue is MAGIC_NO_VALUE:
  72. print('Property {} initialised to {!r}'.format(name, newvalue))
  73. else:
  74. print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))
  75. app = qubes.Qubes()
  76. app.add_handler('property-set:default_netvm')
  77. There is no possible way of collision other than intentionally passing this very
  78. object (not even passing similar featureless ``object()``), because ``is``
  79. python syntax checks object's :py:meth:`id`\ entity, which will be different for
  80. each :py:class:`object` instance.
  81. Returning values from events
  82. ----------------------------
  83. Some events may be called to collect values from the handlers. For example the
  84. event ``is-fully-usable`` allows plugins to report a domain as not fully usable.
  85. Such handlers, instead of returning :py:obj:`None` (which is the default when
  86. the function does not include ``return`` statement), should return an iterable
  87. or itself be a generator. Those values are aggregated from all handlers and
  88. returned to the caller as list. The order of this list is undefined.
  89. .. code-block:: python
  90. import qubes.events
  91. class MyClass(qubes.events.Emitter):
  92. @qubes.events.handler('event1')
  93. def event1_handler1(self, event):
  94. # do not return anything, equivalent to "return" and "return None"
  95. pass
  96. @qubes.events.handler('event1')
  97. def event1_handler2(self, event):
  98. yield 'aqq'
  99. yield 'zxc'
  100. @qubes.events.handler('event1')
  101. def event1_handler3(self, event):
  102. return ('123', '456')
  103. o = MyClass()
  104. # returns ['aqq', 'zxc', '123', '456'], possibly not in order
  105. effect = o.fire_event('event1')
  106. Asynchronous event handling
  107. ---------------------------
  108. Event handlers can be defined as coroutine. This way they can execute long
  109. running actions without blocking the whole qubesd process. To define
  110. asynchronous event handler, annotate a coroutine (a function defined with
  111. `async def`, or decorated with :py:func:`asyncio.coroutine`) with
  112. :py:func:`qubes.events.handler` decorator. By definition, order of
  113. such handlers is undefined.
  114. Asynchronous events can be fired using
  115. :py:meth:`qubes.events.Emitter.fire_event_async` method. It will handle both
  116. synchronous and asynchronous handlers. It's an error to register asynchronous
  117. handler (a coroutine) for synchronous event (the one fired with
  118. :py:meth:`qubes.events.Emitter.fire_event`) - it will result in
  119. :py:exc:`RuntimeError` exception.
  120. .. code-block:: python
  121. import asyncio
  122. import qubes.events
  123. class MyClass(qubes.events.Emitter):
  124. @qubes.events.handler('event1', 'event2')
  125. @asyncio.coroutine
  126. def event_handler(self, event):
  127. if event == 'event1':
  128. print('Got event 1, starting long running action')
  129. yield from asyncio.sleep(10)
  130. print('Done')
  131. o = MyClass()
  132. loop = asyncio.get_event_loop()
  133. loop.run_until_complete(o.fire_event_async('event1'))
  134. Asynchronous event handlers can also return value - but only a collection, not
  135. yield individual values (because of python limitation):
  136. .. code-block:: python
  137. import asyncio
  138. import qubes.events
  139. class MyClass(qubes.events.Emitter):
  140. @qubes.events.handler('event1')
  141. @asyncio.coroutine
  142. def event_handler(self, event):
  143. print('Got event, starting long running action')
  144. yield from asyncio.sleep(10)
  145. return ('result1', 'result2')
  146. @qubes.events.handler('event1')
  147. @asyncio.coroutine
  148. def another_handler(self, event):
  149. print('Got event, starting long running action')
  150. yield from asyncio.sleep(10)
  151. return ('result3', 'result4')
  152. @qubes.events.handler('event1')
  153. def synchronous_handler(self, event):
  154. yield 'sync result'
  155. o = MyClass()
  156. loop = asyncio.get_event_loop()
  157. # returns ['sync result', 'result1', 'result2', 'result3', 'result4'],
  158. # possibly not in order
  159. effects = loop.run_until_complete(o.fire_event_async('event1'))
  160. Module contents
  161. ---------------
  162. .. automodule:: qubes.events
  163. :members:
  164. :show-inheritance:
  165. .. vim: ts=3 sw=3 et