Signals

CKAN provides built-in signal support, powered by blinker.

The same library is used by Flask and anything written in the Flask documentation also applies to CKAN. Probably, the most important point:

Flask comes with a couple of signals and other extensions might provide more. Also keep in mind that signals are intended to notify subscribers and should not encourage subscribers to modify data. You will notice that there are signals that appear to do the same thing like some of the builtin decorators do (eg: request_started is very similar to before_request()). However, there are differences in how they work. The core before_request() handler, for example, is executed in a specific order and is able to abort the request early by returning a response. In contrast all signal handlers are executed in undefined order and do not modify any data.

ckan.lib.signals provides two namespaces for signals: ckan and ckanext. All core signals reside in ckan, while signals from extensions (datastore, datapusher, third-party extensions) are registered under ckanext. This is a recommended pattern and nothing prevents developers from creating and using their own namespaces.

Signal subscribers MUST always be defined as callable accepting one mandatory argument sender and arbitary number of keyword arguments:

def subscriber(sender, **kwargs):
    ...

CKAN core doesn’t make any guarantees as for the concrete named arguments that will be passed to subscriber. For particular CKAN version one can use signlal-listing below as a reference, but in future versions signature may change. In additon, any event can be fired by a third-party plugin, so it is always safer to check whether a particular argument is available inisde the provided kwargs.

Even though it is possible to register subscribers using decorators:

@p.toolkit.signals.before_action.connect
def action_subscriber(sender, **kwargs):
    pass

the recommended approach is to use the ckan.plugins.interfaces.ISignal interface, in order to give CKAN more control over the subscriptions available depending on the enabled plugins:

class ExampleISignalPlugin(p.SingletonPlugin):
    p.implements(p.ISignal)

    def get_signal_subscriptions(self):
        return {
            p.toolkit.signals.before_action: [
                # when subscribing to every signal of type
                action_subscriber,

                # when subscribing to signals from particular sender
                {u'receiver': action_subscriber, u'sender': 'sender_name'}
            ]
        }

Warning

Arguments passed to subscribers should never be modified. Use subscribers only to trigger side effects and not to change existing CKAN behavior. If one needs to alter CKAN behavior use ckan.plugins.interfaces instead.

There are a number of built-in signals in CKAN (check the list at the bottom of the page). All of them are created inside one of the available namespaces: ckan and ckanext. For simplicity sake, all built in signals have aliases inside ckan.lib.signals (or ckan.plugins.toolkit.signals, or ckantoolkit.signals), but you can always get signals directly from corresponding the namespace (you shouldn’t use this directly unless you are familiar with the blinker library):

from ckan.lib.signals import (
    ckan as ckan_namespace,
    register_blueprint, request_started
)
assert register_blueprint is ckan_namespace.signal('register_blueprint')
assert request_started is ckan_namespace.signal('request_started')

This information may be quite handy, if you want to define custom signals inside your extension. Just use ckanext namespace and call its method signal in order to create a new signal (or get an existing one). In order to avoid name collisions and unexpected behavior, always use your plugin’s name as prefix for the signal.:

# ckanext-custom/ckanext/custom/signals.py
import ckan.plugins.toolkit as tk

# create signal and use it somewhere inside your extension
custom_something_happened = tk.signals.ckanext.signal('custom_something_happened)

# after this, you can notify subscribers using following code:
custom_signal_happened.send(SENDER, ARG1=VALUE1, ARG2=VALUE2, ...)

From now on, everyone who is using your extension can subscribe to your signal from another extension:

# ckanext-ext/ckanext/ext/plugin.py
import ckan.plugins as p
from ckanext.custom.signals import custom_something_happened
from ckanext.ext import listeners # here you'll define listeners

class ExtPlugin(p.SingletonPlugin):
    p.implements(p.ISignal)

    def get_signal_subscriptions(self):
        return {
            custom_something_happened: [
                listeners.custom_listener
            ]
        }

There is a small problem in snippet above. If ckanext-custom is not installed, you’ll get ImportError. This is perfectly fine if you are sure that you are using ckanext-custom, but may be a problem for some general-use plugin. To avoid this, import signals from the ckanext namespace instead:

# ckanext-ext/ckanext/ext/plugin.py
import ckan.plugins as p
from ckanext.ext import listeners

class ExtPlugin(p.SingletonPlugin):
    p.implements(p.ISignal)

    def get_signal_subscriptions(self):
        custom_something_happened = p.toolkit.signals.ckanext.signal(
            'custom_something_happened'
        )

        return {
            custom_something_happened: [
                listeners.custom_listener
            ]
        }

All signals are singletons inside their namespace. If ckanext-custom is installed, you’ll get its existing signal, otherwise you’ll create a new signal that is never sent. So your subscription will work only when ckanext-custom is available and do nothing otherwise.

ckan.lib.signals contains a few core signals for plugins to subscribe:

ckan.lib.signals.request_started (app)

This signal is sent when the request context is set up, before any request processing happens.

ckan.lib.signals.request_finished (app, response)

This signal is sent right before the response is sent to the client.

ckan.lib.signals.register_blueprint (blueprint_type, blueprint)

This signal is sent when a blueprint for dataset/resource/group/organization is going to be registered inside the application.

ckan.lib.signals.resource_download (resource_id)

This signal is sent just before a file from an uploaded resource is sent to the user.

ckan.lib.signals.user_logged_in (app, user)

Sent when a user is logged in.

ckan.lib.signals.user_logged_out (app, user)

Sent when a user is logged out

ckan.lib.signals.failed_login (username)

This signal is sent after failed login attempt.

ckan.lib.signals.user_created (username, user)

This signal is sent when new user created.

ckan.lib.signals.request_password_reset (username, user)

This signal is sent just after mail with password reset link sent to user.

ckan.lib.signals.perform_password_reset (username, user)

This signal is sent when user submitted password reset form providing new password.

ckan.lib.signals.action_succeeded (action, context, data_dict, result)

This signal is sent when an action finished without an exception.

ckan.lib.signals.datastore_upsert (resource_id, records)

This signal is sent after datasetore records inserted/updated via datastore_upsert.

ckan.lib.signals.datastore_delete (resource_id, result, data_dict)

This signal is sent after successful call to datastore_delete.