Skip to main content

footman

A task runner with the soul of duty and the UX of typer: typed function signatures become real flags and positionals, modules become nested command groups, independent tasks run in parallel by default, and shell completion answers from a cached manifest — without importing your code. Building that cache does import it, once, in a detached subprocess: the first Tab in a fresh directory pays for it, and no keystroke after that.

Ships two console scripts, footman and the two-letter fm — one program, both names, so a PATH that already has an fm still gets the long spelling. Zero runtime dependencies. Python 3.11+ (the first Python with stdlib TOML reading — that floor is what buys the zero).

Beta. footman is pre-1.0: the surface is settling, but minor versions may still include breaking changes — always called out in the changelog, never in a patch release. Pin the minor (footman~=0.49.0) if you build on it. What is covered, what is internal, and what has to be true before 1.0 are written down in the stability promise.

Why

duty gets a lot right — the run() capture model, the decorator ergonomics — and footman keeps those ideas. It tries to improve on the parts you meet every day: completion served from a cache instead of re-importing your project on every Tab, eager type and choice validation with errors that teach, a DAG scheduler that runs independent tasks concurrently — four steps that each wait on something finish in the time of the longest, not the sum — with true fail-fast that kills in-flight work on the first failure, runnable command groups and parameter forwarding that make composite commands like fm lint and check real commands, a monorepo task cascade that merges a tasks.py per directory, and a first-party story for testing your tasks. The receipts live in the comparison — every number reproducible from comparison/.

Taste

uv add --dev footman        # or: pip install footman
# tasks.py
from typing import Literal
from footman import task, group, run
from toolroom import pytest, ruff

@task
def lint(fix: bool = False):
    "Run ruff over the project."
    ruff.check("src", fix=fix)

@task
def typecheck():
    "Type-check the project."
    run("basedpyright src")

@task(pre=[lint])
def test(*pytest_args):
    "Run the test suite (extra args after --)."
    pytest(*pytest_args)

@task
def deploy(target: Literal["dev", "staging", "prod"]):
    "Deploy to a target."
    run(f"./deploy.sh {target}")

docs = group("docs", help="Documentation")

@docs.task(infinite=True)
def serve(port: int = 8000):
    "Serve the docs locally."
    run(f"mkdocs serve -a localhost:{port}")
$ fm lint --fix
$ fm lint typecheck test                # one chain; independent tasks run in parallel
$ fm docs.serve --port=8001             # options go right after their task
$ fm test -- -k grammar -x              # everything after -- goes to pytest
$ fm deploy produ
fm: deploy: <target> must be one of dev|staging|prod (got 'produ') — did you mean 'prod'?
$ fm --install-completion               # detects your shell; TAB answers from the cache

That from toolroom import … is optional. toolroom makes command-line calls Python: it wraps any program, not a fixed list, with keyword arguments becoming flags — toolroom.terraform("plan") runs whether or not toolroom has heard of terraform. For common tools it also ships type hints generated from each tool's own metadata, so your editor knows the flags and what they do; the hints only decide whether your editor can help, never whether a call works.

It began as part of footman and was spun out for two reasons: it releases on its own schedule, and other people might want type hinted, documented command-line calls without using footman. footman does not depend on it and never imports it — plain run("…") is always there.

One file, dependencies included

A tasks file can declare what it needs inline (PEP 723), and then it needs no project at all — drop it in any directory and run it:

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["footman", "httpx"]
# ///
import footman
from footman import task

@task
def health(url: str = "https://example.com"):
    "Check a deployment is up."
    import httpx
    print(httpx.get(url).status_code)

if __name__ == "__main__":
    footman.main(__file__)

fm health builds that environment once and runs inside it — name any other file with -f=deploy.py and the same rule applies. chmod +x and ./deploy.py health works with no runner installed at all. Checked into a project that pins footman, the header is simply ignored and the file runs on the project's dependencies — portable and at home.

Learn more

Documentation — start with Getting started, then the good parts: typed signatures · chaining & parallelism · monorepos · composing tasks · running tools · testing your tasks · completion · CI & automation · comparison with duty / invoke / poe / typer

MIT licensed. The road to 1.0 lives in ROADMAP.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

footman-0.49.0.tar.gz (442.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

footman-0.49.0-py3-none-any.whl (464.8 kB view details)

Uploaded Python 3

File details

Details for the file footman-0.49.0.tar.gz.

File metadata

  • Download URL: footman-0.49.0.tar.gz
  • Upload date:
  • Size: 442.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for footman-0.49.0.tar.gz
Algorithm Hash digest
SHA256 79d34cd2064ae6bd63bd9056b5efe4b5a6299d63d4d2a24b8e4aace38a196ebb
MD5 2b09e0ce5920d229ac420d2b9de32ed0
BLAKE2b-256 d8fd7c564faf1451c9b22ac8135eab6906fa6f0405b4f933e93109e94289557c

See more details on using hashes here.

Provenance

The following attestation bundles were made for footman-0.49.0.tar.gz:

Publisher: release.yml on willemkokke/footman

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file footman-0.49.0-py3-none-any.whl.

File metadata

  • Download URL: footman-0.49.0-py3-none-any.whl
  • Upload date:
  • Size: 464.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for footman-0.49.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ab7095998187f84cada61345db1cae8fd1a87bd41afbb3c539556f485b6d3ab
MD5 91fa63915174b04431aaef37844bdd3f
BLAKE2b-256 2a0812dfc43dd7d676a10f4512f717631ba23d4f0bd5d24667b036f8e52ba0ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for footman-0.49.0-py3-none-any.whl:

Publisher: release.yml on willemkokke/footman

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.50.0

2 files

0.49.1

2 files

This release

0.49.0 This release

2 files

0.48.0

2 files

0.47.0

2 files

0.46.0

2 files

0.45.0

2 files

0.44.0

2 files

0.43.0

2 files

0.42.0

2 files

0.41.0

2 files

0.40.0

2 files

0.39.1

2 files

0.39.0

2 files

0.38.1

2 files

0.38.0

2 files

0.37.0

2 files

0.36.0

2 files

0.35.0

2 files

0.34.0

2 files

0.33.0

2 files

0.32.0

2 files

0.31.0

2 files

0.30.0

2 files

0.29.1

2 files

0.29.0

2 files

0.28.1

2 files

0.28.0

2 files

0.27.1

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.0

2 files

0.23.0

2 files

0.22.0

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.0

2 files

0.16.0

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page