Package: evproc
EvProc is a framework for building complex event processors.  There
are many similar frameworks available, so why EvProc?  EvProc provides
a few advantages.  First, a@want()decorator is available to
pre-filter events, as opposed to registering an event handler on
specific events.  (Filtering works primarily with an event name,
allowing flexibility, but it is also possible to register arbitrary
filter functions, which can evaluate an event to determine whether to
call the event handler.)  A second advantage is the ability to
register event handlers using the “entrypoint” support ofsetuptools, allowing extensibility.  Finally, EvProc provides
limited inter-handler communication, using theprocproperty ofEventinstances, and backs this up with the ability to specify
ordering among event handlers using requirements.Defining EventsAneventin EvProc is an instance of a subclass ofevproc.Event.
TheEventclass is an abstract class; subclasses must implement anameproperty, the contents of which will be a unique string
naming the event.  The constructor takes a single optional argument, actxtargument, which may be used to pass a context in to event
handlers.  The constructor may, of course, be extended as required to
provide any necessary information about the event, such as a specific
resource that the event occurred on.TheEventclass also provides a specialprocproperty.  This
property may be used by event handlers to store data for use by other
event handlers, by setting attributes.  To minimize the possibility of
two unrelated handlers attempting to manipulate the same attribute,
theprocproperty isnamespaced; that is, attributes onprocmay not be set, but attributes on those attributes may.As an example of the use of theprocproperty, consider two event
handlers that wish to communicate with each other.  Assume that these
handlers interact to perform testing; perhaps one handler selects test
parameters, and the second handler actually performs the test (e.g.,
there may be multiple tests to perform).  The first handler could set,
say,proc.tests.argsandproc.tests.kwargs, and the second
handler would then retrieve the values it needed.Event HandlersAnevent handleris simply a function that will be called with two
arguments; the first argument will be an instance ofevproc.Processor(described below), and the second will be an
instance ofevproc.Event(described above).  The event handler may
perform any code necessary to process the event.  Each event handler
must have a unique name; by default, the name will be the same as the
function name, but this may be overridden when registering the event
handler.  Event handlers may also be loaded fromsetuptoolsentrypoints; in this case, the name used is the entrypoint name.There are three optional decorators that may be used on an event
handler function.  The first is the@evproc.want()decorator,
which may be called with one or more event names; the handler function
will only be called if the event being handled matches one of these
event names.  The decorator may also be passed one or morefilter
functions; in this case, the event handler will only be called if all
of the filter functions returnTrue.  If event names are also
passed, then the event must also match one of those names.  The@want()decorator may be used multiple times to specify other sets
of filters; the event need only matchonefilter specified by@want().  For instance, consider this example:@evproc.want('ev1', 'ev2')
@evproc.want('ev3', lambda ev: ev.resource.name == 'resource')
def handler(proc, ev):
    ...This handler will be called for all events with the names “ev1” and
“ev2”, but will only be called for the event “ev3” ifev.resource.namecontains the value"resource".Event handlers are able to interact with each other, as mentioned
above.  To do this, it is necessary to enforce certain ordering
guarantees on the event handler.  This is controlled by the@evproc.requires()and@evproc.required_by()decorators.
These decorators take the names of one or more event handlers (names
are set at registration time, as mentioned above).  The@requires()decorator is used to indicate that the specified
functions must runbeforethe decorated function, while the@required_by()decorator is used to indicate that the specified
function require thedecoratedfunction to be run first.  In both
cases, the@want()decorators of the functions must be compatible.The@requires()and@required_by()decorators are used to
define adependency graph, which is then topologically sorted to
ensure that the handler functions are called in the correct order.  As
an example, consider the split test functions mentioned above.  We
could declare the functions like so:def test_prepare(proc, ev):
    ...
    ev.tests.args = args
    ev.tests.kwargs = kwargs

@evproc.requires('test_prepare')
def test_run(proc, ev):
    args = ev.tests.args
    kwargs = ev.tests.kwargs
    intermediate = getattr(ev.tests, 'auxiliary', [])
    ...

@evproc.required_by('test_run')
@evproc.requires('test_prepare')
def test_auxiliary(proc, ev):
    ...
    ev.tests.auxiliary = resultsIn this example, thetest_prepare()handler function would be
called first, followed by thetest_auxiliary()handler function,
and finally thetest_run()handler function would be called.An event handler may optionally return a list of events, which will
be processed in order.The Event ProcessorTheevproc.Processorclass is responsible for processing events.
To use EvProc, instantiate aProcessorinstance and use itsregister()orload_from()methods to declare event handler
functions.  Then, simply passEventinstances to theprocess()method to invoke the event processors in the correct order.TheProcessor.register()method may be used to register individual
event handler functions.  By default, the function’s declared name
(func.__name__) is used as the handler name, but this may be
overridden by passing the optionalnameparameter toregister().To load event handlers from asetuptoolsentrypoint, use theProcessor.load_from()method.  This method takes, as its sole
argument, the entrypoint group name; as an example, if one installed
application has asetup.pycontaining:entry_points={
    'app.handlers': [
        'test_prepare = app:test_prepare',
        'test_run = app.test_run',
    ],
}And if a second installed application has the following in itssetup.py:entry_points={
    'app.handlers': [
        'test_auxiliary = otherapp:test_auxiliary',
    ],
}Then all three handler functions could be loaded into theProcessorinstanceprocwith the following call:proc.load_from('app.handlers')TheProcessor.process()method may be called as many times as
necessary.  In fact, most event-driven applications consist of a loop
which constructsEventinstances, then passes them to theProcessor.process()method.  A full application could look
something like the following:def main():
    proc = evproc.Processor()
    proc.load_from('app.handlers')

    while True:
        # Construct event objects
        ...
        ev = AppEvent(...)

        # Process the event
        proc.process(ev)Processor.process()returnsNoneunless an event processor
raises aevproc.StopProcessingexception initialized with aretval, in which case it returns the exception’sretval.Stop ProcessingIt may be necessary for one event processor to stop all event
processing.  This could, for instance, be used by a processor that
performs an authorization check if the event fails that check.  To
allow this, an event processor may raise theevproc.StopProcessingexception.  When an event processor raises aStopProcessingexception, no additional event processors will be called for that
event.  If theStopProcessingexception is raised without aretval, yet-unprocessed events returned by prior event processors
will still be processed.  If theStopProcessingexception is
raised with aretval(even ifNone),Processor.process()will immediately return the exception’sretval, and yet-unprocessed
events returned by prior event processors will not be processed.ConclusionEvProc provides an easy to extend event processing framework, capable
of not only calling event handler functions, but of ensuring certain
ordering constraints and limited inter-handler communication.  The
ability to usesetuptoolsentrypoints allows new event handlers to
be inserted into the event processing loop easily without having to
modify the original application, and the ordering constraints can
allow such inserted event handlers to interact with the existing ones
just as easily.