Tutorial: Packaging an HTTPie Wrapper¶
Time to Complete: 20-25 minutes Difficulty: Intermediate Prerequisites: Python 3.11+, FlavorPack installed
This tutorial demonstrates how to package a Python CLI application that wraps HTTPie (a popular HTTP client) into a self-contained executable using FlavorPack.
What You'll Build¶
A custom HTTP client tool called api-tool that:
- Wraps HTTPie with custom defaults and shortcuts
- Includes colored output and JSON formatting
- Can be distributed as a single executable
- Works without requiring Python or pip on target systems
- Includes environment-specific API configurations
Why This Example?¶
This tutorial demonstrates several important FlavorPack capabilities:
- Third-party dependencies: Packaging external libraries (httpie)
- CLI wrappers: Building tools around existing utilities
- Environment configuration: Managing API endpoints per environment
- Cross-platform distribution: Single file that works anywhere
Step 1: Project Setup¶
Create a new directory for your project:
Your project structure will be:
Step 2: Create the Application¶
Create the main CLI application:
src/api_tool/__init__.py:
src/api_tool/cli.py:
"""
API Tool CLI - A wrapper around HTTPie with environment presets
"""
import os
import sys
import json
from pathlib import Path
import click
from httpie.cli.definition import parser
from httpie.core import main as httpie_main
from httpie.status import ExitStatus
# Environment configurations
ENVIRONMENTS = {
"dev": {
"base_url": "https://api.dev.example.com",
"timeout": "30",
"verify": "no", # Skip SSL verification in dev
},
"staging": {
"base_url": "https://api.staging.example.com",
"timeout": "30",
"verify": "yes",
},
"prod": {
"base_url": "https://api.example.com",
"timeout": "60",
"verify": "yes",
},
}
def get_config_dir():
"""Get config directory for API tool"""
config_dir = Path.home() / ".api-tool"
config_dir.mkdir(exist_ok=True)
return config_dir
def get_current_env():
"""Get currently configured environment"""
env_file = get_config_dir() / "current_env"
if env_file.exists():
return env_file.read_text().strip()
return "dev" # Default
def set_current_env(env):
"""Set current environment"""
if env not in ENVIRONMENTS:
click.echo(f"❌ Invalid environment: {env}", err=True)
click.echo(f"Valid environments: {', '.join(ENVIRONMENTS.keys())}", err=True)
sys.exit(1)
env_file = get_config_dir() / "current_env"
env_file.write_text(env)
click.echo(f"✅ Environment set to: {env}")
@click.group()
@click.version_option(version="1.0.0", prog_name="api-tool")
def cli():
"""
API Tool - HTTPie wrapper with environment management
A convenience wrapper around HTTPie that manages multiple API environments
and provides shortcuts for common operations.
"""
pass
@cli.command()
def env_list():
"""List available environments"""
current = get_current_env()
click.echo("\nAvailable environments:\n")
for name, config in ENVIRONMENTS.items():
marker = "👉" if name == current else " "
click.echo(f"{marker} {name:8} - {config['base_url']}")
click.echo(f"\nCurrent: {current}")
@cli.command()
@click.argument("environment", type=click.Choice(list(ENVIRONMENTS.keys())))
def env_set(environment):
"""Set active environment (dev, staging, prod)"""
set_current_env(environment)
@cli.command()
def env_show():
"""Show current environment configuration"""
env = get_current_env()
config = ENVIRONMENTS[env]
click.echo(f"\nCurrent environment: {env}\n")
for key, value in config.items():
click.echo(f" {key:12}: {value}")
click.echo()
@cli.command(context_settings={"ignore_unknown_options": True})
@click.argument("path")
@click.argument("httpie_args", nargs=-1, type=click.UNPROCESSED)
def get(path, httpie_args):
"""
GET request to API endpoint
Examples:
api-tool get /users
api-tool get /users id==123
api-tool get /users Authorization:"Bearer token"
"""
_make_request("GET", path, httpie_args)
@cli.command(context_settings={"ignore_unknown_options": True})
@click.argument("path")
@click.argument("httpie_args", nargs=-1, type=click.UNPROCESSED)
def post(path, httpie_args):
"""
POST request to API endpoint
Examples:
api-tool post /users name=John [email protected]
api-tool post /users < data.json
"""
_make_request("POST", path, httpie_args)
@cli.command(context_settings={"ignore_unknown_options": True})
@click.argument("path")
@click.argument("httpie_args", nargs=-1, type=click.UNPROCESSED)
def put(path, httpie_args):
"""
PUT request to API endpoint
Examples:
api-tool put /users/123 name=Jane
"""
_make_request("PUT", path, httpie_args)
@cli.command(context_settings={"ignore_unknown_options": True})
@click.argument("path")
@click.argument("httpie_args", nargs=-1, type=click.UNPROCESSED)
def delete(path, httpie_args):
"""
DELETE request to API endpoint
Examples:
api-tool delete /users/123
"""
_make_request("DELETE", path, httpie_args)
def _make_request(method, path, httpie_args):
"""Make HTTP request using httpie with environment configuration"""
env = get_current_env()
config = ENVIRONMENTS[env]
# Build full URL
base_url = config["base_url"].rstrip("/")
if not path.startswith("/"):
path = "/" + path
url = f"{base_url}{path}"
# Build httpie arguments
args = [method, url]
args.extend(httpie_args)
# Add environment-specific options
args.extend([
f"--timeout={config['timeout']}",
"--print=HhBb", # Print everything (headers, body)
"--pretty=all", # Pretty print JSON
"--style=monokai", # Color scheme
])
if config["verify"] == "no":
args.append("--verify=no")
# Execute httpie
try:
exit_status = httpie_main(args)
sys.exit(exit_status.value if isinstance(exit_status, ExitStatus) else exit_status)
except KeyboardInterrupt:
sys.exit(130)
except Exception as e:
click.echo(f"❌ Error: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option("--method", "-X", default="GET", help="HTTP method")
@click.option("--header", "-H", multiple=True, help="Custom header (can be used multiple times)")
@click.option("--data", "-d", help="Request data (JSON)")
@click.argument("path")
def raw(method, header, data, path):
"""
Raw HTTP request with custom options
Examples:
api-tool raw /users
api-tool raw --method POST --data '{"name":"John"}' /users
api-tool raw -X GET -H "X-Custom:value" /users
"""
args = []
for h in header:
args.append(h)
if data:
args.append(data)
_make_request(method, path, tuple(args))
if __name__ == "__main__":
cli()
Step 3: Configure the Package¶
Create pyproject.toml with project configuration:
[project]
name = "api-tool"
version = "1.0.0"
description = "HTTPie wrapper with environment management"
requires-python = ">=3.11"
dependencies = [
"httpie>=3.2.0",
"click>=8.1.0",
]
[project.scripts]
api-tool = "api_tool.cli:cli"
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
api_tool = ["py.typed"]
# FlavorPack configuration
[tool.flavor]
type = "python-app"
entry_point = "api_tool.cli:cli"
[tool.flavor.execution]
command = "{workenv}/bin/api-tool"
[tool.flavor.execution.runtime]
# Python runtime configuration
python_version = "3.11"
[tool.flavor.execution.runtime.env]
# Clean environment, only pass essential variables
unset = ["*"]
pass = [
"PATH",
"HOME",
"USER",
"TERM",
"LANG",
"LC_*",
# Allow HTTPie to detect terminal capabilities
"COLUMNS",
"LINES",
]
[tool.flavor.metadata]
author = "Your Name"
license = "MIT"
keywords = ["http", "api", "cli", "httpie"]
Step 4: Build the Package¶
Now package your application with FlavorPack:
# Ensure FlavorPack helpers are built
make build-helpers # If not already built
# Package the application
flavor pack --manifest pyproject.toml --output api-tool.psp
Expected Output:
📦 FlavorPack Builder
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 Reading manifest: pyproject.toml
🔍 Package type: python-app
🐍 Python version: 3.11
🔧 Selecting helper binary
✓ Platform: darwin_arm64
✓ Helper: flavor-rs-builder-darwin_arm64
📦 Resolving dependencies
✓ httpie>=3.2.0
✓ click>=8.1.0
✓ Total packages: 12 (including transitive dependencies)
🏗️ Building package structure
✓ Slot 0: Python runtime (45.2 MB)
✓ Slot 1: Application code + dependencies (8.7 MB)
🔐 Signing package
✓ Algorithm: Ed25519
✓ Signature: verified
✅ Package created successfully!
📦 File: api-tool.psp
💾 Size: 53.9 MB
🔑 Signed: yes
Step 5: Test the Package¶
Test your packaged application:
# Make executable (if needed)
chmod +x api-tool.psp
# Show help
./api-tool.psp --help
# List environments
./api-tool.psp env-list
# Set environment
./api-tool.psp env-set dev
# Test API call (using a public API)
./api-tool.psp get https://api.github.com/users/octocat
Expected Output (example):
Available environments:
👉 dev - https://api.dev.example.com
staging - https://api.staging.example.com
prod - https://api.example.com
Current: dev
Step 6: Using Your Tool¶
Switching Environments¶
# Development environment (default)
./api-tool.psp env-set dev
# Staging environment
./api-tool.psp env-set staging
# Production environment
./api-tool.psp env-set prod
Making Requests¶
# GET request
./api-tool.psp get /users
# GET with query parameters
./api-tool.psp get /users id==123 status==active
# POST with JSON data
./api-tool.psp post /users name=John email=[email protected] age:=30
# POST with JSON file
./api-tool.psp post /users < user.json
# PUT request
./api-tool.psp put /users/123 name=Jane
# DELETE request
./api-tool.psp delete /users/123
# Custom headers
./api-tool.psp get /users Authorization:"Bearer eyJ..."
# Raw request with all options
./api-tool.psp raw --method POST --header "X-API-Key:secret" --data '{"test":true}' /endpoint
Step 7: Distribution¶
Your api-tool.psp file is now a self-contained executable that can be distributed:
# Copy to a bin directory
cp api-tool.psp ~/bin/api-tool
# Or install system-wide (requires sudo)
sudo cp api-tool.psp /usr/local/bin/api-tool
# Use from anywhere
api-tool env-list
api-tool get /users
Distribution Benefits¶
- ✅ Single file - No installation required
- ✅ No Python required - Includes Python runtime
- ✅ No pip required - All dependencies bundled
- ✅ Cross-platform - Build once per platform
- ✅ Reproducible - Same build everywhere
- ✅ Secure - Cryptographically signed
Understanding the Configuration¶
Environment Management¶
The tool includes three environments by default (dev, staging, prod). Each environment has:
ENVIRONMENTS = {
"dev": {
"base_url": "https://api.dev.example.com",
"timeout": "30",
"verify": "no", # Skip SSL in dev
},
# ...
}
Configuration is stored in ~/.api-tool/current_env so it persists across invocations.
HTTPie Integration¶
The wrapper passes arguments directly to HTTPie:
- Query params:
key==value - JSON fields:
key=value - JSON literals:
key:=123(numbers, booleans) - Headers:
Header:value - Authentication:
--auth username:password
See HTTPie documentation for complete syntax.
FlavorPack Configuration¶
Key aspects of the [tool.flavor] configuration:
[tool.flavor.execution.runtime.env]
unset = ["*"] # Clear all environment variables
pass = [ # Only pass these variables
"PATH",
"HOME",
"TERM",
# ...
]
This creates a clean, reproducible environment for the application.
Advanced Customization¶
Add Authentication¶
Modify _make_request() to add authentication:
def _make_request(method, path, httpie_args):
env = get_current_env()
config = ENVIRONMENTS[env]
# Load API token from config
token_file = get_config_dir() / f"{env}_token"
if token_file.exists():
token = token_file.read_text().strip()
# Add authentication header
auth_header = f"Authorization:Bearer {token}"
httpie_args = (auth_header,) + httpie_args
# ... rest of function
Add Response Caching¶
Add caching using requests-cache:
import requests_cache
# Initialize cache
cache_dir = get_config_dir() / "cache"
requests_cache.install_cache(
str(cache_dir / "api_cache"),
expire_after=300 # 5 minutes
)
Add Configuration File¶
Support a config file for custom environments:
import yaml
def load_custom_environments():
"""Load custom environments from ~/.api-tool/config.yaml"""
config_file = get_config_dir() / "config.yaml"
if config_file.exists():
with open(config_file) as f:
custom = yaml.safe_load(f)
ENVIRONMENTS.update(custom.get("environments", {}))
Troubleshooting¶
Package Too Large¶
If the package size is too large (>100MB), you can:
- Use slim Python build (if available)
- Exclude unnecessary dependencies
- Use
--exclude-testsflag when packaging
SSL Verification Fails¶
If you get SSL errors in development:
Or set verify = "no" in the environment configuration.
Permission Denied¶
If you get permission errors:
HTTPie Not Found¶
If the package can't find HTTPie, ensure it's in your dependencies:
What You've Learned¶
Congratulations! You've successfully:
✅ Created a Python CLI application that wraps HTTPie ✅ Managed multiple API environments ✅ Packaged third-party dependencies (httpie, click) ✅ Built a self-contained executable with FlavorPack ✅ Distributed a tool without requiring Python installation ✅ Configured environment isolation and security
Next Steps¶
Extend the Tool¶
- Add response formatting - Pretty-print JSON, tables, etc.
- Add request history - Track all API calls
- Add test fixtures - Generate sample requests
- Add API documentation - Auto-generate docs from endpoints
Learn More¶
- FlavorPack API Reference - Complete packaging API
- Cookbook Examples - More packaging patterns
- Advanced Topics - Custom builders, performance
- HTTPie Documentation - Complete HTTPie reference
Complete Project Files¶
Directory Structure¶
api-tool/
├── src/
│ └── api_tool/
│ ├── __init__.py
│ └── cli.py
├── pyproject.toml
├── README.md
└── api-tool.psp (generated)
Testing Checklist¶
- Package builds without errors
- Help text displays correctly
- Environment switching works
- GET requests succeed
- POST requests succeed
- Authentication headers work
- Error handling displays messages
- Package size is reasonable (<100MB)
Tutorial Version: 1.0 Last Updated: October 30, 2025 FlavorPack Version: 0.2.0+ Python Version: 3.11+