More on Task creation

importing tasks

The doit loader will look at all objects in the namespace of the dodo. It will look for functions staring with task_ and objects with create_doit_tasks. So it is also possible to load task definitions from other modules just by importing them into your dodo file.

# import task_ functions
from get_var import task_echo

# import tasks with create_doit_tasks callable
from custom_task_def import sample


def task_hello():
    return {'actions': ['echo hello']}

$ doit list
echo
hello
sample

Note

Importing tasks from different modules is useful if you want to split your task definitions in different modules.

The best way to create re-usable tasks that can be used in several projects is to call functions that return task dict’s. For example take a look at a reusable pyflakes task generator. Check the project doit-py for more examples.

delayed task creation

doit execution model is divided in two phases:

  • task-loading : search for task-creator functions (that starts with string task_) and create task metadata

  • task-execution : check which tasks are out-of-date and execute them

Normally task-loading is completed before the task-execution starts. doit allows some task metadata to be modified during task-execution with calc_deps and on uptodate, but those are restricted to modifying already created tasks…

Sometimes it is not possible to know all tasks that should be created before some tasks are executed. For these cases doit supports delayed task creation, that means task-execution starts before task-loading is completed.

When task-creator function is decorated with doit.create_after, its evaluation to create the tasks will be delayed to happen after the execution of the specified task in the executed param.

import glob

from doit import create_after


@create_after(executed='early', target_regex='.*\.out')
def task_build():
    for inf in glob.glob('*.in'):
        yield {
            'name': inf,
            'actions': ['cp %(dependencies)s %(targets)s'],
            'file_dep': [inf],
            'targets': [inf[:-3] + '.out'],
            'clean': True,
        }

def task_early():
    """a task that create some files..."""
    inter_files = ('a.in', 'b.in', 'c.in')
    return {
        'actions': ['touch %(targets)s'],
        'targets': inter_files,
        'clean': True,
    }

Note

To be able to specify targets created by delayed task loaders to doit run, it is possible to also specify a regular expression (regex) for every delayed task loader. If specified, this regex should match any target name possibly generated by this delayed task generator. It can be specified via the additional task-generator argument target_regex. In the above example, the regex .*\.out matches every target name ending with .out.

It is possible to match every possible target name by specifying .*. Alternatively, one can use the command line option –auto-delayed-regex to doit run; see here for more information.

Parameter: creates

In case the task created by a DelayedTask has a different basename than then creator function, or creates several tasks with different basenames, you should pass the parameter creates.

Since doit will only execute the body of the task-creator function on demand, the tasks names must be explicitly specified… Example:

import sys

from doit import create_after

def say_hello(your_name):
    sys.stderr.write("Hello from {}!\n".format(your_name))

def task_a():
    return {
        "actions": [ (say_hello, ["a"]) ]
    }

@create_after("a", creates=['b'])
def task_another_task():
    return {
        "basename": "b",
        "actions": [ (say_hello, ["b"]) ],
    }

Warning

doit normally automatically sets task_dep between tasks by checking the relation of file_dep and targets. Due to performance reasons, these task_dep relations are NOT computed for delayed-task’s targets. This problem can avoided by ordering the creation of delayed-tasks with the expected order of execution.

custom task definition

Apart from collect functions that start with the name task_. The doit loader will also execute the create_doit_tasks callable from any object that contains this attribute.

def make_task(func):
    """make decorated function a task-creator"""
    func.create_doit_tasks = func
    return func

@make_task
def sample():
    return {
        'verbosity': 2,
        'actions': ['echo hi'],
        }

The project letsdoit has some real-world implementations.

For simple examples to help you create your own check this blog post.