Tasks

ViUR is shipped with a sophisticated interface for tasks, providing a standardized way of having a function called in a regular interval, providing an interface for tasks that can be called by the user on demand, and deferring functionality from the current request.

Time based

A common problem on the Google AppEngine is having certain functionality executed in a regular interval. ViUR simplifies this down to a decorator. Just wrap a function with @PeriodicTask and ViUR will call it in a regular interval. The decorator takes one argument – the interval in minutes. ViUR will not call your function faster than once in each interval-minutes.

import logging
from viur.core.tasks import PeriodicTask

@PeriodicTask(24 * 60)
def mytask():  # Will be called roughly every 24 hours
    logging.debug("Your deferred task was just called :)")

Note

This is the lower bound. There is no guarantee that it will be called each ‘interval’ minutes. The upper bound is defined in cron.yaml and currently defaults to each 4 hours.

Note

Time-based tasks can be a bound or unbound function. If a function is bound (defined in a class) it will be called once for each module derived from this class. If there is no instance of its class, it won’t be called. If it’s unbound (defined at module-level) it will be called, regardless if any Class in its module is used or not (but the module itself needs to be imported).

Warning

There’s currently only a main loop which calls all function scheduled for execution. It has a time-limit of 10 minutes. If your task takes more than a few minutes to execute, you should defer that code. Otherwise your tasks (all tasks in total) might exceed these 10 minutes, which causes the request to be aborted despite not all tasks had been called yet.

Deferred

Sometimes its necessary to delay the execution of some specific code, so it won’t slow down the response of the current request. ViUR provides the @CallDeferred Decorator for such cases. A function decorated this way will never execute in the context of the current request. All calls to such a function are caught, its parameters serialized, and a task is created to call this function later. These calls are executed in a deferred task which can run up to 10 minutes. As these tasks run deferred, they run outside of the current context where they had been created. ViUR however will preserve the following two values:

  • The currently logged in user (if any). If the task was created in the context of a known user, calls to current.user.get() will return the same values as it would have returned when the task had been created.

  • The language used for the request. Within the deferred task any calls to i18n functions provided by ViUR will yield results in the language of the original request.

Note

A deferred function cannot return a value! The return-value for the code calling such a function will always be None, and any return-value generated by the function (when its actually called) will be discarded.

Note

There is currently no official cloud task emulator for local development. As a workaround, deferred tasks are called by default at the end of the request where the deferred method was called.
However, there are some third party emulator like the Google Cloud Tasks Emulator from Potato, which can be used by setting the environment variable TASKS_EMULATOR to its host.

On Demand

The third use-case for tasks is on demand: A task that’s run infrequently by the user. One example is our rebuild searchindex task: If changes are made to a data-model (ie. include the contents of a Bone in the fulltext search), and there is already data in the datastore created by the old model, its necessary to update the searchindex, as it doesn’t contain the contents of that bone yet. It would be a waste of resources if we rebuild each index frequently. So this task is only called on demand. If the developer has made changes to the data model, he calls that task once for each affected kind. Creating such a task is also easy, it’s a Class derived from core.tasks.CallableTaskBase and decorated with core.tasks.CallableTask(). The derived subclass must overwrite the following properties and functions.

Name

Type

Description

key

Property (String)

An unique identifier for this task.

name

Property (String)

A short human-readable description

descr

Property (String)

A longer explanation

canCall

Function

Must return True if the current user (if any) is allowed to execute that task. Return False otherwise.

dataSkel

Function or Skeleton-class

If your tasks need additional input (i.e.: which searchindex?) from the user, query him by returning an skeleton. Return None if you don’t need any information.

execute

Function

Does the actual work. If you returned a skeleton in dataSkel, the values of that skeleton are passed as keyword arguments.

On instance startup

The last hook you can use is the @StartupTask decorator. This way you can have code being executed whenever a new instance starts up without slowing down the instance startup itself (The code will be called deferred shortly after an instance gets ready). Useful to ensure some database initialization or the like.

Warning

There’s absolutely no guarantee that the function will be called on the instance that started up. It can be called any of the currently running instances. So it’s possible that such a function is called never, once or multiple times on the same instance. Do not put any code here required to correctly setup your instances.