Skip to content

Extending FastWorker

FastWorker is designed with clear extension points. You can customize serialization, add middleware, hook into task lifecycle events, or build custom backends.

Extension Points Overview

Extension Point How When to Use
Task Hooks (before/after) @task(before=..., after=...) Logging, metrics, auth context per task
Task Middleware task_registry.add_middleware(hook) Global pre/post processing for all tasks
Custom Serializer Implement TaskSerializer protocol Avro, MessagePack, custom binary formats
Event Bus Listener event_bus.subscribe("task.*", handler) Slack notifications, audit trails, webhooks
Custom NNG Pattern Subclass NNGPattern Custom protocol adapters
Result Backend Implement ResultBackend protocol Redis, S3, PostgreSQL result persistence
Worker Hooks Worker.on_task_start / on_task_end Custom instrumentation, resource tracking

Task Hooks

Attach per-task before/after hooks declaratively:

from fastworker import task

def log_start(task):
    print(f"Starting task {task.name} ({task.id})")

def record_metrics(task):
    print(f"Task {task.name} completed: {task.status}")

@task(before=log_start, after=record_metrics)
def process_order(order_id: int):
    ...

Hooks receive the Task object and can be sync or async.

Event Bus

Subscribe to lifecycle events for cross-cutting concerns:

from fastworker.utils.event_bus import event_bus

@event_bus.subscribe("task.failure")
async def on_task_failure(event):
    # event is a dict with: task_id, name, error, status
    await slack.notify(f"Task {event['task_id']} failed: {event['error']}")

@event_bus.subscribe("task.success")
async def on_task_success(event):
    await metrics.increment("tasks.completed")

@event_bus.subscribe("worker.inactive")
async def on_worker_down(event):
    await pagerduty.alert(f"Worker {event['worker_id']} is down")

Available events: - task.queued, task.started, task.success, task.failure, task.cancelled - worker.registered, worker.active, worker.inactive - task.retrying

Custom Serializer

Implement a custom serializer for non-JSON formats:

import msgpack
from fastworker.tasks.serializer import TaskSerializer

class MessagePackSerializer:
    @staticmethod
    def serialize(data: dict) -> bytes:
        return msgpack.packb(data)

    @staticmethod
    def deserialize(data: bytes) -> dict:
        return msgpack.unpackb(data)

Custom Result Backend

Persist task results beyond the in-memory cache:

import redis.asyncio as redis

class RedisResultBackend:
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis = redis.from_url(redis_url)

    async def store(self, task_id: str, result: dict, ttl: int = 3600):
        await self.redis.setex(task_id, ttl, json.dumps(result))

    async def get(self, task_id: str) -> dict | None:
        data = await self.redis.get(task_id)
        return json.loads(data) if data else None

NNG Pattern Subclass

For custom protocol adapters, subclass the pattern base:

from fastworker.patterns.nng_patterns import NNGPattern

class CustomPattern(NNGPattern):
    async def start(self):
        # Custom initialization
        ...

    async def send(self, data: bytes):
        # Custom send logic
        ...

    async def recv(self) -> bytes:
        # Custom receive logic
        ...

Complete Example: Slack Notifier Hook

from fastworker import task
from fastworker.utils.event_bus import event_bus
import httpx


async def notify_slack(event):
    task_id = event.get("task_id", "unknown")
    name = event.get("name", "unknown")
    error = event.get("error", "")
    await httpx.AsyncClient().post(
        "https://hooks.slack.com/services/...",
        json={"text": f"Task *{name}* ({task_id}) failed: {error}"},
    )

event_bus.subscribe("task.failure", notify_slack)

Place this in a module that's imported before the control plane starts.