pyee

pyee is a rough port of node.js’s EventEmitter. Unlike its namesake, it includes a number of subclasses useful for implementing async and threaded programming in python, such as async/await as seen in python 3.5+.

Install:

You can install this project into your environment of choice using pip:

pip install pyee

API Docs:

pyee supplies a BaseEventEmitter class that is similar to the EventEmitter class from Node.js. In addition, it supplies the subclasses AsyncIOEventEmitter, TwistedEventEmitter and ExecutorEventEmitter for supporting async and threaded execution with asyncio, twisted, and concurrent.futures Executors respectively, as supported by the environment.

Example

In [1]: from pyee import BaseEventEmitter

In [2]: ee = BaseEventEmitter()

In [3]: @ee.on('event')
   ...: def event_handler():
   ...:     print('BANG BANG')
   ...:

In [4]: ee.emit('event')
BANG BANG

In [5]:
class pyee.BaseEventEmitter[source]

The base event emitter class. All other event emitters inherit from this class.

Most events are registered with an emitter via the on and once methods, and fired with the emit method. However, pyee event emitters have two special events:

  • new_listener: Fires whenever a new listener is created. Listeners for this event do not fire upon their own creation.

  • error: When emitted raises an Exception by default, behavior can be overriden by attaching callback to the event.

    For example:

    @ee.on('error')
    def on_error(message):
        logging.err(message)
    
    ee.emit('error', Exception('something blew up'))
    

All callbacks are handled in a synchronous, blocking manner. As in node.js, raised exceptions are not automatically handled for you—you must catch your own exceptions, and treat them accordingly.

emit(event, *args, **kwargs)[source]

Emit event, passing *args and **kwargs to each attached function. Returns True if any functions are attached to event; otherwise returns False.

Example:

ee.emit('data', '00101001')

Assuming data is an attached function, this will call data('00101001')'.

listeners(event)[source]

Returns a list of all listeners registered to the event.

on(event, f=None)[source]

Registers the function f to the event name event.

If f isn’t provided, this method returns a function that takes f as a callback; in other words, you can use this method as a decorator, like so:

@ee.on('data')
def data_handler(data):
    print(data)

In both the decorated and undecorated forms, the event handler is returned. The upshot of this is that you can call decorated handlers directly, as well as use them in remove_listener calls.

once(event, f=None)[source]

The same as ee.on, except that the listener is automatically removed after being called.

remove_all_listeners(event=None)[source]

Remove all listeners attached to event. If event is None, remove all listeners on all events.

remove_listener(event, f)[source]

Removes the function f from event.

class pyee.AsyncIOEventEmitter(loop=None)[source]

An event emitter class which can run asyncio coroutines in addition to synchronous blocking functions. For example:

@ee.on('event')
async def async_handler(*args, **kwargs):
    await returns_a_future()

On emit, the event emitter will automatically schedule the coroutine using asyncio.ensure_future and the configured event loop (defaults to asyncio.get_event_loop()).

Unlike the case with the BaseEventEmitter, all exceptions raised by event handlers are automatically emitted on the error event. This is important for asyncio coroutines specifically but is also handled for synchronous functions for consistency.

When loop is specified, the supplied event loop will be used when scheduling work with ensure_future. Otherwise, the default asyncio event loop is used.

For asyncio coroutine event handlers, calling emit is non-blocking. In other words, you do not have to await any results from emit, and the coroutine is scheduled in a fire-and-forget fashion.

class pyee.TwistedEventEmitter[source]

An event emitter class which can run twisted coroutines and handle returned Deferreds, in addition to synchronous blocking functions. For example:

@ee.on('event')
@inlineCallbacks
def async_handler(*args, **kwargs):
    yield returns_a_deferred()

or:

@ee.on('event')
async def async_handler(*args, **kwargs):
    await returns_a_deferred()

When async handlers fail, Failures are first emitted on the failure event. If there are no failure handlers, the Failure’s associated exception is then emitted on the error event. If there are no error handlers, the exception is raised. For consistency, when handlers raise errors synchronously, they’re captured, wrapped in a Failure and treated as an async failure. This is unlike the behavior of BaseEventEmitter, which have no special error handling.

For twisted coroutine event handlers, calling emit is non-blocking. In other words, you do not have to await any results from emit, and the coroutine is scheduled in a fire-and-forget fashion.

Similar behavior occurs for “sync” functions which return Deferreds.

class pyee.ExecutorEventEmitter(executor=None)[source]

An event emitter class which runs handlers in a concurrent.futures executor. If using python 2, this will fall back to trying to use the futures backported library (caveats there apply).

By default, this class creates a default ThreadPoolExecutor, but a custom executor may also be passed in explicitly to, for instance, use a ProcessPoolExecutor instead.

This class runs all emitted events on the configured executor. Errors captured by the resulting Future are automatically emitted on the error event. This is unlike the BaseEventEmitter, which have no error handling.

The underlying executor may be shut down by calling the shutdown method. Alternately you can treat the event emitter as a context manager:

with ExecutorEventEmitter() as ee:
    # Underlying executor open

    @ee.on('data')
    def handler(data):
        print(data)

    ee.emit('event')

# Underlying executor closed

Since the function call is scheduled on an executor, emit is always non-blocking.

No effort is made to ensure thread safety, beyond using an executor.

shutdown(wait=True)[source]

Call shutdown on the internal executor.

class pyee.TrioEventEmitter(nursery=None, manager=None)[source]

An event emitter class which can run trio tasks in a trio nursery.

By default, this class will lazily create both a nursery manager (the object returned from trio.open_nursery() and a nursery (the object yielded by using the nursery manager as an async context manager). It is also possible to supply an existing nursery manager via the manager argument, or an existing nursery via the nursery argument.

Instances of TrioEventEmitter are themselves async context managers, so that they may manage the lifecycle of the underlying trio nursery. For example, typical usage of this library may look something like this:

async with TrioEventEmitter() as ee:
    # Underlying nursery is instantiated and ready to go
    @ee.on('data')
    async def handler(data):
        print(data)

    ee.emit('event')

# Underlying nursery and manager have been cleaned up

Unlike the case with the BaseEventEmitter, all exceptions raised by event handlers are automatically emitted on the error event. This is important for trio coroutines specifically but is also handled for synchronous functions for consistency.

For trio coroutine event handlers, calling emit is non-blocking. In other words, you should not attempt to await emit; the coroutine is scheduled in a fire-and-forget fashion.

context()[source]

Returns an async contextmanager which manages the underlying nursery to the EventEmitter. The TrioEventEmitter’s async context management methods are implemented using this function, but it may also be used directly for clarity.

pyee.EventEmitter

alias of pyee._compat.CompatEventEmitter

class pyee._compat.CompatEventEmitter(scheduler=<function ensure_future>, loop=None)[source]

An EventEmitter exposed for compatibility with prior versions of pyee. This functionality is deprecated; you should instead use either AsyncIOEventEmitter, TwistedEventEmitter, ExecutorEventEmitter, TrioEventEmitter or BaseEventEmitter.

This class is similar to the AsyncIOEventEmitter class, but also allows for overriding the scheduler function (ensure_future by default as in ASyncIOEventEmitter) and does duck typing checks to handle Deferreds. In other words, by setting scheduler to twisted.internet.defer.ensureDeferred this will support twisted use cases for coroutines.

When calling synchronous handlers, raised exceptions are ignored - as with the BaseEventEmitter, you must capture and handle your own exceptions. However, for coroutine functions, exceptions are handled by emitting them on the error event. Note that when using with twisted, the error event will emit Failures, not Exceptions.

This class will also successfully import in python 2, but without coroutine support.

exception pyee.PyeeException[source]

An exception internal to pyee.

pyee.uplift.uplift(cls, underlying, error_handling='new', proxy_new_listener='forward', *args, **kwargs)[source]

A helper to create instances of an event emitter cls that inherits event behavior from an underlying event emitter instance.

This is mostly helpful if you have a simple underlying event emitter that you don’t have direct control over, but you want to use that event emitter in a new context - for example, you may want to uplift a BaseEventEmitter supplied by a third party library into an AsyncIOEventEmitter so that you may register async event handlers in your asyncio app but still be able to receive events from the underlying event emitter and call the underlying event emitter’s existing handlers. This trick will also often work for a deprecated EventEmitter instance.

When called, uplift instantiates a new instance of cls, passing along any unrecognized arguments, and overwrites the emit method on the underlying event emitter to also emit events on the new event emitter and vice versa. In both cases, they return whether the emit method was handled by either emitter. Execution order prefers the event emitter on which emit was called.

uplift also adds an unwrap method to both instances, either of which will unwrap both emit methods when called.

The error_handling flag can be configured to control what happens to unhandled errors:

  • ‘new’: Error handling for the new event emitter is always used and the underlying library’s non-event-based error handling is inert.

  • ‘underlying’: Error handling on the underlying event emitter is always used and the new event emitter can not implement non-event-based error handling.

  • ‘neither’: Error handling for the new event emitter is used if the handler was registered on the new event emitter, and vice versa.

Tuning this option can be useful depending on how the underlying event emitter does error handling. The default is ‘new’.

The proxy_new_listener option can be configured to control how new_listener events are treated:

  • ‘forward’: new_listener events are propagated from the underlying

  • ‘both’: new_listener events are propagated as with other events.

  • ‘neither’: new_listener events are only fired on their respective event emitters. event emitter to the new event emitter but not vice versa.

  • ‘backward’: new_listener events are propagated from the new event emitter to the underlying event emitter, but not vice versa.

Tuning this option can be useful depending on how the new_listener event is used by the underlying event emitter, if at all. The default is ‘forward’, since underlying may not know how to handle certain handlers, such as asyncio coroutines.

Each event emitter tracks its own internal table of handlers. remove_listener, remove_all_listeners and listeners all work independently. This means you will have to remember which event emitter an event handler was added to!

Note that both the new event emitter returned by cls and the underlying event emitter should inherit from BaseEventEmitter, or at least implement the interface for the undocumented _call_handlers and _emit_handle_potential_error methods.