Introduction

Quickie is a simple task runner, inspired on tools like cargo-make, Task, and invoke. It aims to be simple to use, easy to extend, and to provide a good experience for teams and individuals.

Unlike other task runners that define tasks in YAML, TOML or specialized formats, Quickie uses the Python programming language directly, leveraging the power of the language and the ecosystem around it. This means for example, that syntax highlighting, errors, and auto completion in most code editors will work out of the box.

Some features include:

  • Run python, shell scripts, and subprocesses as tasks.

  • Powerful arguments parsing, by wrapping argparse.

  • Autocompletion both for the CLI and the individual tasks, thanks to argcomplete.

  • Custom autocompletion for task arguments.

  • Conditions to control when or if a task should run.

  • Dependencies between tasks.

  • Namespaces to organize tasks.

Requirements

Quickie works with macOS, Linux and Windows. Python 3.12 or higher is required.

Per Project Installation

You can install Quickie with pip or your favorite package manager. For projects, it is usually better to install Quickie in a virtual environment. By using a virtual environment you can isolate your dependencies for that specific project, and use different versions of Quickie for different projects without conflicts.

python -m venv .venv
source .venv/bin/activate
pip install quickie-runner
qk --help

Global installation

While Quickie allows to run tasks defined between a project, sometimes it is useful to have tasks defined globally and run them from anywhere. quickie-runner-global is a package that allows to do just that.

This is a wrapper around quickie-runner that will add a separate qkg command, thus not conflicting with qk. Tasks in this case need to be defined at ~/Quickie.

You can do this install for your default Python installation, or use pipx to create an isolated environment.

With pip

pip install quickie-runner-global
qkg --help

With pipx

pipx install quickie-runner-global
qkg --help

Tip

If installing via PIPX and you need to add extra dependencies, you can inject them:

pipx inject quickie-runner-global my-extra-dependency

Upgrading

You can also upgrade via pip:

pip install --upgrade quickie-runner

Or pipx:

pipx upgrade quickie-runner

Auto completion

Quickie provides auto completion for tasks and arguments via the argcomplete package.

To enable it, you need to install argcomplete globally and add the following line to your shell configuration file:

eval "$(register-python-argcomplete qk)"

This will enable auto completion for the qk command. If you have a global installation, you can enable auto completion for the qkg command as well:

eval "$(register-python-argcomplete qkg)"

You can also call qk --autocomplete bash or qk --autocomplete zsh for instructions on how to enable auto completion for your shell.

Quick(ie)start

Defining tasks

Tasks can be defined in a __quickie Python module, be it a single file or a package, usually at the root of the project. For global tasks they can be defined in the same way at ~/Quickie. They can also be defined at an arbitrary Python module, and passed to the runner using the –module or -m argument.

For example:

# MyProject/__quickie.py
from quickie import arg, task, script, command

@task
def hello():
    print("Hello, World!")

@script
@arg("--name", help="Your name")
def hello_script(name):
    return f"echo 'Hello, {name}!'"

@command(extra_args=True)
def some_command(*args):
    return ["my_command", *args]

Now you can run the tasks from anywhere in the project, even from a subdirectory.

$ qk hello
Hello, World!

$ qk hello_script --name Alice
Hello, Alice!

$ qk some_command arg1 arg2
my_command arg1 arg2

Defining tasks in a package

For more complex projects, or teams, it is recommended to define tasks in a package. This allows to better organize the tasks and to have private tasks that are not committed to the repository.

For example:

MyProject/
├── __quickie
│   ├── __init__.py
│   ├── public.py
│   ├── private.py  # might not exist   └── ...        # more files
└── ...

Then in the __init__.py file you can import the tasks from the other files.

# MyProject/__quickie/__init__.py
from . import public
try:
    from . import private
except ImportError:
    # Null values are ignored
    private = None

NAMESPACES = {  # Namespaces tasks
    "": [public, private],  # private tasks will take precedence
    "private": private,  # private tasks also available under `private` namespace
}

For most of of the documentation, we will assume tasks are defined in a package.