mrkeyoor.com_
Sun 20 Sept 23:43 UTC
PyPICLI & Toolingupdated 20 Sept 2026

invoke review

Invoke 3.0.3 turns decorated Python functions into project commands and gives those functions a Context for running local subprocesses. A tasks.py module can define flags, aliases, prerequisites, namespaces, help text, configuration, prompt responders, and PTY behavior. It suits shell-heavy automation that has outgrown a short Make target but still belongs beside the source. Release 3.0.3 specifically removed a @task return annotation introduced one day earlier because that typing change broke uses where decorated tasks were passed to collection methods. Our install added only 1 MB and no dependencies.

Verdict

Invoke 3.0.3 installed in 0.3 seconds as 1 MB with 0 dependencies in our sandbox, so package cost is negligible. Install it when project commands need real Python control flow; skip it for incremental builds, environment matrices, or a couple of fixed subprocess calls.

We installed it

Lab card: what happened when we installed invokeScreenshot of invoke documentation
Install✓ · 0.3s1 package on disk · 1 MB
Importimport invoke in 0.55s · pure Python · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does invoke install cleanly?

Yes. In a fresh container with an empty cache, pip install invoke finished in 0.3s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does invoke need to run?

Python >=3.9, and nothing compiled: it is pure Python. In our run import invoke succeeded in 0.55s, and the package ships py.typed for type checkers.

invoke or doit: which should you use?

doit: Use it when Python 3 tasks need dependencies plus target files and up-to-date checks. Invoke 3.0.3 installed in 0.3 seconds as 1 MB with 0 dependencies in our sandbox, so package cost is negligible.

When should you not use invoke?

Build work depends on file freshness; Invoke orders tasks but does not compare input and output timestamps like make or doit

API stability4/5Invoke 3.0.3 keeps the long-standing @task, Context, Collection, Result, and Program concepts, and normal tasks still resemble older major versions. The 3.0 line mainly raises supported Python versions and adjusts edge behavior. A return annotation added in 3.0.2 had to be removed in 3.0.3 because decorated tasks no longer fit collection APIs, which shows that typing changes around the decorator deserve direct tests.
Docs4/5The versioned documentation covers task arguments, command execution, namespaces, configuration layers, runners, watchers, testing, and library embedding. API pages identify exceptions and runner options instead of stopping at a quick start. A production task often touches 3 or more of those chapters, and the site does not consolidate directory behavior, config precedence, PTY limits, and failure handling into one deployment checklist.
Maintenance2/5PyPI published 3.0.3 on 2026-04-07 and GitHub records the repository's latest push on the same date. GitHub also reports 460 open items including both issues and pull requests. Three 3.0 releases appeared within 2 days, one reverting a typing regression, but there has been no later repository push. The project is mature, yet teams with uncommon runner bugs should not assume a quick upstream patch.
Ecosystem4/5PyPI Stats counted 19,288,326 Invoke downloads in the latest week, and GitHub showed 4,768 stars. Fabric builds remote execution around Invoke's context and runner model, giving local task code an established neighboring project. The package has no runtime dependencies, ships py.typed, and imported in 0.55 seconds in our Python 3.12 test, though its extension story is ordinary Python modules rather than a large plugin registry.

Use it if

  • Python 3.9+ is already required and build or release commands need branching that is awkward in a Makefile
  • Contributors need inv --list plus generated per-task flags and help without maintaining a separate command parser
  • Subprocess calls need captured output, live streaming, environment overrides, prompt responses, or explicit failure handling
  • Local tasks may later share Invoke's command model with Fabric for remote execution
Skip it if

Setup reality

We installed Invoke 3.0.3 in 0.3 seconds under Python 3.12. It left 1 package and 1 MB on disk, with 0 direct dependencies and 0 known vulnerabilities from pip-audit. import invoke completed in 0.55 seconds. The distribution is pure Python, requires Python 3.9+, and includes py.typed. PyPI leaves its license field empty, even though the GitHub repository identifies BSD-2-Clause. The How we test run used a clean unprivileged container.

Both invoke and inv commands are installed. Invoke searches upward from the current directory for tasks.py or a tasks package, so starting in a nested checkout can select a different collection than expected. Each @task function reserves argument 1 for Context. Underscores become dashes in CLI options, and inferred one-letter flags may vanish when two names share an initial. Add explicit help and aliases to commands used by a team.

Context.cd() changes the shell command prefix, not Python's current directory. A pathlib read inside that block still resolves from the original process location. Context.run() raises UnexpectedExit for a nonzero exit unless warn=True. PTY mode combines stderr with stdout and fails on Windows. Prompt watchers can send responses repeatedly, so narrow the regular expression and keep credentials out of tasks.py and checked-in invoke.yaml files.

Invoke 3 merges defaults, machine and user files, project config, environment variables, collection config, and call overrides. INVOKE_ values only map to known keys and still undergo type conversion. Pre and post tasks establish order within 1 invocation; they do not cache outputs for tomorrow's run. If a project has only 2 or 3 fixed subprocess calls, plain subprocess.run is easier to trace and removes task discovery from the execution path.

Patterns

Expose a Python function through inv define-task

from invoke import task

@task
def test(c, coverage=False):
    command = 'pytest --cov' if coverage else 'pytest'
    c.run(command)

# inv test --coverage

Argument 1 is always the Context and does not become a command-line option in Invoke 3.

Document generated flags add-option-help

@task(help={'env': 'Deployment environment', 'dry-run': 'Print only'})
def deploy(c, env='staging', dry_run=False):
    print(env, dry_run)

Use dry-run in the help mapping because Python's dry_run parameter is exposed as --dry-run.

Inspect a failed subprocess handle-command-failure

result = c.run('ruff check .', warn=True, hide=True)
if result.failed:
    print(result.exited, result.stdout, result.stderr)

Without warn=True, a nonzero status raises UnexpectedExit. hide=True suppresses display but leaves output on Result.

Run cleanup before a build declare-prerequisite

from invoke import call, task

@task
def clean(c, docs=False):
    c.run('rm -rf build')

@task(pre=[call(clean, docs=True)])
def build(c):
    c.run('python -m build')

A pre-task gives order for 1 run. Invoke does not check whether build outputs are already newer than their sources.

Prefix shell work with a directory change run-in-directory

@task
def frontend(c):
    with c.cd('web'):
        c.run('npm ci')
        c.run('npm run build')

c.cd() affects commands sent to the shell. Python open() and pathlib still use the process working directory.

Publish namespaced task groups group-namespaces

from invoke import Collection
from tasks import docs, database

ns = Collection()
ns.add_collection(Collection.from_module(docs), name='docs')
ns.add_collection(Collection.from_module(database), name='db')

Only tasks added to ns appear in inv --list once an explicit root collection exists.

Pass a scoped environment value set-subprocess-environment

c.run(
    'alembic upgrade head',
    env={'DATABASE_URL': c.database.url},
    echo=True,
)

env merges with the inherited environment. replace_env=True removes everything else, including PATH unless you add it.

Answer one known prompt respond-to-prompt

from invoke import Responder

confirm = Responder(pattern=r'Continue\? ', response='yes\n')
c.run('tool deploy', watchers=[confirm])

A watcher may respond more than 1 time when its pattern repeats. Keep its match narrow and do not put secrets in source.

Accept multiple values for one option collect-repeatable-flag

@task(iterable=['tag'])
def publish(c, tag=()):
    for value in tag:
        print(value)

# inv publish --tag latest --tag v3

Iterable flags arrive as a sequence. Validate each value before using it in a shell command.

Alternatives

PackageRegistryPick it when
doitPyPIUse it when Python 3 tasks need dependencies plus target files and up-to-date checks
taskipyPyPIUse it for a small set of commands declared directly in pyproject.toml
poethepoetPyPIUse it when pyproject tasks need sequences, environment values, and script references

More cli & tooling guides

commander · chalk · typescript · esbuild · yargs · click · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.