triggerset: Support for managing triggers and handlers

triggerset: Support for managing triggers and handlers

This module defines one class, TriggerSet, which implements a simple callback mechanism. A TriggerSet instance contains a set of named triggers, each of which may have a number of handlers registered with it. Activating a trigger in the instance causes all its handlers to be called, in the order of registration.

This document describes the mechanics of creating or subscribing to a trigger, but not what triggers exist. Well Known Triggers describes the most widely useful triggers available in ChimeraX.

Example

The following example creates a TriggerSet instance named ts and adds a trigger named conrad. Two handlers are registered: the first reports its arguments; the second reports its arguments and then deregisters itself.

import triggerset

ts = triggerset.TriggerSet()
ts.add_trigger('conrad')

def first(trigger, trigger_data):
    print('trigger =', trigger)
    print('  trigger_data =', trigger_data)

class Second:

    def __init__(self, ts):
        self.triggerset = ts
        self.handler = None

    def trigger_handler(self, trigger, trigger_data):
        print('trigger =', trigger)
        print('  triggerset =', self.triggerset)
        print('  handler =', self.handler)
        print('  trigger_data =', trigger_data)
        if self.triggerset and self.handler:
            self.triggerset.remove_handler(self.handler)
            self.handler = None

h1 = ts.add_handler('conrad', first)
o = Second(ts)
o.handler = ts.add_handler('conrad', o.trigger_handler)

ts.activate_trigger('conrad', 1)
print()
ts.activate_trigger('conrad', 2)

The output from this example is:

trigger = conrad
  trigger_data = 1
trigger = conrad
  triggerset = <triggerset.TriggerSet instance at 1400f3010>
  handler = <triggerset._TriggerHandler instance at 140097ac0>
  trigger_data = 1

trigger = conrad trigger_data = 2

If a handler returns the value triggerset.DEREGISTER, then the handler will be deregistered after it returns. Therfore, the ‘Second.handler()’ method above could have been written more simply as:

def trigger_handler(trigger, trigger_data):
    print('trigger =', trigger,
        'triggerset =', self.triggerset,
        'handler =', self.handler,
        'trigger_data =', trigger_data)
    self.handler = None
    return triggerset.DEREGISTER
class TriggerSet

Bases: object

Keep track of related groups of triggers.

activate_trigger(name, data, absent_okay=False)

Supported API . Invoke all handlers registered with the given name.

triggerset.activate_trigger(name, data) => None

If no trigger corresponds to name, an exception is raised. Handlers are invoked in the order in which they were registered, and are called in the following manner:

func(name, data)

where func is the function previously registered with add_handler, name is the name of the trigger, and data is the data argument to the activate_trigger() call.

During trigger activation, handlers may add new handlers or delete existing handlers. These operations, however, are deferred until after all handlers have been invoked; in particular, for the current trigger activation, newly added handlers will not be invoked and newly deleted handlers will be invoked.

add_dependency(trigger, after)

Supported API . Specify firing order dependency for ‘trigger’.

triggerset.add_dependency(trigger, after) => None

Specifies that ‘trigger’ should be fired after all triggers in the ‘after’ list when a trigger set is blocked and released. If the dependency relationship conflicts with a previously specified dependency, a ‘ValueError’ exception is thrown. If any name given is not a trigger name, a ‘KeyError’ exception is thrown.

Note that the dependency information is _only_ used when an entire trigger set is blocked and released, and not used for blocking and releasing of individual triggers.

add_handler(name, func)

Supported API . Register a function with the trigger with the given name.

triggerset.add_handler(name, func) => handler

If no trigger corresponds to name, an exception is raised. add_handler returns a handler for use with remove_handler.

add_trigger(name, *, usage_cb=None, after=None, default_one_time=False, remove_bad_handlers=False)

Supported API . Add a trigger with the given name.

triggerset.add_trigger(name) => None

The name should be a string. If a trigger by the same name already exists, an exception is raised.

The optional ‘usage_cb’ argument can be used to provide a callback function for when the trigger goes from no handlers registered to at least one registered, and vice versa. The callback function will be given the trigger name and 1 or 0 (respectively) as arguments.

The optional argument ‘default_one_time’ (default False) may be used to designate triggers whose registered handlers should only be called once. For example, an “exit trigger” may only want its handler run once and then discarded without having the handler explicitly deregister itself or return DEREGISTER.

The optional argument ‘after’ (default None) may be a list of trigger names, and is passed to a call to ‘add_dependency’ after the new trigger has been created.

if ‘remove_bad_handlers’ is True, then handlers that throw errors will be removed from the list of handlers if they throw an error.

block()

Experimental API . Block all triggers from firing until released.

triggerset.block() => None

block_trigger(name)

Experimental API . Context manager to block all handlers registered with the given name until the context is exited, at which point the triggers fire (if applicable).

with triggerset.block_trigger(name):

…code to execute with trigger blocked…

If no trigger corresponds to name, an exception is raised. Blocked trigger contexts can be nested.

delete_trigger(name)

Supported API . Remove a trigger with the given name.

triggerset.delete_trigger(name) => None

The name should be a string. If no trigger corresponds to the name, an exception is raised.

has_handlers(name)

Supported API . Return if the trigger with the given name has any handlers.

triggerset.has_handlers(name) => bool

If no trigger corresponds to name, an exception is raised.

has_trigger(name)

Supported API . Check if trigger exists.

is_blocked()

Experimental API . Returns whether entire trigger set is blocked.

is_trigger_blocked(name)

Experimental API . Returns whether named trigger is blocked.

manual_block(name)

Experimental API . For situations where the block and release aren’t in the same code block, and therefore the context-manager version (block_trigger) can’t be used.

manual_release(name)

Experimental API . Complement to manual_block

profile_trigger(name, sort_by='time', num_entries=20)

Supported API . Profile the next firing of the given trigger.

triggerset.profile_trigger(name) => None

The resulting profile information will be printed to the ChimeraX log. The optional sort_by argument may be any of the arguments allowed by Python’s pstats.sort_stats() method. Typically, the most useful of these are:

‘calls’: call count ‘cumulative’: cumulative time ‘time’: internal time

release()

Experimental API . Release trigger blocks and fire trigger in dependency order.

triggerset.release() => boolean

Return ‘True’ if triggers were blocked, ‘False’ otherwise.

Dependency order is defined by either the ‘add_dependency’ method or providing list specified in the ‘after’ keyword to ‘add_trigger’.

remove_handler(handler)

Supported API . Deregister a handler.

triggerset.remove_handler(handler) => None

The handler should be the return value from a previous call to add_handler. If the given handler is invalid, an exception is raised.

trigger_handlers(name)

Supported API . Return functions registered for trigger with the given name.

triggerset.trigger_handlers(name) => list

If no trigger corresponds to name, an exception is raised.

trigger_names()

Supported API . Return an unordered list of the trigger names (dict keys).

triggerset.trigger_names() => list