soev for developers
WarrenReference

warren.workers

Workers: the worker base classes, the processing message and the worker runner.

To choose a base class, decide how the worker selects its messages and how it runs. CapabilityWorkerBase selects by a declared set of accepts data types. FilteringWorkerBase selects with a hand-written should_process. Both are async. For a worker without selection, use AsyncProcessingWorkerBase for async code, or SyncProcessingWorkerBase for sync code that runs in a thread.

Worker base classes

CapabilityWorkerBase

classCapabilityWorkerBase(worker_name, *, accepts, produces=None, worker_type=None)

Async worker that declares its capabilities (accepts / produces).

The declarative counterpart to FilteringWorkerBase: instead of hand-writing should_process, the worker declares the input data_types it accepts, and should_process defaults to "is the message's data_type one I accept?". This doubles as a runtime type guard (defense in depth) even when a topic/direct exchange has already routed only matching messages.

accepts / produces are typically supplied from the WorkerSpec via WorkerFactoryContext (single source of truth, also used by routing-plan validation).

paramworker_namestr

Unique instance identifier. See AsyncProcessingWorkerBase.

paramacceptsfrozenset[str] | set[str] | list[str]

Input data_types this worker handles.

paramproducesstr | None
= None

Output data_type (informational; None if none).

paramworker_typestr | None
= None

Defaults to the class name in snake_case. See AsyncProcessingWorkerBase.

methodshould_process(message) -> bool

Default guard: accept the message iff its data_type is in accepts.

Override for finer-grained selection (e.g. on preprocessing_required).

parammessagedict
methodprocessasync (message) -> dict | None

Process a message that passed should_process().

parammessagedict

Raises:

  • SoftFailureException: When failure is retryable.
  • HardFailureException: When failure is permanent.
method__call__async (message) -> dict | None
parammessagedict

FilteringWorkerBase

classFilteringWorkerBase(worker_name, *, worker_type=None)

Async worker that self-selects messages via should_process().

Subclasses implement should_process() to decide whether a message is relevant, and process() for the actual work. When should_process() returns False, the message is ACKed with no downstream publish.

paramworker_namestr

Unique instance identifier. See AsyncProcessingWorkerBase.

paramworker_typestr | None
= None

Defaults to the class name in snake_case. See AsyncProcessingWorkerBase.

methodshould_process(message) -> bool

Decide whether this worker should handle the message.

parammessagedict

Message body as a dictionary.

Returns

bool

True if this worker should process the message.

methodprocessasync (message) -> dict | None

Process a message that passed should_process().

parammessagedict

Message body as a dictionary.

Returns

dict | None

Optional result to publish downstream, or None if no downstream message is needed (terminal worker).

Raises:

  • SoftFailureException: When failure is retryable.
  • HardFailureException: When failure is permanent.
method__call__async (message) -> dict | None
parammessagedict

AsyncProcessingWorkerBase

classAsyncProcessingWorkerBase(worker_name, *, worker_type=None)

Abstract base class for async processing workers.

Satisfies the AsyncMessageConsumerInterface protocol. Workers receive plain dictionaries and return optional dictionaries — they never interact with RabbitMQ or any other message transport directly.

Heartbeat safety: this worker runs on the main event loop alongside the RabbitMQ connection. Every await yields control so heartbeats can be processed.

  • Pure async I/O (HTTP clients, DB queries) is safe.
  • CPU-bound work MUST be offloaded via asyncio.get_running_loop().run_in_executor().
  • If your worker is primarily CPU-bound or uses sync libraries, use SyncProcessingWorkerBase instead — the framework handles threading automatically.

Use this base class when:

  • Worker needs async I/O with shared connection pools (async HTTP clients, async DB clients).
  • Worker combines async I/O with some CPU work (offload CPU parts via run_in_executor).
paramworker_namestr

Unique instance identifier. It is the name of the worker and origin.name on its failure envelopes.

paramworker_typestr | None
= None

Defaults to the class name in snake_case. It is origin.type on failure envelopes. Set it as origin.type on results too: RoutingPlanRouter reads it as the plan node id. Pass WorkerFactoryContext.worker_type.

attributenamestr

Unique instance identifier for this worker.

attributetypestr

Identifies the kind of worker (e.g., "parser_worker").

methodsetupasync () -> None

Start any owned async resources.

Called by the runner after the worker is constructed and before the consumer begins delivering messages. Override to start long-lived async resources (subprocesses, HTTP client pools, connection warm-ups) that cannot be safely initialised in __init__ because they need the running event loop.

Default: no-op.

methodteardownasync () -> None

Close any owned async resources.

Called by the runner after the consumer has stopped delivering messages and before infrastructure (MongoDB, Redis, RMQ) is torn down. Override to close resources that require an explicit async moment — async HTTP client pools (cannot close from __del__) and subprocesses (GC does not reap them).

Default: no-op. Implementations should be best-effort: log and continue rather than raise, so one resource's failure does not strand another.

method__call__async (message) -> dict | None

Process a message from the queue.

parammessagedict

Message body as a dictionary.

Returns

dict | None

Optional result to publish downstream, or None if no downstream message is needed.

Raises:

  • SoftFailureException: When failure is retryable. Message is retried with back-off (see SoftFailureException).
  • HardFailureException: When failure is permanent. Message will be dropped. Any other exception counts as a hard failure (see HardFailureException).

SyncProcessingWorkerBase

classSyncProcessingWorkerBase(worker_name, *, worker_type=None)

Abstract base class for sync processing workers.

Satisfies the SyncMessageConsumerInterface protocol. The framework automatically runs __call__ in a ThreadPoolExecutor via run_in_executor(), keeping the main event loop free for RabbitMQ heartbeats.

Threading behavior: each invocation runs in a thread from the default ThreadPoolExecutor (sized min(32, os.cpu_count() + 4)). The worker may spawn its own ProcessPoolExecutor or multiprocessing.Pool internally for CPU parallelism across cores.

  • CPU-bound work and sync library calls (requests, file I/O, sync DB drivers) work naturally.
  • Do NOT use asyncio.run() or asyncio.get_event_loop() — async objects from the main loop cannot be used from a different thread due to event loop affinity.
  • If you need async I/O (async HTTP clients, async DB clients), use AsyncProcessingWorkerBase instead.

Use this base class when:

  • Worker does CPU-bound work (parsing, extraction, computation).
  • Worker uses sync libraries (requests, file I/O, sync DB drivers).
  • Worker manages its own multiprocessing internally.
paramworker_namestr

Unique instance identifier. See AsyncProcessingWorkerBase.

paramworker_typestr | None
= None

Defaults to the class name in snake_case. See AsyncProcessingWorkerBase.

attributenamestr

Unique instance identifier for this worker.

attributetypestr

Identifies the kind of worker (e.g., "parser_worker").

methodsetupasync () -> None

Start any owned async resources. Default: no-op.

See AsyncProcessingWorkerBase.setup for semantics. Sync workers can still own async resources at the process level (the runner operates on the event loop even when __call__ runs in a thread).

methodteardownasync () -> None

Close any owned async resources. Default: no-op.

See AsyncProcessingWorkerBase.teardown for semantics.

method__call__(message) -> dict | None

Process a message from the queue.

parammessagedict

Message body as a dictionary.

Returns

dict | None

Optional result to publish downstream, or None if no downstream message is needed.

Raises:

  • SoftFailureException: When failure is retryable. Message is retried with back-off (see SoftFailureException).
  • HardFailureException: When failure is permanent. Message will be dropped. Any other exception counts as a hard failure (see HardFailureException).

Messages

ProcessingMessage

dataclassProcessingMessage(data_type, data, job_id, origin, job_metadata=None, job_parameters=dict(), preprocessing_required=list(), preprocessing_completed=list(), retry=None, scope=None, lane=None, priority=None)

Canonical message format for the distributed processing framework.

Workers use data_type (and optionally job_metadata) to decide whether to process a message, and job_parameters to decide how.

paramdata_typestr

Semantic type of the payload (e.g., "pdf_document", "markdown_document", "text_chunks").

paramdatadict[str, Any]

Payload, or a reference to the payload in storage. The framework reads two keys: doc_id (required for job status: a message without it is not counted) and part_idx (default 0). Together with job_id, they identify a message for retries and logs. All other keys are opaque to the framework.

paramjob_idstr

Identifier grouping related messages into a single job.

paramoriginMessageOrigin

Source that produced this message.

paramjob_metadatadict[str, Any] | None
= None

Optional metadata about the job. Structure is application-defined (e.g., user, reason, priority).

paramjob_parametersdict[str, Any]
= dict()

Application-defined processing parameters. Workers interpret these to decide how to process (e.g., force flags for reprocessing).

parampreprocessing_requiredlist[str]
= list()

Preprocessing steps still needed before the main processing can proceed.

parampreprocessing_completedlist[str]
= list()

Preprocessing steps already done.

paramretryRetryInfo | None
= None

Retry state, populated on soft failure.

paramscopestr | None
= None

Optional storage scope. The consumer manager sets it as the current scope while the handler runs, and copies it to the result.

paramlanestr | None
= None

Optional application-defined routing lane.

parampriorityint | None
= None

Optional AMQP priority, an integer from 0 through 255. Only the RabbitMQ publisher sends it, and the queue needs an x-max-priority argument (RMQConsumerConfig.queue_arguments). The memory and Kafka backends ignore it.

Raises:

  • ValueError: If priority is a bool, or an int outside 0 through 255.
methodcreate_from(message) -> ProcessingMessage

Create a ProcessingMessage from a raw message dict.

parammessagedict[str, Any]

Raw message dictionary from the transport layer.

Returns

ProcessingMessage

Typed ProcessingMessage instance.

Raises:

  • KeyError: If required fields are missing.
methodderive(*, data_type, data, origin, preprocessing_required=None, preprocessing_completed=None) -> ProcessingMessage

Derive a new message carrying forward job metadata from this one.

Copies job_id, job_metadata, job_parameters, scope, lane, and priority from self. Preprocessing fields are copied unless explicitly overridden. The retry field is intentionally not copied — a derived message starts with a clean retry state.

paramdata_typestr

Semantic type of the new payload.

paramdatadict[str, Any]

New payload or reference data.

paramoriginMessageOrigin

Origin identifying the worker producing this message.

parampreprocessing_requiredlist[str] | None
= None

Override preprocessing steps still needed. If None, copies from self.

parampreprocessing_completedlist[str] | None
= None

Override completed preprocessing steps. If None, copies from self.

Returns

ProcessingMessage

New ProcessingMessage with inherited job metadata.

methodto_dict() -> dict[str, Any]

Serialize to dict for publishing via transport layer.

Produces a dict compatible with create_from() — optional fields (job_metadata, retry) are omitted when None.

MessageOrigin

dataclassMessageOrigin(type, name)

Identifies the source that produced a message.

paramtypestr

Category of the origin (e.g., "document_parser", "API").

paramnamestr

Specific instance name (e.g., "document_parser-1").

methodto_dict() -> dict[str, str]

Serialize to dict for transport layer.

RetryInfo

dataclassRetryInfo(count, after, reason, max=5)

Retry state attached to messages on soft failure.

paramcountint

Current retry attempt number.

paramafterint

Delay in seconds before the next retry.

paramreasonstr

Explanation for the retry.

parammaxint
= 5

Maximum retry attempts before hard failure.

methodto_dict() -> dict[str, Any]

Serialize to dict for transport layer.

Runner

WorkerRunnerBase

classWorkerRunnerBase(*, name=None, health=None)

Base class for running a worker process.

Provides the generic lifecycle: signal handling, consume loop, and graceful shutdown. Subclasses implement setup() to wire their specific components (connections, stores, worker, consumer manager).

A subclass uses these protected names:

  • self._consumer_manager: set it in setup(). run() and teardown() use it.
  • self._mark_setup_succeeded(): call it at the end of setup(). Without it, run() raises RuntimeError.
  • _on_teardown(): override it for subclass-specific cleanup (e.g., closing connections, cancelling timers). It runs after the consumer is stopped but before state is reset.
  • self._exception_wrapping(description): a context manager for a setup phase. It re-raises a failure as WarrenError with the phase and the worker name.
paramnamestr | None
= None

Logger name of the runner.

paramhealthHealthConfig | None
= None

Health endpoint and watchdog settings. Defaults to HealthConfig(). Usage:: runner = MyRunner(...) await runner.setup() # subclass wires components await runner.run() # consume until shutdown signal await runner.teardown() # cleanup

methodsetupasync () -> None

Initialize all resources and wire components.

Subclasses must set self._consumer_manager and call self._mark_setup_succeeded() at the end.

methodrunasync () -> None

Start consuming and wait for shutdown signal (SIGINT/SIGTERM).

Raises:

  • RuntimeError: If setup() did not succeed before.
  • WarrenError: If the watchdog declared the consumer lost.
methodteardownasync () -> None

Stop consuming, run subclass cleanup, and reset state.

Idempotent and safe after a partial or failed setup(): tears down whatever exists, best-effort, regardless of whether setup() ran to completion. _setup_succeeded gates run() only — never cleanup.

ConsumerManagerFactory

type aliasConsumerManagerFactory = Callable[[MessageConsumerInterface], ConsumerManagerInterface]

Creates a consumer manager for the given consumer.

Signature: (consumer) -> ConsumerManagerInterface

The factory encapsulates transport-specific wiring (exchange, queue, connection) so runners stay transport-agnostic. The consumer carries its own identity (name and type) for use on failure envelopes.

Health

HealthConfig

modelHealthConfig

Watchdog and endpoint settings.

attributeenabledbool
= True

Serve /ready and /live. On by default; a failed bind is logged and the worker keeps consuming without the endpoint.

attributehoststr
= '0.0.0.0'

Bind address.

attributeportint
= 8080

Bind port; 0 picks a free one (tests).

attributecheck_interval_sfloat
= 5.0

Seconds between watchdog observations.

attributeprobe_timeout_sfloat
= 1.0

Upper bound on each observation's waits.

attributeconsumer_lost_grace_sfloat
= 60.0

How long the consumer may be lost, with a live connection, before the process exits so the orchestrator restarts it.

HealthServer

classHealthServer(get_health, *, host, port)

Minimal HTTP readiness endpoint.

GET /ready → 200 while the last sample says ready, else 503; GET /live → 503 if a handler exceeds twice its timeout, else 200. Anything else → 404. The body is the sample as JSON.

paramget_healthCallable[[], ConsumerHealth | None]

Returns the last observed health, or None before the first observation.

paramhoststr

Bind address.

paramportint

Bind port; 0 picks a free one.

attributeportint | None

The bound port, or None while not serving.

methodstartasync () -> bool

Bind and serve. A failed bind is reported, not raised.

Returns

bool

True if serving.

methodstopasync () -> None

Message keys and identity

build_message_key

functionbuild_message_key(job_id, doc_id, part_idx=0) -> str

Build a composite message key from identity fields.

Consistent key format used across ResultsStore, RetryWorker, and cache layers. Format: job:{job_id}:doc:{doc_id}:part:{part_idx}.

None values are normalized to "-" so keys remain valid and consistent regardless of whether identifiers are available.

paramjob_idstr | None

Job identifier, or None.

paramdoc_idstr | None

Document identifier, or None.

parampart_idxint
= 0

Part index within the document.

Returns

str

Composite key string.

build_message_key_prefix

functionbuild_message_key_prefix(doc_id, job_id) -> str

Build a key prefix for matching all parts of a document.

Warning: doc_id is the first parameter, but build_message_key takes job_id first. Pass the arguments by keyword, or the prefix matches no key.

Use with CacheInterface.get_by_key_prefix() to retrieve all parts of a document within a job. Includes trailing separator to avoid matching unrelated keys.

None values are normalized to "-".

paramdoc_idstr | None

Document identifier, or None.

paramjob_idstr | None

Job identifier, or None.

Returns

str

Key prefix string ending with :.

extract_message_identity

functionextract_message_identity(message) -> str

Extract identity fields from a message dict for logging.

Returns a human-readable string with job_id, doc_id, and part_idx. Missing fields are shown as "?". Safe to call on any message dict.

parammessagedict

Raw message dictionary.

Returns

str

Identity string for log messages, e.g. "job=abc123 doc=doc456 part=0".

ExtractMessageIdentityFunc

type aliasExtractMessageIdentityFunc = Callable[[dict], str]

Callable that extracts a human-readable identity string from a message dict.

On this page