Extending doit¶
doit is built to be extended and this can be done in several levels. So far we have seen:
- User’s can create new ways to define when a task is up-to-date using the uptodate task parameter (more)
- You can customize how tasks are executed by creating new Action types (more)
- Tasks can be created in different styles by creating custom task creators (more)
- The output can be configured by creating custom reports (more)
Apart from those, doit also provides a plugin system and expose it’s internal API so you can create new applications on top of doit.
task loader customization¶
The task loader controls the source/creation of tasks. Normally doit tasks are defined in a dodo.py file. This file is loaded, and the list of tasks is created from the dict containing task meta-data from the task-creator functions.
Subclass TaskLoader to create a custom loader:
-
class
doit.cmd_base.
TaskLoader
[source]¶ task-loader interface responsible of creating Task objects
Subclasses must implement the method load_tasks
Variables: cmd_options – (list of dict) see cmdparse.CmdOption for dict format -
load_tasks
(cmd, opt_values, pos_args)[source]¶ load tasks and DOIT_CONFIG
Returns: (tuple) list of Task, dict with DOIT_CONFIG options
Parameters: - cmd – (doit.cmd_base.Command) current command being executed
- opt_values – (dict) with values for cmd_options
- pos_args – (list str) positional arguments from command line
-
The main program is implemented in the DoitMain. It’s constructor takes an instance of the task loader to be used.
Example: pre-defined task¶
In the full example below a application is created where the only task available is defined using a dict (so no dodo.py will be used).
#! /usr/bin/env python3
import sys
from doit.task import dict_to_task
from doit.cmd_base import TaskLoader
from doit.doit_cmd import DoitMain
my_builtin_task = {
'name': 'sample_task',
'actions': ['echo hello from built in'],
'doc': 'sample doc',
}
class MyLoader(TaskLoader):
@staticmethod
def load_tasks(cmd, opt_values, pos_args):
task_list = [dict_to_task(my_builtin_task)]
config = {'verbosity': 2}
return task_list, config
if __name__ == "__main__":
sys.exit(DoitMain(MyLoader()).run(sys.argv[1:]))
Example: load tasks from a module¶
The ModuleTaskLoader can be used to load tasks from a specified module, where this module specifies tasks in the same way as in dodo.py. ModuleTaskLoader is included in doit source.
#! /usr/bin/env python3
import sys
from doit.cmd_base import ModuleTaskLoader
from doit.doit_cmd import DoitMain
if __name__ == "__main__":
import my_module_with_tasks
sys.exit(DoitMain(ModuleTaskLoader(my_module_with_tasks)).run(sys.argv[1:]))
ModuleTaskLoader can take also take a dict where its items are functions or methods of an object.
command customization¶
In doit a command usually perform some kind of operations on tasks. run to execute tasks, list to display available tasks, etc.
Most of the time you should really be creating tasks but when developing a custom application on top of doit it may make sense to provide some extra commands…
To create a new command, subclass doit.cmd_base.Command set some class variables and implement the execute method.
-
class
doit.cmd_base.
Command
(config=None, bin_name='doit', **kwargs)[source]¶ third-party should subclass this for commands that do no use tasks
Variables: - name – (str) name of sub-cmd to be use from cmdline
- doc_purpose – (str) single line cmd description
- doc_usage – (str) describe accepted parameters
- doc_description – (str) long description/help for cmd
- cmd_options – (list of dict) see cmdparse.CmdOption for dict format
cmd_options
uses the same format as
task parameters.
If the command needs to access tasks it should sub-class doit.cmd_base.DoitCmdBase.
Example: scaffolding¶
A common example is applications that provide some kind of scaffolding when creating new projects.
from doit.cmd_base import Command
class Init(Command):
doc_purpose = 'create a project scaffolding'
doc_usage = ''
doc_description = """This is a multiline command description.
It will be displayed on `doit help init`"""
def execute(self, opt_values, pos_args):
print("TODO: create some files for my project")
plugins¶
doit plugin system is based on the use of entry points, the plugin does not need to implement any kind of “plugin interface”. It needs only to implement the API of the component it is extending.
Plugins can be enabled in 2 different ways:
- local plugins are enabled through the doit.cfg file.
- plugins installed with setuptools (that provide an entry point), are automatically enabled on installation.
Check this sample plugin for details on how to create a plugin.
config plugin¶
To enable a plugin create a section named after the plugin category. The value is an entry point to the python class/function/object that implements the plugin. The format is <module-name>:<attribute-name>.
Example of command plugin implemented in the class FooCmd, located at the module my_plugins.py:
[COMMAND]
foo = my_plugins:FooCmd
Note
The python module containing the plugin must be in the PYTHONPATH.
category COMMAND¶
Creates a new sub-command. Check command section for details on how to create a new command.
category BACKEND¶
Implements the internal doit DB storage system. Check the module doit/dependency.py to see the existing implementation / API.
category REPORTER¶
Register a custom reporter as introduced in the custom reporter section.
category LOADER¶
Creates a custom task loader. Check loader section for details on how to create a new command.
Apart from getting the plugin you also need to indicate which loader will be used in the GLOBAL section of your config file.
[GLOBAL]
loader = my_loader
[LOADER]
my_loader = my_plugins:MyLoader