Imagine having a smart assistant for your Python project. You tell them "run tests" and they know exactly what to do, every single time.
No remembering long commands. No typing the same thing repeatedly. No confusion about which virtual environment to use.
What is Poe the Poet? The Simple Answer 🎯
Poe the Poet is a task runner for Python projects.
Think of it as saving shortcuts on your phone. Instead of typing "Hey Google, play my workout playlist on Spotify," you just say "start workout" and it does everything automatically.
Poe lets you save common project tasks (testing, linting, building) as simple shortcuts that run with one command.
Without Poe the Poet: "Computer, open terminal, activate virtual environment, run pytest with coverage flags, then run black formatter with specific settings..."
With Poe the Poet: "poe test" → Everything runs automatically! ✨
Why Do We Need Poe? The Problem It Solves ?
The Manual Chaos (Before Poe)
Imagine you're working on a Python web application. Every day, you need to run these commands:
# Activate virtual environment source venv/bin/activate # or: poetry shell # Run tests with coverage pytest tests/ --cov=myapp --cov-report=html --verbose # Check code style black myapp/ --line-length=88 --check # Lint for errors pylint myapp/ --rcfile=.pylintrc # Check type hints mypy myapp/ --strict --ignore-missing-imports # Start development server python -m uvicorn myapp.main:app --reload --port 8000
Problems with this:
- Long commands that are hard to remember
- Easy to make typos
- Teammates might run things differently
- Forget to activate virtual environment
- Copy-paste from notes or chat history
- New team members confused about what to run
The Poe Way (Modern Approach)
Define tasks once in your pyproject.toml:
[tool.poe.tasks] test = "pytest tests/ --cov=myapp --cov-report=html --verbose" format = "black myapp/ --line-length=88" lint = "pylint myapp/ --rcfile=.pylintrc" typecheck = "mypy myapp/ --strict --ignore-missing-imports" serve = "python -m uvicorn myapp.main:app --reload --port 8000"
Now run them with simple commands:
poe test # Runs tests poe format # Formats code poe lint # Checks code quality poe typecheck # Validates types poe serve # Starts server
- Short, memorable commands
- Consistent across all team members
- Automatically uses correct virtual environment
- Self-documenting (tasks listed in config)
- Easy for newcomers to understand
Installing Poe the Poet 🛠️
Prerequisites
You need Python 3.10 or higher:
python --version # Should show: Python 3.10.x or higher
Installation Option 1: With pipx (Recommended)
Pipx installs Poe globally, making it available for all your projects:
# Install pipx if you don't have it python -m pip install pipx python -m pipx ensurepath # Install Poe the Poet globally pipx install poethepoet # Verify installation poe --version # Output: Poe the Poet - version 0.40.0
Installation Option 2: As Poetry Plugin
If you're using Poetry, you can install Poe as a plugin:
# Install as Poetry plugin poetry self add 'poethepoet[poetry-plugin]' # Now you can use 'poetry poe' command poetry poe --version
Installation Option 3: As Development Dependency
Install Poe as part of your project:
# With Poetry poetry add --group dev poethepoet # With uv uv add --dev poethepoet # With pip pip install poethepoet
- Use pipx: If you want Poe available everywhere (best for most people)
- Use Poetry plugin: If you exclusively use Poetry and want tight integration
- Use project dependency: If you want Poe version-locked with your project
Understanding Task Types 📚
Poe supports four types of tasks. Let's explore each with practical examples!
Type 1: Command Tasks (Most Common)
Simple shell commands, exactly as you'd type them:
[tool.poe.tasks] # Single command test = "pytest tests/" # Command with multiple options test-verbose = "pytest tests/ --verbose --cov=myapp" # Multiple tools can have separate tasks format = "black ." lint = "ruff check ."
Running command tasks:
poe test # Output: Poe => pytest tests/ # (Poe shows you what command it's running) poe test-verbose # Runs: pytest tests/ --verbose --cov=myapp
Type 2: Script Tasks (Python Functions)
Run Python functions directly from your code:
[tool.poe.tasks] # Format: "module.submodule:function_name(arguments)" serve.script = "myapp.server:run(debug=True)" seed-db.script = "myapp.database:seed()"
This runs the run() function from myapp/server.py:
# In myapp/server.py
def run(debug=False):
"""Start the development server"""
import uvicorn
uvicorn.run(
"myapp.main:app",
reload=debug,
port=8000
)
When to use script tasks:
- Need to run Python code directly
- Want to pass complex arguments
- Need programmatic control
Type 3: Shell Tasks (Multi-Command Scripts)
Run multiple commands in sequence:
[tool.poe.tasks.setup]
shell = """
echo "Setting up project..."
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
echo "Setup complete!"
"""
[tool.poe.tasks.deploy]
shell = """
echo "Building application..."
docker build -t myapp .
docker push myapp:latest
kubectl apply -f k8s/
"""
When to use shell tasks:
- Need to run multiple commands together
- Want to use shell features (pipes, redirects)
- Setting up complex environments
Shell tasks use your system's shell (bash/zsh on Mac/Linux, cmd/PowerShell on Windows). Commands might not work cross-platform!
For cross-platform projects, prefer command tasks or script tasks.
Type 4: Sequence Tasks (Running Multiple Tasks)
Chain multiple Poe tasks together:
[tool.poe.tasks] # Define individual tasks format = "black ." lint = "ruff check ." typecheck = "mypy src/" test = "pytest tests/" # Combine them into a quality check check.sequence = ["format", "lint", "typecheck", "test"] check.ignore_fail = "return_non_zero" # Continue even if one fails
Now run all checks with one command:
poe check # Runs: format, then lint, then typecheck, then test # Shows progress for each step
Sequence task options:
ignore_fail = "return_non_zero"- Continue if task failsignore_fail = "return_zero"- Stop on first failure (default)
Creating Your First Poe Project 🎨
Let's build a complete example from scratch!
Step 1: Create Project Structure
# Create project directory mkdir weather-cli cd weather-cli # Create basic structure mkdir src tests touch src/weather.py touch tests/test_weather.py touch pyproject.toml
Step 2: Setup pyproject.toml
[project]
name = "weather-cli"
version = "0.1.0"
requires-python = ">=3.10"
[project.dependencies]
requests = ">=2.31.0"
rich = ">=13.0.0"
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.0.0",
"black>=24.0.0",
"ruff>=0.3.0"
]
[tool.poe.tasks]
# Simple command tasks
test = "pytest tests/ --verbose"
test-cov = "pytest tests/ --cov=src --cov-report=html"
format = "black src/ tests/"
format-check = "black src/ tests/ --check"
lint = "ruff check src/ tests/"
# Script task to run the app
run.script = "src.weather:main()"
# Shell task for setup
setup.shell = """
echo "Installing dependencies..."
pip install -e .[dev]
echo "Setup complete! Run 'poe test' to verify."
"""
# Sequence task for CI
ci.sequence = ["format-check", "lint", "test-cov"]
ci.ignore_fail = "return_non_zero"
Step 3: Create the Application
In src/weather.py:
import requests
from rich.console import Console
console = Console()
def get_weather(city: str) -> dict:
"""Fetch weather for a city"""
url = f"https://wttr.in/{city}?format=j1"
response = requests.get(url)
return response.json()
def main():
"""Main CLI entry point"""
console.print("[bold blue]Weather CLI[/bold blue]")
city = input("Enter city name: ")
with console.status(f"Fetching weather for {city}..."):
weather = get_weather(city)
temp = weather["current_condition"][0]["temp_C"]
desc = weather["current_condition"][0]["weatherDesc"][0]["value"]
console.print(f"\n[green]Temperature:[/green] {temp}°C")
console.print(f"[green]Conditions:[/green] {desc}")
if __name__ == "__main__":
main()
Step 4: Create Tests
In tests/test_weather.py:
from src.weather import get_weather
def test_get_weather():
"""Test weather fetching"""
result = get_weather("London")
assert "current_condition" in result
assert len(result["current_condition"]) > 0
Step 5: Use Your Tasks!
# Install everything poe setup # Run the application poe run # Run tests poe test # Format code poe format # Run all CI checks poe ci
Perfect! You now have a fully automated project workflow! 🎉
Advanced Task Features 🚀
Adding Arguments to Tasks
Make tasks accept dynamic input:
[tool.poe.tasks.test]
cmd = "pytest ${path} --verbose"
args = [
{name = "path", default = "tests/", positional = true}
]
[tool.poe.tasks.serve]
cmd = "uvicorn myapp.main:app --port ${port}"
args = [
{name = "port", default = "8000", options = ["--port", "-p"]}
]
Using tasks with arguments:
# Use default path poe test # Runs: pytest tests/ --verbose # Specify custom path poe test tests/unit # Runs: pytest tests/unit --verbose # Use default port poe serve # Runs: uvicorn myapp.main:app --port 8000 # Custom port poe serve --port 3000 # Runs: uvicorn myapp.main:app --port 3000
Task Help Documentation
Add helpful descriptions to your tasks:
[tool.poe.tasks.test]
help = "Run the test suite with coverage reporting"
cmd = "pytest tests/ --cov=src"
[tool.poe.tasks.deploy]
help = "Deploy application to production"
shell = """
docker build -t myapp .
kubectl apply -f k8s/
"""
View all available tasks:
poe --help # Output: CONFIGURED TASKS test Run the test suite with coverage reporting deploy Deploy application to production
Environment Variables
Set environment variables for tasks:
[tool.poe.tasks.test]
cmd = "pytest tests/"
env = {ENVIRONMENT = "test", DEBUG = "true"}
[tool.poe.tasks.serve-prod]
cmd = "uvicorn myapp.main:app"
env = {ENVIRONMENT = "production", DEBUG = "false"}
Load from .env files:
[tool.poe.tasks.serve] cmd = "uvicorn myapp.main:app" envfile = ".env" # Load from .env file
Working Directory Control
Run tasks from specific directories:
[tool.poe.tasks.frontend-build] cmd = "npm run build" cwd = "frontend/" # Run from frontend directory [tool.poe.tasks.docs] cmd = "mkdocs serve" cwd = "docs/" # Run from docs directory
Task Dependencies
Run prerequisite tasks automatically:
[tool.poe.tasks] install = "pip install -e .[dev]" migrate = "python manage.py migrate" # Run install and migrate before starting server [tool.poe.tasks.serve] cmd = "uvicorn myapp.main:app --reload" deps = ["install", "migrate"]
When you run poe serve, it automatically runs
install and migrate first!
Integration with Poetry and uv 🔗
Using Poe with Poetry
Poe automatically detects Poetry projects and uses the Poetry virtual environment:
# Your pyproject.toml (Poetry project) [tool.poetry.dependencies] python = "^3.10" requests = "^2.31.0" [tool.poetry.group.dev.dependencies] pytest = "^8.0.0" [tool.poe.tasks] test = "pytest tests/" # Running the task # Poe automatically uses Poetry's virtualenv! poe test # No need for: poetry run pytest tests/
Using Poe with uv
Similar seamless integration with uv:
# Install Poe as dev dependency
uv add --dev poethepoet
# Define tasks in pyproject.toml
[tool.poe.tasks]
test = "pytest tests/"
serve = "uvicorn app.main:app --reload"
# Run tasks
uv run poe test
uv run poe serve
# Or configure Poe to use uv explicitly
[tool.poe]
executor = {type = "uv"}
Standalone (No Poetry/uv)
Poe works without Poetry or uv too:
# Use system Python or standard venv
[tool.poe]
executor = {type = "simple"}
# Or specify virtual environment location
[tool.poe]
executor = {type = "virtualenv", location = ".venv"}
Task Organization Best Practices 📋
Organizing Complex Task Files
For large projects, split tasks into separate file:
# Create poe_tasks.toml in project root [tool.poe.tasks] test = "pytest tests/" serve = "uvicorn app.main:app" # ... all your tasks here # In pyproject.toml, just reference it: [tool.poe] include = "poe_tasks.toml"
Naming Conventions
test- Run teststest-unit- Run unit tests specificallytest-integration- Run integration testsserve- Start development serverserve-prod- Start production serverdb-migrate- Run database migrationsdocker-build- Build Docker image
runTests- Use kebab-case, not camelCaset- Too short, not descriptivetest_everything_including_integration- Too longdo-the-thing- Vague, unclear purpose
Task Categories
Group related tasks with prefixes:
[tool.poe.tasks] # Testing test = "pytest tests/" test-unit = "pytest tests/unit" test-integration = "pytest tests/integration" test-cov = "pytest --cov=src" # Database db-migrate = "alembic upgrade head" db-rollback = "alembic downgrade -1" db-reset = "alembic downgrade base && alembic upgrade head" # Docker docker-build = "docker build -t myapp ." docker-run = "docker run -p 8000:8000 myapp" docker-clean = "docker system prune -f" # Documentation docs-build = "mkdocs build" docs-serve = "mkdocs serve" docs-deploy = "mkdocs gh-deploy"
Troubleshooting Common Issues 🔧
Issue 1: "poe: command not found"
Problem: Poe not in PATH after installation
# Solution 1: Ensure pipx path is in PATH pipx ensurepath # Then restart terminal # Solution 2: Use poetry run poetry run poe --version # Solution 3: Use uv run uv run poe --version # Solution 4: Check installation pipx list # Poe should appear in the list
Issue 2: Wrong Virtual Environment
Problem: Poe using wrong Python environment
# Check which environment Poe is using
poe _env
# Specify environment explicitly in pyproject.toml
[tool.poe]
executor = {type = "poetry"} # Use Poetry's virtualenv
# Or for uv
[tool.poe]
executor = {type = "uv"}
# Or specify virtualenv path
[tool.poe]
executor = {type = "virtualenv", location = ".venv"}
Issue 3: Task Not Running
# Verify task exists poe --help # Lists all available tasks # Check task definition poe _show test # Shows the actual command that will run # Run with verbose mode poe --verbose test # Shows detailed execution info # Check for typos in pyproject.toml # Make sure [tool.poe.tasks] section exists
Issue 4: Arguments Not Working
# Wrong format - won't accept arguments
[tool.poe.tasks]
test = "pytest ${path}" # ❌ Missing args definition
# Correct format
[tool.poe.tasks.test]
cmd = "pytest ${path}"
args = [{name = "path", default = "tests/"}] # ✅
Poe vs Other Task Runners ⚖️
Poe vs Make
| Feature | Make | Poe the Poet |
|---|---|---|
| Config File | Makefile (separate) | pyproject.toml (integrated) |
| Syntax | Tab-sensitive, cryptic | TOML, beginner-friendly |
| Python Integration | None (shell only) | Native Python support |
| Virtual Env Handling | Manual | Automatic |
| Cross-Platform | Limited (needs GNU Make) | Full (Python everywhere) |
Poe vs npm scripts
| Feature | npm scripts | Poe the Poet |
|---|---|---|
| Ecosystem | JavaScript/Node.js | Python |
| Task Types | Shell commands only | Commands, scripts, shell, sequences |
| Arguments | Manual parsing needed | Built-in arg handling |
| Environment | Node modules | Python virtualenv |
- Native Python integration (run functions directly)
- Automatic virtualenv detection (Poetry, uv, venv)
- Clean TOML syntax (easier than Make)
- Cross-platform by default
- Integrated with modern Python tools
Quick Reference Cheat Sheet 📝
# INSTALLATION
pipx install poethepoet # Install globally
poetry self add 'poethepoet[poetry-plugin]' # As Poetry plugin
poetry add --group dev poethepoet # As project dependency
uv add --dev poethepoet # With uv
# RUNNING TASKS
poe task-name # Run a task
poe task-name arg1 arg2 # With arguments
poe task-name --option value # With options
poe --help # List all tasks
poe _show task-name # Show task details
poe _env # Show environment info
# BASIC TASK TYPES
[tool.poe.tasks]
simple = "pytest tests/" # Command task
script.script = "module:function()" # Script task
shell.shell = "echo hello && ls" # Shell task
sequence.sequence = ["task1", "task2"] # Sequence task
# TASK WITH ARGUMENTS
[tool.poe.tasks.test]
cmd = "pytest ${path}"
args = [{name = "path", default = "tests/"}]
# TASK WITH OPTIONS
[tool.poe.tasks.serve]
cmd = "uvicorn app:main --port ${port}"
args = [
{name = "port", options = ["--port", "-p"], default = "8000"}
]
# TASK WITH HELP
[tool.poe.tasks.deploy]
help = "Deploy to production"
cmd = "docker build -t app ."
# TASK WITH ENVIRONMENT
[tool.poe.tasks.test]
cmd = "pytest tests/"
env = {DEBUG = "true", ENV = "test"}
envfile = ".env"
# TASK WITH DEPENDENCIES
[tool.poe.tasks.serve]
cmd = "uvicorn app:main"
deps = ["install", "migrate"] # Run these first
# EXECUTOR CONFIGURATION
[tool.poe]
executor = {type = "poetry"} # Use Poetry
executor = {type = "uv"} # Use uv
executor = {type = "simple"} # Simple executor
executor = {type = "virtualenv", location = ".venv"}
Best Practices Checklist ✅
- Keep tasks simple: One clear purpose per task
- Use descriptive names: test, serve, deploy (not t, s, d)
- Add help text: Especially for complex tasks
- Group related tasks: test-unit, test-integration
- Define sequence tasks: For common workflows (ci, pre-commit)
- Document arguments: Make options clear and discoverable
- Use task dependencies: Ensure prerequisites run automatically
- Don't make tasks too long: Break complex tasks into smaller ones
- Don't use platform-specific commands: Unless necessary (prefer Python)
- Don't hardcode secrets: Use environment variables or .env files
- Don't duplicate task logic: Use sequence or dependencies instead
- Don't forget error handling: Use ignore_fail for sequences when appropriate
- Use shell completion: Run
poe _bash_completionorpoe _zsh_completion - Create a 'check' task: Run all quality checks before committing
- Use 'dev' task: Start everything needed for development
- Document in README: List common tasks for new contributors
- Use verbose mode: Add
--verbosewhen debugging
Summary - Your Poe Journey 🎓
Congratulations! You've learned Poe the Poet from scratch!
What You Now Know:
- ✅ What Poe the Poet is and why it exists
- ✅ How to install Poe (multiple methods)
- ✅ Four types of tasks (command, script, shell, sequence)
- ✅ How to add arguments and options to tasks
- ✅ Working with Poetry and uv integration
- ✅ Real-world project examples
- ✅ Best practices and common patterns
- ✅ Troubleshooting common issues
Your Next Steps:
- Install Poe using pipx
- Add basic tasks to your current project
- Create a 'dev' task that starts everything you need
- Add a 'ci' sequence task for automated checks
- Share your pyproject.toml with your team
- Explore advanced features as you need them
Additional Resources 📚
Official Documentation:
- Poe the Poet Docs: poethepoet.natn.io
- GitHub Repository: github.com/nat-n/poethepoet
- PyPI Package: pypi.org/project/poethepoet
Community:
- GitHub Discussions for questions and ideas
- GitHub Issues for bug reports
- Python packaging community resources
Related Tools:
- Poetry: python-poetry.org
- uv: github.com/astral-sh/uv
- pytest, black, ruff (common task tools)
Final Thoughts 💭
Poe the Poet transforms your Python project from a collection of remembered commands into a well-documented, automated workflow that anyone can understand and use.
Whether you're working solo or with a team of 50, Poe ensures everyone runs tasks the same way, every time.
The best part? It's simple to start with basic tasks, and grows with your project as your needs become more complex.
Start small today. Add one task. Then another. Before you know it, your entire workflow is automated, and you'll wonder how you ever lived without Poe! 🎭
Happy task running! ✨
Comments
Post a Comment