A batteries included task runner that works well with poetry and uv
—
Multiple task types supporting different execution modes: shell commands, Python expressions, script references, and complex task composition with sequences and DAGs.
Poethepoet supports multiple task execution modes, each optimized for different use cases.
# Available task types in configuration
TASK_TYPES = [
"cmd", # Shell command execution
"shell", # Shell script execution
"script", # Python script/function execution
"expr", # Python expression evaluation
"sequence", # Sequential task composition
"ref", # Task reference/alias
"switch" # Conditional task switching
]Execute shell commands with full argument and environment support.
# Command task configuration
class CmdTask:
cmd: str # Command to execute
args: list[str] # Additional arguments
env: dict[str, str] # Environment variables
cwd: str # Working directory
help: str # Help description
deps: list[str] # Task dependencies[tool.poe.tasks]
# Simple command
test = "pytest tests/"
# Command with arguments
serve = {cmd = "python -m http.server", args = ["8000"]}
# Command with environment variables
build = {cmd = "python setup.py build", env = {DEBUG = "false"}}
# Command with working directory
docs = {cmd = "sphinx-build . _build", cwd = "docs/"}
# Command with dependencies
deploy = {cmd = "deploy.sh", deps = ["test", "build"]}Execute shell scripts with interpreter selection and variable substitution.
# Shell task configuration
class ShellTask:
shell: str # Shell script content
interpreter: str # Shell interpreter
env: dict[str, str] # Environment variables
cwd: str # Working directory
help: str # Help description
deps: list[str] # Task dependencies[tool.poe.tasks]
# Multi-line shell script
setup.shell = """
echo "Setting up environment..."
mkdir -p build/
cp config.json build/
echo "Setup complete"
"""
# Shell script with custom interpreter
bash_script = {shell = "echo 'Hello from bash'", interpreter = "bash"}
# Shell script with environment variables
build_env = {
shell = "echo $PROJECT_NAME version $VERSION",
env = {PROJECT_NAME = "myapp", VERSION = "1.0.0"}
}Execute Python functions or modules with argument passing and import resolution.
# Script task configuration
class ScriptTask:
script: str # Python function reference (module:function)
args: list[str] # Function arguments
env: dict[str, str] # Environment variables
cwd: str # Working directory
help: str # Help description
deps: list[str] # Task dependencies[tool.poe.tasks]
# Function reference
build = {script = "scripts:build_project"}
# Function with arguments
process = {script = "data.processor:main", args = ["--input", "data.csv"]}
# Module execution
serve = {script = "myapp:run_server"}
# Built-in script utilities
clean = {script = "poethepoet.scripts:rm", args = ["dist/", "build/"]}Evaluate Python expressions with access to environment variables and utility functions.
# Expression task configuration
class ExprTask:
expr: str # Python expression to evaluate
env: dict[str, str] # Environment variables
cwd: str # Working directory
help: str # Help description
deps: list[str] # Task dependencies[tool.poe.tasks]
# Simple expression
version = {expr = "open('VERSION').read().strip()"}
# Expression with imports
timestamp = {expr = "__import__('datetime').datetime.now().isoformat()"}
# Expression using environment
debug_info = {
expr = "f'Debug mode: {environ.get(\"DEBUG\", \"false\")}'",
env = {DEBUG = "true"}
}Compose multiple tasks into sequential execution chains with dependency management.
# Sequence task configuration
class SequenceTask:
sequence: list[str | dict] # List of tasks to execute
env: dict[str, str] # Environment variables
cwd: str # Working directory
help: str # Help description
deps: list[str] # Task dependencies
ignore_fail: bool # Continue on task failure[tool.poe.tasks]
# Simple sequence
check = {sequence = ["format", "lint", "test"]}
# Sequence with inline tasks
build = {sequence = [
"clean",
{cmd = "python setup.py build"},
{script = "scripts:package"}
]}
# Sequence with failure handling
deploy = {
sequence = ["test", "build", "upload"],
ignore_fail = false
}
# Sequence with environment
full_test = {
sequence = ["unit_test", "integration_test"],
env = {TEST_MODE = "comprehensive"}
}Create task aliases and references for code reuse and organization.
# Reference task configuration
class RefTask:
ref: str # Reference to another task
args: list[str] # Additional arguments
env: dict[str, str] # Environment variables
help: str # Help description[tool.poe.tasks]
# Task reference/alias
t = {ref = "test"}
quick_test = {ref = "test", args = ["-x"]}
# Reference with environment override
test_debug = {
ref = "test",
env = {DEBUG = "true", VERBOSE = "1"}
}Conditional task execution based on the output of a control task, with different cases executed based on the control task's result.
# Switch task configuration
class SwitchTask:
control: str | dict # Control task definition
switch: list[dict] # List of case definitions
default: Literal["pass", "fail"] = "fail" # Default behavior when no case matches
env: dict[str, str] # Environment variables
cwd: str # Working directory
help: str # Help description
deps: list[str] # Task dependencies[tool.poe.tasks]
# Platform-based switching using control task
build = {
control = {expr = "sys.platform"},
switch = [
{case = "win32", cmd = "build_windows.bat"},
{case = "darwin", cmd = "./build_macos.sh"},
{case = "linux", cmd = "./build_linux.sh"},
{cmd = "echo 'Unsupported platform'"} # default case
]
}
# Environment-based switching
test = {
control = {expr = "environ.get('TEST_MODE', 'default')"},
switch = [
{case = "ci", cmd = "pytest --ci-mode"},
{case = "debug", cmd = "pytest -v -s"},
{cmd = "pytest"} # default case
]
}Common configuration options available across all task types.
# Base task options (available to all task types)
class TaskOptions:
help: str # Help text description
env: dict[str, str] # Environment variables
cwd: str # Working directory
deps: list[str] # Task dependencies
args: dict | list | None # Additional arguments
capture_stdout: str | None # Capture standard output to variable
envfile: str | list[str] # Environment file(s) to load
executor: dict # Executor configuration
uses: dict[str, str] # Task dependencies with outputs
verbosity: int | None # Verbosity level (-2 to 2)
# Task-specific options
class CmdTaskOptions(TaskOptions):
use_exec: bool = False # Use exec instead of subprocess
empty_glob: str = "pass" # Handle empty glob patterns ("pass", "null", "fail")
class ShellTaskOptions(TaskOptions):
interpreter: str | list[str] # Shell interpreter override
class ScriptTaskOptions(TaskOptions):
use_exec: bool = False # Use exec instead of subprocess
print_result: bool = False # Print function return value
class ExprTaskOptions(TaskOptions):
imports: list[str] = [] # Additional imports for expression
assert_: bool | int = False # Assert expression result
use_exec: bool = False # Use exec instead of subprocess
class SequenceTaskOptions(TaskOptions):
ignore_fail: str | bool = False # Continue on failure ("return_zero", "return_non_zero", True, False)
default_item_type: str # Default task type for sequence items
class SwitchTaskOptions(TaskOptions):
control: str | dict # Switch control expression/mapping
default: str = "fail" # Default behavior ("pass", "fail")Tasks can define and use environment variables in multiple ways.
# Environment variable handling
class EnvOptions:
# Task-level environment variables
env: dict[str, str]
# Global environment from .env file
env_file: str
# Environment variable interpolation in task definitions
# Supports: ${VAR}, $VAR, environ.get('VAR')[tool.poe.tasks]
# Task with environment variables
deploy = {
cmd = "deploy.sh",
env = {
API_URL = "https://api.example.com",
API_KEY = "${API_KEY}", # From shell environment
DEBUG = "false"
}
}
# Global environment file
[tool.poe]
env_file = ".env"
# Environment variable in task definition
backup = {cmd = "backup.sh ${BACKUP_TARGET}"}Tasks can accept and process command-line arguments in various ways.
# Argument handling options
class ArgOptions:
args: list[str] # Predefined arguments
# CLI arguments are passed through to tasks automatically
# Access via: ${@}, ${1}, ${2}, etc. in shell tasks
# Access via: sys.argv in script tasks[tool.poe.tasks]
# Task with predefined arguments
test = {cmd = "pytest", args = ["--verbose", "--color=yes"]}
# Task accepting CLI arguments
serve = "python -m http.server" # CLI args passed through
# Usage: poe serve 8080
# Script task with argument handling
process = {script = "scripts:process_data"}
# Usage: poe process input.json --format csvTasks can declare dependencies that must complete successfully before execution.
# Dependency configuration
class DependencyOptions:
deps: list[str] # Task dependencies
# Dependencies run in parallel when possible
# Cyclic dependencies are detected and reported[tool.poe.tasks]
# Task with dependencies
deploy = {
cmd = "deploy.sh",
deps = ["test", "build"]
}
# Complex dependency chain
package = {
cmd = "package.sh",
deps = ["build"]
}
build = {
cmd = "build.sh",
deps = ["clean", "compile"]
}
test = {deps = ["lint", "unit_test", "integration_test"]}Tasks can inherit configuration from parent tasks, including working directory, verbosity level, and other options.
# Task inheritance context
class TaskContext:
config: PoeConfig # Configuration object
cwd: str # Working directory (inherited)
io: PoeIO # IO handler (inherited)
ui: PoeUi # UI handler (inherited)
specs: TaskSpecFactory # Task specifications
verbosity: int # Verbosity level (inherited)[tool.poe.tasks]
# Parent task with configuration
parent = {
cmd = "echo 'Running parent'",
cwd = "src/",
verbosity = 1,
env = {PROJECT = "myapp"}
}
# Child tasks inherit parent context
child_sequence = {sequence = [
"parent", # Child inherits cwd=src/, verbosity=1, env
{cmd = "echo 'Running child'"} # Runs in inherited context
]}[tool.poe.tasks]
# Command tasks
test = "pytest"
lint = "flake8 src/"
format = "black ."
# Script tasks
build = {script = "build:main"}
docs = {script = "sphinx.cmd.build:build_main"}[tool.poe.tasks]
# Development workflow
dev = {sequence = [
"format",
"lint",
"test",
{cmd = "python -m myapp", env = {DEBUG = "true"}}
]}
# CI/CD pipeline
ci = {sequence = [
"clean",
"deps",
"lint",
"test",
"build",
"package"
]}
# Platform-specific builds
build = {
switch = {
"sys.platform == 'win32'" = {cmd = "build.bat"},
"sys.platform != 'win32'" = {cmd = "./build.sh"}
}
}[tool.poe.tasks]
# Development vs production
deploy = {
switch = {
"environ.get('ENVIRONMENT') == 'prod'" = {
cmd = "deploy-prod.sh",
env = {API_URL = "https://api.prod.com"}
}
},
default = {
cmd = "deploy-dev.sh",
env = {API_URL = "https://api.dev.com"}
}
}
# Debug mode tasks
test_debug = {
ref = "test",
env = {DEBUG = "1", VERBOSE = "1"}
}Install with Tessl CLI
npx tessl i tessl/pypi-poethepoet