Poethepoet (poe) is a task runner that allows you to define and run tasks in your Python project. It's similar to npm scripts for JavaScript projects, but designed specifically for Python.
Poethepoet provides:
- A simple way to define project tasks
- Integration with pyproject.toml
- Support for task dependencies and sequences
- Command-line arguments for tasks
- Environment variable configuration
- Cross-platform compatibility
Poethepoet is included as a development dependency:
# Install with other development dependencies
uv sync --devTo install it directly:
uv pip install poethepoetIn this project, Poethepoet is used to:
- Define common development tasks
- Run linters and formatters
- Execute tests
- Build documentation
- Generate code
- Provide a consistent interface for various tools
Poethepoet is configured in the pyproject.toml file under the [tool.poe.tasks] section:
[tool.poe.tasks]
flynt = "flynt --aggressive --fail-on-change --quiet src tests"
lint = { sequence = ["pyupgrade", "flynt", "pyright", "ruff", "ruff-format"] }
mkdocs = { cmd = "mkdocs build" }
mkdocs-serve = { cmd = "mkdocs serve" }
mkinit = { sequence = ["mkinit-src", "mkinit-tests"] }
mkinit-src = { cmd = "mkinit src --relative --lazy_loader_typed --black --recursive -w" }
mkinit-tests = { cmd = "mkinit tests --relative --lazy_loader_typed --black --recursive -w" }
pre = "pre-commit run --all-files --show-diff-on-failure --verbose"
pre-commit = "pre-commit run --all-files"
pyright = "pyright"
pyupgrade = "pyupgrade --py311-plus"
radon = "radon cc src --min C --total-average"
ruff = "ruff check"
ruff-format = "ruff format"
bandit = "bandit -c pyproject.toml -r src --exclude tests,.venv,.git"
interrogate = "interrogate -v src"
test-coverage = "pytest -xvs -n auto --cov=src --cov-report=xml --cov-fail-under=80 --ignore=tests"
vulture = "vulture src --min-confidence 80"
xenon = "xenon src"To run a Poethepoet task:
# Run via uv
uv run poe task-name
# Run directly (if poethepoet is installed globally or in the env)
poe task-name# Run linters
poe lint
# Run tests with coverage
poe test-coverage
# Build documentation
poe mkdocs
# Serve documentation locally
poe mkdocs-serve
# Run pre-commit hooks
poe pre-commit
# Generate __init__.py files
poe mkinitPoethepoet supports several types of tasks:
task-name = "command --with --options"task-name = { cmd = "command --with --options" }task-name = { sequence = ["task1", "task2", "task3"] }task-name = { script = "python_module:function_name" }task-name = { shell = "echo 'Running in a shell'" }You can define tasks that depend on other tasks:
lint = { sequence = ["pyupgrade", "flynt", "pyright", "ruff", "ruff-format"] }You can pass arguments to tasks:
poe ruff src/specific_module.pyYou can set environment variables for tasks:
task-name = { cmd = "command", env = { VAR1 = "value1", VAR2 = "value2" } }- Group related tasks: Use sequence tasks to group related operations.
- Use descriptive task names: Choose names that clearly indicate what the task does.
- Document tasks: Include comments in your pyproject.toml to explain complex tasks.
- Prefer poe over direct commands: Use poe tasks to provide a consistent interface.
- Use task dependencies: Break complex tasks into smaller, reusable tasks.
If you get a "task not found" error:
- Check the spelling of the task name
- Ensure the task is defined in pyproject.toml
- Make sure you're running the command from the project root
If a task fails with an error:
- Check the error message for details
- Run the command directly to see if it works outside of poe
- Check for environment or path issues
If arguments aren't being passed correctly:
- Try quoting the arguments
- Use
--to separate poe arguments from task arguments - Check if the task is defined to accept arguments