Notifications

Knowing some records have been modified in a resource is very useful to integrate a Kinto-Core-based application with other services.

For example, a search service that gets notified everytime something has changed, can continuously update its index.

Kinto-Core leverages Pyramid’s built-in event system and produces the following events:

  • kinto.core.events.ResourceRead: a read operation occured on the resource.

  • kinto.core.events.ResourceChanged: a resource is being changed. This event occurs synchronously within the transaction and within the request/response cycle. Commit is not yet done and rollback is still possible.

    Subscribers of this event are likely to perform database operations, alter the server response, or cancel the transaction (by raising an HTTP exception for example). Do not subscribe to this event for operations that will not be rolled-back automatically.

  • kinto.core.events.AfterResourceChanged: a resource was changed and committed.

    Subscribers of this event can fail, errors are swallowed and logged. The final transaction result (or response) cannot be altered.

    Subscribers of this event are likely to perform irreversible actions that requires data to be committed in database (like sending messages, deleting files on disk, or run asynchronous tasks).

Event subscribers can then pick up those events and act upon them.

from kinto.core.events import AfterResourceChanged


def on_resource_changed(event):
    for change in event.impacted_records:
        start_download(change['new']['url'])

config.add_subscriber(on_resource_changed, AfterResourceChanged)
class kinto.core.events.ResourceRead(payload, read_records, request)

Triggered when a resource is being read.

class kinto.core.events.ResourceChanged(payload, impacted_records, request)

Triggered when a resource is being changed.

class kinto.core.events.AfterResourceChanged(payload, impacted_records, request)

Triggered after a resource was successfully changed.

It can be useful to emit events, especially if you wish to simulate resources (as in e.g. kinto-changes).

kinto.core.events.notify_resource_event(request, parent_id, timestamp, data, action, old=None, resource_name=None, resource_data=None)

Request helper to stack a resource event.

If a similar event (same resource, same action) already occured during the current transaction (e.g. batch) then just extend the impacted records of the previous one.

Parameters:
  • resource_name – The name of the resource on which the event happened (taken from the request if not provided).
  • resource_data (dict) – Information about the resource on which the event is being emitted. Usually contains information about how to find this record in the hierarchy (for instance, bucket_id and collection_id for a record). Taken from the request matchdict if absent.

Transactions

Only one event is sent per transaction, per resource and per action.

In other words, if every requests of a batch requests perform the same action on the same resource, only one event will be sent.

The AfterResourceChanged is sent only if the transaction was comitted successfully.

It is possible to cancel the current transaction by raising an HTTP Exception from a ResourceChanged event. For example:

from kinto.core.events import ResourceChanged
from pyramid import httpexceptions

def check_quota(event):
     max_quota = event.request.registry.settings['max_quota']
     if check_quota(event, max_quota):
        raise httpexceptions.HTTPInsufficientStorage()

config.add_subscriber(check_quota, ResourceChanged)

Filtering

It is possible to filter events based on its action or the name of the resource where it occured.

For example:

from kinto.core.events import ResourceChanged, ACTIONS

config.add_subscriber(on_mushroom_changed, ResourceChanged, for_resources=('mushroom',))
config.add_subscriber(on_record_deleted, ResourceChanged, for_actions=(ACTIONS.DELETE,))

Payload

The kinto.core.events.ResourceChanged and kinto.core.events.AfterResourceChanged events contain a payload attribute with the following information:

  • timestamp: the collection timestamp of the (parent_id, resource_name) pair for which this event was sent
  • action: what happened. ‘create’, ‘update’ or ‘delete’
  • uri: the uri of the impacted resource
  • user_id: the authenticated user id
  • resource_name: the name of the impacted resouce (e.g. ‘article’, ‘bookmark’, bucket’, ‘group’ etc.)
  • <resource_name>_id: id of the impacted record
  • <matchdict value>: every value matched by each URL pattern name (see Pyramid request matchdict)

And provides the list of affected records in the impacted_records attribute. This list contains dictionaries with new and old keys. For creation events, only new is provided. For deletion events, only old is provided. This also allows listeners to react on particular field change or handle diff between versions.

Example, when deleting a collection with two records:

>>> event.impacted_records
[{'old': {'deleted': True, 'last_modified': 1447240896769, 'id': 'a1f4af60-ddf5-4c49-933f-4cfeff18ad07'}},
 {'old': {'deleted': True, 'last_modified': 1447240896770, 'id': '7a6916aa-0ea1-42a7-9741-c24fe13cb70b'}}]

Event listeners

It is possible for an application or a plugin to listen to events and execute some code. Triggered code on events is synchronously called when a request is handled. An event handler can itself trigger other events.

Kinto-Core offers custom listeners that can be activated through configuration, so that every Kinto-Core-based application can benefit from pluggable listeners without using config.add_subscriber() explicitely.

Currently, a simple built-in listener is available, that just delivers the events into a Redis queue, allowing asynchronous event handling:

class kinto_redis.listeners.Listener(client, listname, *args, **kwargs)

A Redis-based event listener that simply pushes the events payloads into the specified Redis list as they happen.

This listener allows actions to be performed asynchronously, using Redis Pub/Sub notifications, or scheduled inspections of the queue.

To activate it, look at the dedicated configuration.

Implementing a custom listener consists on implementing the following interface:

class kinto.core.listeners.ListenerBase(*args, **kwargs)
__call__(event)
Parameters:event – Incoming event