Skip to main content

Defining jobs

A job handler is a plain function registered with the @worker.job(...) decorator. The decorator returns the function unchanged, so your handlers stay ordinary, unit-testable functions.

from zeridion_flare.worker import FlareWorker, JobContext

worker = FlareWorker()

@worker.job("SendWelcomeEmail", queue="email", max_attempts=5, timeout=60)
def send_welcome(payload, ctx: JobContext) -> None:
send_email(payload["email"])
ctx.report_progress(1.0)

A payload handler takes two positional parameters: the decoded payload and the JobContext. The first is the job's data; the second carries metadata, progress reporting, and the cancellation handle.

The job type is explicit

The first argument to @worker.job(...) is the job type — the routing key that ties an enqueued job to this handler. It is always an explicit string (there is no name derivation), so a worker reliably matches jobs enqueued under the same string from any language or service sharing the queue.

Registering the same job type twice raises a ValueError at startup.

Per-job configuration

ArgumentDefaultMeaning
queue"default"The queue this job type is served from.
max_attempts3Advertised attempt ceiling for this job type.
timeout1800 (30 min)Per-job timeout in seconds.

The server is the source of truth for retry and dead-letter decisions; these values describe the worker's defaults for the job type.

Typed payloads

By default the payload is the decoded JSON value — a dict for an object body. Annotate the first parameter and the worker hydrates it for you:

  • Dataclass — fields are populated from the JSON object (unknown keys are ignored; nested dataclasses hydrate recursively).

    from dataclasses import dataclass

    @dataclass
    class NewUserEvent:
    email: str
    name: str

    @worker.job("SendWelcomeEmail", queue="email")
    def send_welcome(payload: NewUserEvent, ctx) -> None:
    print(payload.email, payload.name)
  • Pydantic v2 model — validated via the model's own parsing. This requires the optional extra:

    pip install "zeridion-flare[pydantic]"
    from pydantic import BaseModel

    class NewUserEvent(BaseModel):
    email: str
    name: str

    @worker.job("SendWelcomeEmail")
    def send_welcome(payload: NewUserEvent, ctx) -> None:
    ...

    Pydantic is never imported unless it is installed — the worker detects it at runtime.

  • No annotation / dict — the raw decoded dict is passed through. A null payload arrives as None.

If payload parsing raises, the job is acknowledged as failed and the server applies its retry policy.

Handling failures

Returning normally acknowledges success. Raising any exception acknowledges failure — the worker captures the exception type, message, and traceback and sends them with the failure so they show up against the job. You do not decide retry-vs-give-up in the handler; the server does, based on the attempt count.

A poison payload (one that can never succeed) should raise a clear, recognizable exception so it is easy to spot in the dashboard; the server governs how many times it is retried before it is dead-lettered.

See also