How to Configure¶
To configure a trigger manager instance, call its update_configuration
method and provide a dict with the following items:
- Each key is the name of a trigger task. These can be anything you want, but
the convention is to start each name with a
t_
prefix. - Each value is a dict containing that task’s configuration.
Here is an example showing how to add 3 tasks to the trigger manager’s configuration.
trigger_manager.update_configuration({
't_importSubject': {...},
't_importResponses': {...},
't_importDeviceMetadata': {...},
})
Task Configuration¶
There are many directives you can specify to customize the behavior of each trigger task.
The directives are named and structured in such a way that you should be able to “read” a trigger task configuration like an English sentence.
As an example, consider the following trigger task configuration:
trigger_manager.update_configuration({
't_importResponses': {
'after': ['t_importSubject'],
'andEvery': 'pageReceived',
'run': 'app.tasks.ImportResponses',
},
})
You can “read” the configuration for the t_importResponses
task as:
After t_importSubject
fires, and every time pageReceived
fires,
run the app.tasks.ImportResponses
Celery task.
Required Directives¶
The following directives must be provided in each task’s configuration.
after
¶
after
is a list of strings that indicates which triggers must fire in order
for the task to be run.
Tip
Recall from Getting Started that triggers are fired by your application logic, so you get to decide how triggers are named and what events they represent.
As an example, suppose we want a task to process data from the first page of a questionnaire, but we don’t want it to run until the user has completed the questionnaire. We might configure the trigger task like this:
trigger_manager.update_configuration({
't_importSubject': {
'after': ['firstPageReceived', 'questionnaireComplete'],
...
},
})
You can also use task names as triggers. These will fire each time the corresponding task finishes successfully.
Here’s an example of a task that processes data from a single page of
questionnaire responses, but only after the t_importSubject
task has
finished successfully.
trigger_manager.update_configuration({
't_importDeviceMetadata': {
'after': ['pageReceived', 't_importSubject'],
...
},
})
Tip
The order of values in after
do not matter.
For compatibility with serialization formats like JSON, after
is
usually expressed as a list
in Python code, but you can use a set
if
you prefer.
run
¶
run
tells the trigger manager which Celery task to run once the trigger
task’s after
condition is satisfied.
The value should match the name
of a Celery task, exactly the same as if
you were configuring CELERYBEAT_SCHEDULE
.
As an example, to configure a trigger task to run the
my_app.tasks.ImportSubject
task, the configuration might look like this:
from my_app.tasks import ImportSubject
trigger_manager.update_configuration({
't_importSubject': {
...
'run': ImportSubject.name,
},
})
Important
The trigger manager can only execute Celery tasks that extend the
triggers.task.TriggerTask
class.
See Writing Celery Tasks for more information.
Optional Directives¶
The following optional directives allow you to further customize the behavior of your trigger tasks.
andEvery
¶
By default, every trigger task is “one shot”. That is, it will only run once,
even if the triggers in its after
directive are fired multiple times.
If you would like a trigger task to run multiple times, you can add the
andEvery
directive to the trigger configuration.
andEvery
accepts a single trigger. Whenever this trigger fires, the
trigger manager will create a new “instance” of the trigger task.
For example, suppose we want to configure a trigger task to process data from
each page in a questionnaire, but it can only run once the t_importSubject
trigger task has finished successfully.
The configuration might look like this:
trigger_manager.update_configuration({
't_importResponses': {
'after': ['t_importSubject'],
'andEvery': 'pageReceived',
...
},
})
Using the above configuration, a new instance of t_importResponses
will be
created, but they will only run after the t_importSubject task finishes.
unless
¶
unless
is the opposite of after
. It defines a condition that will
prevent the trigger task from running.
Once a task’s unless
condition is satisfied, the trigger manager will not
allow that task to run, even if its after
condition is satisfied later.
Important
This only prevents the trigger manager from scheduling Celery tasks. It will not recall a Celery task that has already been added to a Celery queue, nor will it abort any task that is currently being executed by a Celery worker.
As an example, suppose you wanted to import metadata about the applicant’s browser during a questionnaire, but only if the user is completing the questionnaire in a web browser. If the backend detects that the questionnaire is embedded in a mobile application, then this task should not run.
The configuration might look like this:
trigger_manager.update_configuration({
't_importBrowserMetadata': {
'after': ['t_importSubject', 'pageReceived'],
'unless': ['isEmbeddedApplication'],
...
},
})
If isEmbeddedApplication
fires before t_importSubject
and/or
pageReceived
, then the trigger manager will not allow the
t_importBrowserMetadata
task to run.
Caution
Watch out for race conditions!
withParams
¶
When the trigger manager executes a task, it will provide the kwargs that were
provided when each of that task’s after
triggers were fired (see
Writing Celery Tasks for more information).
But, what if you need to inject your own static kwargs?
This is what the withParams
directive is for.
As an example, suppose you have a generic trigger task that you use to generate a psychometric credit score at the end of a questionnaire, but you have to tell it which model to use.
Using the withParams
directive, you can inject the name of the model like
this:
from my_app.tasks import ComputeScore
trigger_manager.update_configuration({
't_computePsychometricScore': {
...
'run': ComputeScore.name,
'withParams': {
'scoring': {'model': 'Psych 01'},
},
},
})
When the my_app.tasks.ComputeScore
Celery task runs, it will be provided
with the model name 'Psych 01'
so that it knows which model to load.
Important
withParams
must be a dict of dicts, so that it matches the structure of
trigger kwargs (see Writing Celery Tasks for more information).
For example, this configuration is not correct:
trigger_manager.update_configuration({
't_computePsychometricScore': {
...
'withParams': {
'model': 'Psych 01',
},
},
})
using
¶
By default, the trigger manager uses Celery to execute trigger tasks (except during unit tests).
However, if you want to use a different task runner, you can
specify it via the using
directive.
For example, suppose we created a custom task runner that executes tasks via AWS Lambda. To tell the trigger manager to execute a task using the custom task runner, we might use the following configuration:
from my_app.tasks import ComputeScore
from my_app.triggers.runners import AwsLambdaRunner
trigger_manager.update_configuration({
't_computePsychometricScore': {
...
'run': ComputeScore.name,
'using': AwsLambdaRunner.name,
},
})
Tip
To change the default task runner globally, override
triggers.runners.DEFAULT_TASK_RUNNER
.
Custom Directives¶
You can add any additional directives that you want; each will be added to the
corresponding task’s extras
attribute.
These aren’t used for anything by default, but if you write a custom trigger manager, you can take advantage of custom directives to satisfy your application’s requirements.
For an example of how to use custom directives, see the “Finalizing a Session” recipe in the Cookbook