qubes.events – Qubes events

Some objects in qubes (most notably domains) emit events. You may hook them and execute your code when particular event is fired. Events in qubes are added class-wide – it is not possible to add event handler to one instance only, you have to add handler for whole class.

Firing events

Events are fired by calling qubes.events.Emitter.fire_event(). The first argument is event name (a string). You can fire any event you wish, the names are not checked in any way, however each class’ documentation tells what standard events will be fired on it. When firing an event, caller may specify some optional keyword arguments. Those are dependent on the particular event in question – they are passed as-is to handlers.

Event handlers are fired in reverse method resolution order, that is, first for parent class and then for it’s child. For each class, first are called handlers defined in it’s source, then handlers from extensions and last the callers added manually.

The qubes.events.Emitter.fire_event() method have keyword argument pre_event, which 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.

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

There are several ways to handle events. In all cases you supply a callable (most likely function or method) that will be called when someone fires the event. The first argument passed to the callable will be the object instance on which the event was fired and the second one is the event name. The rest are passed from 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 use qubes.events.handler() decorator.

import qubes.events

class MyClass(qubes.events.Emitter):
    @qubes.events.handler('event1', 'event2')
    def event_handler(self, event):
        if event == 'event1':
            print('Got event 1')
        elif event == 'event2':
            print('Got event 2')

o = MyClass()
o.events_enabled = True
o.fire_event('event1')

Note that your handler will be called for all instances of this class.

Handling events with variable signature

Some events are specified with variable signature (i.e. they may have different number of arguments on each call to handlers). You can write handlers just like every other python function with variable signature.

import qubes

def on_property_change(subject, event, name, newvalue, oldvalue=None):
    if oldvalue is None:
        print('Property {} initialised to {!r}'.format(name, newvalue))
    else:
        print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))

app = qubes.Qubes()
app.add_handler('property-set:default_netvm')

If you expect 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 object instance.

import qubes

MAGIC_NO_VALUE = object()
def on_property_change(subject, event, name, newvalue, oldvalue=MAGIC_NO_VALUE):
    if oldvalue is MAGIC_NO_VALUE:
        print('Property {} initialised to {!r}'.format(name, newvalue))
    else:
        print('Property {} changed {!r} -> {!r}'.format(name, oldvalue, newvalue))

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 python syntax checks object’s id()entity, which will be different for each 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 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.

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()
o.events_enabled = True

# returns ['aqq', 'zxc', '123', '456'], possibly not in order
effect = o.fire_event('event1')

Asynchronous event handling

Event handlers can be defined as coroutine. This way they can execute long running actions without blocking the whole qubesd process. To define asynchronous event handler, annotate a coroutine (a function defined with async def) with qubes.events.handler() decorator. By definition, order of such handlers is undefined.

Asynchronous events can be fired using qubes.events.Emitter.fire_event_async() method. It will handle both synchronous and asynchronous handlers. It’s an error to register asynchronous handler (a coroutine) for synchronous event (the one fired with qubes.events.Emitter.fire_event()) - it will result in RuntimeError exception.

import asyncio
import qubes.events

class MyClass(qubes.events.Emitter):
    @qubes.events.handler('event1', 'event2')
    async def event_handler(self, event):
        if event == 'event1':
            print('Got event 1, starting long running action')
            await asyncio.sleep(10)
            print('Done')

o = MyClass()
o.events_enabled = True
loop = asyncio.get_event_loop()
loop.run_until_complete(o.fire_event_async('event1'))

Asynchronous event handlers can also return value - but only a collection, not yield individual values (because of python limitation):

import asyncio
import qubes.events

class MyClass(qubes.events.Emitter):
    @qubes.events.handler('event1')
    async def event_handler(self, event):
         print('Got event, starting long running action')
         await asyncio.sleep(10)
         return ('result1', 'result2')

    @qubes.events.handler('event1')
    async def another_handler(self, event):
         print('Got event, starting long running action')
         await asyncio.sleep(10)
         return ('result3', 'result4')

    @qubes.events.handler('event1')
    def synchronous_handler(self, event):
         yield 'sync result'

o = MyClass()
o.events_enabled = True
loop = asyncio.get_event_loop()
# returns ['sync result', 'result1', 'result2', 'result3', 'result4'],
# possibly not in order
effects = loop.run_until_complete(o.fire_event_async('event1'))

Module contents

Qubes events.

Events are fired when something happens, like VM start or stop, property change etc.

class qubes.events.Emitter(*args, **kwargs)[source]

Bases: object

Subject that can emit events.

By default all events are disabled not to interfere with loading from XML. To enable event dispatch, set events_enabled to True.

add_handler(event, func)[source]

Add event handler to subject’s class.

This is class method, it is invalid to call it on object instance.

Parameters:
fire_event(event, pre_event=False, **kwargs)[source]

Call all handlers for an event.

Handlers are called for class and all parent classes, in reversed or true (depending on pre_event parameter) method resolution order. For each class first are called bound handlers (specified in class definition), then handlers from extensions. Aside from above, remaining order is undefined.

This method call only synchronous handlers. If any asynchronous handler is registered for the event, :py:class:RuntimeError is raised.

Parameters:
  • event (str) – event identifier

  • pre_event – is this -pre- event? reverse handlers calling order

Returns:

list of effects

All kwargs are passed verbatim. They are different for different events.

async fire_event_async(event, pre_event=False, **kwargs)[source]

Call all handlers for an event, allowing async calls.

Handlers are called for class and all parent classes, in reversed or true (depending on pre_event parameter) method resolution order. For each class first are called bound handlers (specified in class definition), then handlers from extensions. Aside from above, remaining order is undefined.

This method call both synchronous and asynchronous handlers. Order of asynchronous calls is, by definition, undefined.

See also

fire_event()

Parameters:
  • event (str) – event identifier

  • pre_event – is this -pre- event? reverse handlers calling order

Returns:

list of effects

All kwargs are passed verbatim. They are different for different events.

remove_handler(event, func)[source]

Remove event handler from subject’s class.

This is class method, it is invalid to call it on object instance.

This method must be called on the same class as add_handler() was called to register the handler.

Parameters:
class qubes.events.EmitterMeta(name, bases, dict_)[source]

Bases: type

Metaclass for Emitter

qubes.events.handler(*events)[source]

Event handler decorator factory.

To hook an event, decorate a method in your plugin class with this decorator.

Some event handlers may be async functions. See appropriate event documentation for details.

Note

For hooking events from extensions, see qubes.ext.handler().

Parameters:

events (str) – events names, can contain basic wildcards (*, ?)

qubes.events.ishandler(obj)[source]

Test if a method is hooked to an event.

Parameters:

o (object) – suspected hook

Returns:

True when function is a hook, False otherwise

Return type:

bool