Cookbook¶
This page describes some strategies for customizing the behavior of the Triggers framework, depending on the needs of your application.
Setting the Manager/Storage Type at Runtime¶
Internally, the Triggers framework uses a library called ClassRegistry to manage the registered trigger managers and storage backends. ClassRegistry works by assigning each class a unique key and adding it to a registry (dict-like object).
You can leverage this feature in your application to make the manager and/or
storage type configurable at runtime, by storing the corresponding keys in
application settings (e.g., in a Django settings.py
module).
Here’s an example:
First, we set some sensible defaults:
# my_app/settings.py
TRIGGER_MANAGER_TYPE = 'default'
TRIGGER_STORAGE_TYPE = 'cache'
Tip
The values 'default'
and 'cache'
can be found in the entry point
definitions for TriggerManager
and
CacheStorageBackend
, respectively.
Entry point definitions are set in the library’s setup.py; look for the
entry_points
configuration.
See Registering Your Trigger Manager for more information.
Next, we’ll define a function that will build the trigger manager object from these settings:
# my_app/triggers.py
from typing import Text
from triggers import TriggerManager
from triggers.manager import trigger_managers
from triggers.storages import storage_backends
from my_app.settings import TRIGGER_MANAGER_TYPE, \
TRIGGER_STORAGE_TYPE
def get_trigger_manager(uid):
# type: (Text) -> TriggerManager
"""
Given a session UID, returns a configured trigger manager.
"""
storage = storage_backends.get(TRIGGER_STORAGE_TYPE, uid)
manager = trigger_managers.get(TRIGGER_MANAGER_TYPE, storage)
return manager
Note the use of triggers.manager.trigger_managers
and
triggers.storages.storage_backends
. These are the registries of
trigger managers and storage backends, respectively.
The get()
method retrieves the class corresponding to the identifier
(e.g., TRIGGER_STORAGE_TYPE
— “cache” in this case) and instantiates it
using the remaining arguments (e.g., uid
).
Finally, call get_trigger_manager()
in your application wherever you need a
TriggerManager
instance.
By changing the values of the TRIGGER_MANAGER_TYPE
and/or
TRIGGER_STORAGE_TYPE
settings, you can customize the trigger manager and/or
storage backend that your application uses, without having to rewrite any logic.
Finalizing a Session¶
In many cases, it is useful to schedule trigger tasks to run when everything else is finished.
For example, we may want to have our questionnaire application set a status flag in the database once the questionnaire is 100% complete and all of the other trigger tasks have finished successfully.
To make this work, we will define a new trigger called sessionFinalized
that
fires when all of the trigger tasks in a session have finished running.
We can detect that a trigger task has finished running by waiting for its cascade; that is, we can perform the “is session finalized” check after each trigger fires.
To accomplish this, we must create our own trigger manager and
override its _post_fire()
hook.
We will also take advantage of the trigger manager’s ability to find unresolved tasks, so that we can determine if there are any tasks waiting to run.
The end result looks like this:
class FinalizingTriggerManager(TriggerManager):
TRIGGER_SESSION_FINALIZED = "sessionFinalized"
def _post_fire(self, trigger_name, tasks_scheduled):
# Prevent infinite recursion.
if trigger_name == self.TRIGGER_SESSION_FINALIZED:
return
# A session can only be finalized once.
if self.TRIGGER_SESSION_FINALIZED in self.storage.latest_kwargs:
return
# Check for any unresolved tasks...
for config in self.get_unresolved_tasks():
# ... ignoring any that are waiting for session finalized.
if self.TRIGGER_SESSION_FINALIZED not in config.after:
return
# If we get here, we are ready to finalize the session.
self.fire(self.TRIGGER_SESSION_FINALIZED)
Important
Don’t forget to register your trigger manager!
Namespaced Session UIDs¶
Suppose you have a set of related triggers sessions, and you want to schedule some tasks to run in a “super session” of sorts.
For example, let’s suppose that our questionnaire application has two different questionnaires: “Flora” and “Fauna”. We would like to execute a trigger task after the applicant completes page 3 of the Flora questionnaire and page 6 of the Fauna questionnaire. But, we can’t predict what order these events will occur.
To accomplish this, we can create a “namespaced session UID” for the applicant. When the application is processing responses from the applicant’s questionnaire, it will actually create two trigger managers, each with a separate UID:
from my_app.models import Questionnaire
def start_questionnaire(request):
"""
Django view that is called when the user clicks the "start" button
on a questionnaire.
"""
questionnaire = get_object_or_404(
klass = Questionnaire,
pk = request.POST["questionnaire_id"],
)
# Prepare our regular triggers session for the questionnaire.
trigger_manager = TriggerManager(...)
trigger_manager.update_configuration(...)
# Prepare our "super session", which will maintain state across
# multiple questionnaires.
#
# Note that the UID is tied to the applicant, not a particular
# questionnaire. We also add a prefix, to avoid conflicts with
# regular trigger session UIDs.
super_trigger_manager = TriggerManager(
storage = CacheStorageBackend(
uid = 'applicant:{}'.format(request.session.applicant_id),
),
)
super_trigger_manager.update_configuration({
# This task will run after the applicant completes page 3 in the
# Flora questionnaire, and page 6 in the Fauna questionnaire.
't_compareResponses': {
'after': ['flora_page3', 'fauna_page6'],
'run': CompareResponses.name,
},
})
def responses(request):
"""
Django view that processes a page of response data from
the client.
"""
questionnaire = get_object_or_404(
klass = Questionnaire,
pk = request.POST["questionnaire_id"],
)
responses_form = QuestionnaireResponsesForm(request.POST)
if responses_form.is_valid():
# Regular triggers session for the questionnaire.
trigger_manager = TriggerManager(...)
trigger_manager.fire(...)
# Fire triggers for "super session".
super_trigger_manager = TriggerManager(
storage = CacheStorageBackend(
uid = 'applicant:{}'.format(request.session.applicant_id),
),
)
super_trigger_manager.fire(
# E.g., "fauna_page3", etc.
trigger_name = '{}_page{}'.format(
questionnaire.name,
responses_form.cleaned_data['page_number'],
),
trigger_kwargs = {'responses': responses_form.cleaned_data},
)