Skip to content

Listening to Events

Registering and dispatching events accomplishes nothing if no one is listening. A listener ties an event to a handler (a callback) that runs when the event is dispatched.

The best place to register your listeners is your package provider's register() method, which conveniently exposes the dispatcher as self.events.

Tip

The listener methods listen(), listener(), handle(), handler() and call() are all aliases of one another. Use whichever verbiage reads best to you. (Note that subscribe() is not an alias, it registers a bulk subscription instead.)


Listening to Class Based Events

A class based event carries its own listener methods, so you register a handler right off the event class:

from acme.wiki.events.post import Created as OnPostCreated

@uvicore.provider()
class Wiki(Provider):
    def register(self) -> None:

        # Use a callback.  listen / listener / handle / handler / call are aliases.
        OnPostCreated.listen(self.notify_users)

        # Or hand it a string path to a handler class, Uvicore will import and
        # instantiate it for you.
        OnPostCreated.listen('acme.wiki.handlers.NotifyUsers')

See Handling Events for what self.notify_users and the NotifyUsers class actually look like.


Listening to String Based Events

From within your provider you already have self.events available:

@uvicore.provider()
class Wiki(Provider):
    def register(self) -> None:

        # A local method (function) as the callback
        self.events.listen('acme.wiki.post.Created', self.notify_users)

        # A handler class (string path) as the callback
        self.events.listen('acme.wiki.post.Created', 'acme.wiki.handlers.NotifyUsers')

From anywhere else in your code, use the uvicore.events global:

from uvicore import events
events.listen('acme.wiki.post.Created', my_handler)

Info

When you pass a string path like acme.wiki.handlers.NotifyUsers, the event system imports it, instantiates the class, and calls the instance during dispatch. That means your handler class needs a __call__() method, see Class Handlers.


Listening with the Decorator

listen() (and all its aliases) doubles as a decorator when you don't pass a handler. This is the cleanest way to wire up a standalone function, and it works with a single event or a list:

import uvicore

@uvicore.events.handle([
    'uvicore.console.events.command.Startup',
    'uvicore.console.events.command.PytestStartup',
    'uvicore.http.events.server.Startup',
])
async def on_startup(event):
    # Spin up a shared resource once the app starts
    ...

This decorator form is exactly how the framework's own packages (like the HTTP client) bootstrap shared resources on startup and tear them down on shutdown.


Setting a Priority

Every listener method accepts a keyword priority (default 50). Listeners run sorted by priority ascending, so lower numbers run first:

# Bootstrap the console late, after most other services have booted
AppEvents.Booted.listen(bootstrap.Console, priority=90)

Listening to Multiple Events

Pass a list to register one handler against several events at once:

@uvicore.provider()
class Wiki(Provider):
    def register(self) -> None:

        self.events.listen([
            'acme.wiki.post.Created',
            'acme.wiki.post.Deleted',
        ], self.notify_users)

Listening to Wildcard Events

Use a * to listen to a whole family of events. Wildcards are matched as a regular expression search against each dispatched event name, so they work at the end or in the middle of a name:

@uvicore.provider()
class Wiki(Provider):
    def register(self) -> None:

        # Every foundation event
        self.events.listen('uvicore.foundation.events.*', self.notify_users)

        # The * also works in the middle of a name
        self.events.listen('acme.wiki.models.*.Deleted', self.log_deletions)

Registering a Subscriber

A subscription is an all-in-one class that both lists the events to watch and holds the handlers for each. Notice you do not name the event here, the subscription declares its own interests internally. See Handling Events for the class itself.

# From your service provider's register() method
self.events.subscribe('acme.wiki.listeners.HttpEventSubscription')