Introduction

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

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.

Quickie is not limited to Python projects, you can for example define a virtual environment with Quickie and other dependencies alongside a project that is not using Python at all, similar to how make can be used in non C/C++ projects.

Some features include:

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

  • Powerful arguments parsing, by wrapping argparse.

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

  • Custom autocompletion for task arguments.

  • Conditions to run tasks only if certain conditions are met.

  • Dependencies between tasks.

  • Namespaces to organize tasks.

Requirements

Quickie has been tested on macOs, but should work on Linux and Windows as well. If you find any issues, please open an issue on GitHub.

Per Project Installation

The recommended way to use Quickie is to install it per-project in a virtual environment. This allows each project to pin its own version of Quickie and avoids version conflicts across projects.

With automatic launcher (recommended):

Once you have Quickie installed in a project virtual environment, you can run tasks from anywhere without manual virtual environment activation. The global qk command will automatically discover your project and delegate to the project-specific version:

cd my-project
python -m venv .venv
.venv/bin/pip install quickie-runner
qk task-name  # Automatically uses project-specific quickie!

The launcher will search for a _qk directory or _qk.py file starting from your current directory and traveling up the directory tree. Once found, it delegates to the quickie installation in that project’s virtual environment.

Traditional manual activation (still supported):

If preferred, you can still manually activate the virtual environment:

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

Global installation

While Quickie allows you to run tasks defined within a project, sometimes it is useful to have global tasks accessible from anywhere. Install quickie-runner globally (e.g. with pipx) and use the --global (or -g) flag:

qk --global task-name

Global tasks are stored in ~/_qkg and are only used when explicitly requested with --global (or -g).

Installation with pipx (recommended for global tools):

pipx install quickie-runner
qk --global task-name

Tip

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

pipx inject quickie-runner 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 is sufficient for both project tasks and global tasks. When tab completing inside a project directory, the smart launcher delegates to the project-local quickie installation, so completions reflect that project’s tasks and version.

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 _qk 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 ~/_qkg. They can also be defined at an arbitrary Python module, and passed to the runner using the --module or -m argument.

For example:

# MyProject/_qk.py
from quickie import Arg, task, script, command

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

@script(
    args=[
        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/
├── _qk
│   ├── __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/_qk/__init__.py
from quickie import namespace
from . import public

@namespace
def _():
    tasks = [public]
    try:
        from . import private
        tasks.append(private)
        return {"": tasks, "private": [private]}
    except ImportError:
        return tasks

Because @namespace is lazy, the optional private module is only imported when Quickie actually needs one of the tasks in this namespace.

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