A set of extensions for rich output, more options in collection/config discovery through entry-points.
This extends the Collection from Invoke so it can create automatically collections.
- Task discovery by namespace for extendable/composable CLIs
- Discovery to plain old tasks.py (or any other name)
- Local tasks discovery from
local_tasks.pyin the current directory - Integration with stand alone binaries for specific tasks
- Task result caching with TTL support via
diskcache(optional) - Future Download binaries
If you have...
- Used
invokefor a while and... - Have a large
tasks.pythat needs to be modularized - Have a lot of copy/pasted code in multiple
tasks.pyacross multiple repos. - Have exceeded the approach of a repository cloned as
~/tasks/with more .py files that you want to manage. - Or you want to combine various tasks defined in multiple directories
- You want to create a zipped (shiv) redistribute script for container environments like Kubernetes based CI environments with only requiring the Python interpreter.
pip install invoke-toolkitCreate a local_tasks.py file in your project directory with your tasks:
from invoke_toolkit import task
@task()
def my_task(ctx):
"""Do something useful"""
print("Hello from local tasks!")Then run it with:
intk local.my-taskLocal tasks are automatically discovered and added to the local namespace, allowing you to keep project-specific tasks separate from your main task collection.
Cache expensive task results with the cache parameter:
from invoke_toolkit import task
# Simple caching (no expiration)
@task(cache=True)
def expensive_task(ctx, param: str) -> str:
"""Results are cached across invocations."""
return do_expensive_computation(param)
# Caching with TTL (1 hour)
@task(cache={"ttl": 3600})
def cached_task(ctx, name: str) -> dict:
"""Results cached for 1 hour."""
return fetch_data(name)
# Caching with ignored arguments
@task(cache={"ttl": 600, "ignore_args": ["verbose"]})
def cached_with_options(ctx, query: str, verbose: bool = False) -> list:
"""Cache key ignores verbose flag."""
return search(query, verbose=verbose)Cache features:
- Cache location is computed from git repository root + platformdirs
- Debug logging (
-dflag) shows cache hits/misses - Graceful degradation when
diskcacheis not installed
To enable caching, install with the cache extra:
pip install invoke-toolkit[cache]Use Field when an argument default must be computed from the final task
context or resolved from a URI. Explicit command-line values always take
precedence.
Bind a resolver once, then reuse the resulting callable for scalar and file defaults. A local resolver always returns one string value per request. The resolver receives every URI using that callback and scheme in one batch.
from invoke_toolkit import Context, Field, FilePath, task
def resolve_bw(ctx: Context, requests: list) -> dict[str, str]:
return {
request.parameter: ctx.run("bw get password ...", hide=True).stdout.strip()
for request in requests
}
BitwardenField = Field(resolver=resolve_bw)
ExistingFile = FilePath(exists=True, dir_okay=False)
@task
def deploy(
ctx: Context,
password: str = BitwardenField(default="bw://PASSWORD_ID"),
config: ExistingFile = BitwardenField(
default="bw://CONFIG_ID", cleanup="task"
),
) -> None:
...For str fields, the resolver string reaches the task unchanged. For Path or
FilePath fields, Field.create_temporary_file() writes that string to a
managed temporary file and passes its Path to the task. Override that method
in a Field subclass to control file creation; resolvers do not return paths.
For omitted arguments, lookup precedence is explicit CLI/Python/call value,
then INVOKE_<PARAMETER> environment value, then the resolved ctx.config
value, then the declared Field default or factory. URI values from config use
the same local or entry-point resolver dispatch as declared URI defaults.
Field(default_factory=callback) runs only if no higher-precedence value is
available. Factories and resolvers never run during help, listing, or shell
completion; Path/FilePath completion continues to work normally for
explicit values.
Temporary Path files from resolver-bound Field instances are owned by
invoke-toolkit. cleanup="pipeline" is the default and keeps the file through
expanded pre/main/post execution; cleanup="task" removes it when that task
returns. Cleanup runs after failures and cancellation.
Installed invoke_toolkit.field_resolver entry points remain supported for
compatibility but issue a warning recommending a task-local resolver. Generate a
resolver-only provider package with:
intk -x create.package --provider opProvider packages expose no task collection. Providers return text only; for
Path fields invoke-toolkit materializes and cleans the resulting temporary
file according to that Field's cleanup lifetime.
Plugin management requires a persistent uv tool install invoke-toolkit
environment. Plugins are distributions that expose an
invoke_toolkit.collection entry point; their package names do not need an
invoke-toolkit- prefix.
intk -x plugin.list
intk -x plugin.add 'invoke-toolkit-example>=1'
intk -x plugin.add git+https://github.com/example/invoke-toolkit-example.git
intk -x plugin.add ./invoke-toolkit-example
intk -x plugin.link ./invoke-toolkit-example
intk -x plugin.update --name invoke-toolkit-example
intk -x plugin.update
intk -x plugin.remove invoke-toolkit-exampleplugin.add uses uv's --with mode. A local directory is built as a static
installation. plugin.link uses --with-editable, so source changes are live.
A named update refreshes one non-editable plugin; an update without a name
refreshes all non-editable plugins and reports linked plugins as skipped.
Registry constraints and pinned Git references remain in effect. Plugin updates
do not update invoke-toolkit itself.
Shell completion suggests directly installed plugin names for plugin.remove
and named plugin.update operations.
To reduce shell-completion latency in environments with expensive collection
plugins, set completion.disable_plugins: true in invoke.yaml:
completion:
disable_plugins: trueINVOKE_COMPLETION_DISABLE_PLUGINS=1 enables the same behavior without changing
the configuration file. Completion then skips installed
invoke_toolkit.collection entry points while retaining project, local, and
built-in -x task suggestions. These settings affect completion only; normal
task execution and listing still load plugins.
These commands reject uvx, uv run, project environments, and installations
managed by another tool. pipx offers a similar manual workflow:
pipx inject invoke-toolkit PLUGIN
pipx inject invoke-toolkit --editable ./PLUGIN
pipx uninject invoke-toolkit PLUGIN
pipx list --include-injectedpipx accepts registry, local, and Git sources supported by pip. invoke-toolkit does not detect or execute pipx lifecycle operations.
This project utilizes the pre-commit framework, make sure you run:
pre-commit install
With uvx:
uvx --with pre-commit-uv pre-commit install
invoke-toolkit is distributed under the terms of the MIT license.