A convenient, flexible mechanism for running arbitrary tasks (or sequences of tasks) repeatedly.
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



Originally designed to aid with performing batch runs of Player/Stage simulations, it developed into a convenient, flexible mechanism for running arbitrary tasks (or sequences of tasks) repeatedly. The outputs of any executed commands are captured and collated between runs.

The Jinja2 templating engine is embedded, providing advanced value substitution on a per-run basis. This makes the process of varying the contents of configuration files extremely simple.

A note on terminology: A job consists of a number of runs. A run may consist of a single command, or sequence of commands.



To perform a complete job execute run.py with the job file as a parameter:

$ run.py /path/to/job

If the job has a number of runs and you'd only like to execute a subset of them, specify a range after the job file:

$ run.py /path/to/job 4
$ run.py /path/to/job 5,7,10-11
$ run.py /path/to/job "5, 7, 10-11"

Ranges are comma-delimited and can be either individual run numbers or spans. If any template substitutions use generators (covered below) they will be 'skipped' forward appropriately to ensure the values emitted match those from a full job.

NOTE: If any generators make use of random numbers, a seed must be specified to allow deterministic, repeated runs.


A job can reference templates, such as configuration files, containing variables. Variables can be bound to either a constant or a generator. Constants allow centralised access across files. Generators allow differing values to be substituted in between runs.

A variable is defined as {{ variable_name }}. Any occurrences of this within a template will be replaced by the corresponding value defined in the job file.

The Jinja2 templating engine is used internally, providing advanced control flow such as conditionals and loops. See the Jinja2 documentation for detailed usage information.


Generators provide a way to vary the values of template variables between runs. The built-in generators are:

cycle(choices, initial_offset = 0)

  • choices: A list of the items to iterate over.
  • initial_offset: The index of the item to return for the first call.

Returns items from a list sequentially, wrapping around to the first item if necessary.

random_choice(choices, seed = None)

  • choices: A list of the items to choose from.
  • seed: The number to seed the random number generator with. If omitted, the time when the variable is bound is used.

Returns a random item from the list of choices.

random_range(min = 0, max = 1, seed = None)

  • min: The inclusive lower bound for values to return.
  • max: The inclusive upper bound for values to return.
  • seed: The number to seed the random number generator with. If omitted, the time when the variable is bound is used.

Returns a random number in the range [min, max]. If both min and max are integers the random number will also be an integer. Otherwise, a floating point value will be returned.

seq(start = 0, step = 1)

  • start: The value to return on the first call.
  • step: The value to increment/decrement by for subsequent calls.

Returns the previous value incremented by the value of step (or decremented, if step is negative). The return value will be an integer, unless either start or step is a floating point.

Creating a new generator

Generators must be located in batcher/generators subdirectory and begin with a 'g_'. They must inherit from the base Generator class. The class name is used to refer to the generator from within the job file.

A generator class must provide a Generate() method, returning a Python Generator object via the yield statement.

For convenience, a skeleton generator is provided below:

from generator import Generator

class constant(Generator):

    def __init__(self, constant):
      self.constant = constant

    def Generate(self):
        while True:
            yield self.constant

Job File Format

The job file is written in JSON and consists of a number of sections. In the global scope are general job settings:

  • runs: The number of runs to perform. Required.
  • output-dir: The directory to store the job's output. Batcher saves a log of its output to 'job.log'. The captured output from each run is stored in a numbered subdirectory. Optional; defaults to "output" if omitted.
  • nuke-existing: If false and the output directory exists the job is aborted. If true the existing output directory is deleted before the job starts. Use with caution. Optional, defaults to "false" if omitted.


Binds variables to a generator, optionally specifying any initialisation parameters. Generators are specified as follows:

"variable_name": {"type": "generator_name", "options": {"arg1": value}}
  • variable_name defines the placeholder text that will be substituted for the generated value, if wrapped in ${}.
  • type specifies the type of generator, i.e. its class name. See the Generators section above for a list of built-in generators.
  • options are the keyword arguments to pass to the generator's constructor.


A list specifying any templates to include in the job, of the form:

{"src": "/path/to/file", "dst": "relative/to/run/output"}

src specifies the source file and can be relative to the job file, or absolute. dst is the destination of the populated template. Relative paths are relative to the run's output directory. If dst is omitted the source file is copied into the root of the run's output directory.

Note that if binary files or non-template files are required to reside within the run's output directory then the operating system's copy command should be used in the run section of exec, before the main task(s). For example:

{"bin": "cp", "args": ["/path/to/file", "."]}


Defines the values that each of the template variables should take. The key is the name of the variable in any template files, for example, {{ name }} would be "name": value in the mapping.

Values can be any valid JSON object. Arrays will be used for populating any loops in the template.

The output of a generator can be substituted into string values, by wrapping the generator's name with ${}. For example, to substitute the value of a generator called position into a template variable {{ start }}:

  "position": ...

  "start": "${position}"

Generator substitutions can appear anywhere within a value; including nested within arrays or objects, and even with multiple occurrences.

Note: Generators are stepped at the start of each run, so all occurrences of a substitution within a run will be assigned the same value.


A list of commands to execute at each stage of the batch job.

  • pre: Executed once, before the first run is started. Optional.
  • run: Executed for each run of the job. Optional.
  • post: Executed once, after the last run has finished. Optional.

Commands are specified as follows:

{"bin": "/path/to/binary", "args": ["arg1", "arg2"], "blocking": true}

When blocking is set to false the command is executed asynchronously. The working directory for any executed commands is the run's output directory.

bin is required; both args and blocking are optional, defaulting to none and true respectively.


A complete example can be found in the example subdirectory.