Tasks as Functions

Usage

If a task is relatively simple, but isn’t suited to being defined as a string, it can instead be defined as a function. The function can be used to simply run some Python code, or it can construct and return a command to execute on the command line (or both!). Tasks defined in this way might be useful if:

  • The task does not need to execute anything on the command line (it returns nothing)

  • The returned command is dynamic, and may differ depending on certain aspects of the environment, e.g. whether a particular package is installed

  • The returned command is complex and it is useful for various aspects of it to be commented

  • The returned command is simply too long to be easily maintained as a single string

  • It is a shared task that may require per-project configuration (see Using settings below)

In jog.py, these tasks might look like:

def run_tests(settings, stdout, stderr):
    """
    Run the Django test suite, with coverage.py if installed.
    """

    try:
        import coverage
    except ImportError:
        stdout.write('Warning: coverage.py not installed.', style='warning')
        return 'python manage.py test'
    else:
        return 'coverage run python manage.py test'

tasks = {
    'test': run_tests
}

Task functions accept the following arguments:

  • settings: A dictionary of the settings defined for the task in a config file, as explained below.

  • stdout: A proxy for the standard output stream, offering more control over output from the task. See Controlling Output.

  • stderr: A proxy for the standard error stream, offering more control over output from the task. See Controlling Output.

Like tasks defined as strings, tasks defined as functions cannot accept additional command line arguments. Their behaviour is configurable via static settings, but not by runtime arguments. For example, considering the test task defined above:

jog test  # good
jog test myproject.tests.test_module  # bad

Halting execution

If an error occurs and the execution of the task should be interrupted, simply raise TaskError. Any message passed to the exception will be written to the configured stderr stream and the task will be halted.

Using settings

As noted above, task functions accept a settings argument. Your project can define a config file, as explained further in the Config Files documentation. If that config file contains a section for the task being executed, the settings within that section will be passed through to the task using the settings argument. This allows common tasks to be shared among multiple projects, while still allowing them to be configured as necessary for each one.

Re-working the above example so that the use of coverage.py is based on a project-level setting might look like:

[tool.jogger.test]
coverage = true
[jogger:test]
coverage = true
# jog.py
def run_tests(settings, stdout, stderr):
    """
    Run the Django test suite, optionally with coverage.py.
    """

    if settings.get('coverage', True):
        return 'coverage run python manage.py test'
    else:
        return 'python manage.py test'

tasks = {
    'test': run_tests
}

Default arguments

Function-based tasks accept a minimal set of default arguments:

  • -h/--help: Display the task’s help output. The description will be pulled from the function’s docstring. If the function does not have a docstring, the task’s signature and argument list will be displayed, but it will not include any descriptive text.

  • --no-color: Prevents colourisation of output (e.g. if the task makes use of styled output).

  • --stderr: The output stream to use for error messages. Defaults to the system’s stderr stream. Can be redirected, e.g. to a file: jog test --stderr /home/myuser/logs/test/err.log.

  • --stdout: The output stream to use for general messages. Defaults to the system’s stdout stream. Can be redirected, e.g. to a file: jog test --stdout /home/myuser/logs/test/out.log.

Note

Only output generated using the stdout and stderr function arguments is affected by the --no-color option. Any output generated by a returned command will NOT be affected. If the command accepts its own argument for suppressing coloured output, it should be incorporated into the returned command string if necessary.