Building Web APIs with Huey: A Comprehensive Guide
What is Huey?
Huey is a lightweight, multi-threaded task queue for Python. It is designed to be simple to set up and use, yet powerful enough for production workloads. Huey allows you to execute tasks asynchronously (in the background) and supports task scheduling, periodic tasks, retries, and result storage. It uses a lightweight backend (by default Redis) to store task metadata and results.
Unlike heavier task queues like Celery, Huey has a minimal footprint and a single-file core, making it ideal for small to medium-sized projects, microservices, and Web APIs where you need to offload time-consuming operations without introducing complex infrastructure.
Why Use Huey for Web APIs?
Web APIs often need to handle requests that involve slow or blocking operations: sending emails, processing uploaded files, generating reports, calling external services, or performing heavy computations. If these operations run synchronously inside the request handler, the API response time increases, and the server can become unresponsive under load.
Huey solves this by letting you:
- Offload long-running tasks to background workers.
- Return an immediate response (e.g., "Task accepted") while the worker processes the job.
- Schedule tasks for future execution or on a recurring basis.
- Retry failed tasks automatically.
- Store and retrieve task results for polling or webhook callbacks.
Integrating Huey with a web framework like Flask, FastAPI, or Django is straightforward and keeps your API endpoints fast and responsive.
Setting Up Huey
First, install Huey and its Redis backend:
pip install huey[redis]
Huey can also work with filesystem or SQLite backends, but Redis is recommended for production.
Create a configuration module (e.g., huey_config.py) to define your Huey instance:
from huey import RedisHuey
# Redis connection details
huey = RedisHuey(
'my_app',
host='localhost',
port=6379,
db=0,
result_store=True, # store task results
events=True, # enable event broadcasting
store_none=False # don't store None results
)
This single huey object will be imported by both your web application and your worker process.
Integrating Huey with a Web API (Flask Example)
We'll build a simple Flask API that accepts a long-running task (simulating a report generation) and returns a task ID. The client can later poll for the result.
# tasks.py
from huey_config import huey
@huey.task()
def generate_report(user_id):
# Simulate a time-consuming operation
import time
time.sleep(10)
return f"Report for user {user_id} generated at {time.time()}"
# app.py
from flask import Flask, request, jsonify
from huey_config import huey
from tasks import generate_report
app = Flask(__name__)
@app.route('/report', methods=['POST'])
def create_report():
user_id = request.json.get('user_id')
if not user_id:
return jsonify({'error': 'user_id required'}), 400
# Enqueue the task – returns immediately
task = generate_report(user_id)
return jsonify({'task_id': task.id, 'status': 'queued'}), 202
@app.route('/report/<task_id>', methods=['GET'])
def get_report(task_id):
# Retrieve the task result from Huey's result store
task = huey.get_task(task_id)
if task is None:
return jsonify({'error': 'task not found'}), 404
if task.result is not None:
return jsonify({'status': 'completed', 'result': task.result})
elif task.error:
return jsonify({'status': 'failed', 'error': str(task.error)})
else:
return jsonify({'status': 'pending'}), 200
if __name__ == '__main__':
app.run(debug=True)
Running Tasks Asynchronously
To process the enqueued tasks, you need to run the Huey consumer (worker). Open a terminal and start the worker:
python -m huey.consumer tasks.huey -w 2 -k process
Explanation:
tasks.huey– the module path to yourhueyinstance (e.g., intasks.pywe importhueyfromhuey_config; the consumer needs to import the same instance).-w 2– number of worker threads/processes.-k process– use multiprocessing workers (default is threads).
The worker will listen for new tasks, execute them, and store results. You can also run the consumer in the background using a process manager like Supervisor or systemd.
Task Scheduling and Periodic Tasks
Huey supports @huey.periodic_task and @huey.scheduled_task decorators for recurring or one-off future tasks.
from huey_config import huey
from datetime import timedelta
# Run every 30 minutes
@huey.periodic_task(crontab(minute='*/30'))
def clean_expired_sessions():
print("Cleaning expired sessions...")
# Your cleanup logic here
# Run once after 5 seconds
@huey.task()
def send_welcome_email(user_email):
print(f"Sending welcome email to {user_email}")
# In your API, schedule a future task
def schedule_welcome(user_email):
send_welcome_email.schedule(args=(user_email,), delay=timedelta(seconds=5))
To enable periodic tasks, you must run the consumer with the --periodic flag:
python -m huey.consumer tasks.huey -w 2 -k process --periodic
Error Handling and Retries
Huey provides built-in retry support. Use the @huey.task(retries=3, retry_delay=10) decorator to automatically retry failed tasks.
@huey.task(retries=3, retry_delay=10)
def send_email(to, subject, body):
# If this raises an exception, Huey will retry up to 3 times
# with a 10-second delay between attempts.
mailer.send(to, subject, body)
# In the API, you can also mark a task as failed manually
@huey.task()
def fragile_operation(data):
try:
# risky code
pass
except Exception as e:
# Log and re-raise to trigger retry
huey.logger.error(f"Task failed: {e}")
raise
Best Practices
- Keep tasks idempotent: Design task functions so that running them multiple times produces the same result (or is safe). This is crucial for retries.
- Use a dedicated Redis instance or database: Separate the Huey queue from your application's primary database to avoid contention.
- Monitor task queues: Use Huey's built-in signals or a monitoring tool (e.g., Huey's web UI via
huey.contrib.djhueyfor Django) to track task failures and queue depth. - Set reasonable timeouts: Use
@huey.task(timeout=60)to prevent a single task from blocking the worker indefinitely. - Store only lightweight results: Avoid storing large binary objects as task results. Instead, save results to a file or database and return a reference.
- Separate task definitions from API code: Keep task functions in a separate module (e.g.,
tasks.py) to allow the consumer to import them cleanly. - Use environment variables: Configure Redis connection parameters via environment variables instead of hardcoding.
- Graceful shutdown: In production, ensure the worker process receives SIGTERM and finishes in-progress tasks before exiting (Huey handles this by default).
- Test tasks locally: Use
huey.immediate=Truein your test configuration to run tasks synchronously without needing a worker.
Conclusion
Huey provides a clean, minimalistic approach to adding asynchronous task processing to your Web APIs. Its simplicity, built-in scheduling, retry mechanism, and result store make it an excellent choice for projects that need background processing without the overhead of larger task queues. By integrating Huey with frameworks like Flask or FastAPI, you can keep your API endpoints fast and responsive while offloading heavy work to dedicated workers. Follow the best practices outlined here to ensure your tasks are reliable, maintainable, and production-ready. Whether you're sending emails, processing uploads, or generating reports, Huey gives you the power to build scalable, asynchronous Web APIs with minimal friction.