warren
Warren: a message-driven framework for distributed document processing.
The top-level package holds the contracts that pubsub and workers share: the message consumer protocols, the failure exceptions and the base errors.
Modules
warren.jobswarren.processorswarren.pubsubwarren.retry_managementwarren.runtimewarren.storagewarren.workers
Protocols
AsyncMessageConsumerInterface
protocolAsyncMessageConsumerInterfaceAsync consumer with identity — runs on the main event loop.
Heartbeat safety: this consumer runs on the same event loop as the RabbitMQ connection. The event loop must not be blocked, or heartbeats will fail and the connection will be dropped.
Rules:
- Pure async I/O (HTTP clients, DB queries) is safe — every
awaityields control for heartbeats. - CPU-bound or blocking I/O work MUST be offloaded to a thread or
process pool using
asyncio.get_running_loop().run_in_executor(). - If your consumer is primarily CPU-bound or uses sync libraries, use
SyncMessageConsumerInterfaceinstead — the framework handles threading automatically.
method__call__async (message) -> dict | NoneProcess a message.
Example — async consumer with CPU offloading:
import asyncio
import httpx
def cpu_heavy_parse(data: dict) -> dict:
return data # placeholder for CPU-bound work
class FetchAndParse:
name = "fetch-and-parse-1"
type = "fetch_and_parse"
async def __call__(self, message: dict) -> dict | None:
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/data")
data = resp.json()
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, cpu_heavy_parse, data)
return {"parsed": result}parammessagedictMessage body as a dictionary.
Returns
dict | NoneOptional result to publish downstream, or None if no downstream message is needed. The result gets the scope of the input message.
Raises:
SoftFailureException: When failure is retryable. Message is retried with back-off (seeSoftFailureException).HardFailureException: When failure is permanent. Message will be dropped. Any other exception counts as a hard failure. A hard failure publishes ahard-failureenvelope and rejects the message without requeue. Do not raisePublishFailureExceptionfrom a consumer: the message is then requeued without limit.
MessageConsumerIdentity
protocolMessageConsumerIdentityIdentity for a message consumer — name and type.
Base protocol bundling identity with the consumer. Both async and sync consumer interfaces inherit from this, ensuring that any callable consumer also carries its identity.
attributenamestrUnique instance identifier.
attributetypestrIdentifies the kind of consumer (e.g., "parser_worker").
SyncMessageConsumerInterface
protocolSyncMessageConsumerInterfaceSync consumer with identity — framework runs this in a thread pool.
The framework automatically executes this consumer in a
ThreadPoolExecutor via run_in_executor(), keeping the main
event loop free for RabbitMQ heartbeats.
Rules:
- CPU-bound work and sync library calls (
requests, file I/O, sync DB drivers) work naturally — no special handling needed. - The consumer may spawn its own
ProcessPoolExecutorormultiprocessing.Poolinternally for CPU parallelism. - Do NOT use
asyncio.run()orasyncio.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 (aiohttp, async DB clients), use
AsyncMessageConsumerInterfaceinstead.
method__call__(message) -> dict | NoneProcess a message.
Example — sync consumer with HTTP calls:
import httpx
def heavy_parse(payload: dict) -> dict:
return payload # placeholder for CPU-bound work
class ParseAndPost:
name = "parse-and-post-1"
type = "parse_and_post"
def __call__(self, message: dict) -> dict | None:
parsed = heavy_parse(message["data"])
resp = httpx.post("https://api.example.com", json=parsed)
resp.raise_for_status()
return resp.json()parammessagedictMessage body as a dictionary.
Returns
dict | NoneOptional result to publish downstream, or None if no downstream message is needed. The result gets the scope of the input message.
Raises:
SoftFailureException: When failure is retryable. Message is retried with back-off (seeSoftFailureException).HardFailureException: When failure is permanent. Message will be dropped. Any other exception counts as a hard failure. A hard failure publishes ahard-failureenvelope and rejects the message without requeue. Do not raisePublishFailureExceptionfrom a consumer: the message is then requeued without limit.
Type aliases
MessageConsumerInterface
type aliasMessageConsumerInterface = Union[AsyncMessageConsumerInterface, SyncMessageConsumerInterface]Union type accepted by the consumer manager — either sync or async.
Constants
PUBLISHER_ORIGIN_TYPE
constantPUBLISHER_ORIGIN_TYPE: str = 'publisher'Value for origin.type on messages that a publisher puts into the pipeline.
RoutingPlanRouter routes a message whose origin is not a plan node
to the plan entry. JobStatusWorker stores the value as
origin_type in the job results record. No other framework code
reads this value.
Errors
HardFailureException
errorHardFailureException(reason='', *, cause=None)Permanent failure — message is dropped. No retry.
Any other exception from a worker, except SoftFailureException
and PublishFailureException, also counts as a hard failure. A
hard failure publishes a hard-failure envelope and rejects the
message without requeue.
paramreasonstr= ''Human-readable description of the failure.
paramcauseException | None= NoneOptional exception that caused this failure. Sets __cause__ for exception chaining.
OptionalDependencyError
errorOptionalDependencyError(package_name, *, install_hint='')Raised when an optional dependency is not installed.
parampackage_namestrpip package name that is missing.
paraminstall_hintstr= ''install command or extra specifier (e.g. pip install warren[gcs]).
SoftFailureException
errorSoftFailureException(reason, *, retry_after=None, retry_max=None, backoff_base=None, jitter=None, cause=None, retry_count_consumed=True)Retryable failure — message is retried with back-off.
Workers raise this to signal a transient failure. The consumer manager
catches it, publishes a soft-failure envelope on the observer
exchange, and acks the original message.
- A running
RetryWorkeris required: it stores the envelope and republishes the message after the delay. Without a retry worker, the message is not retried. - Without a control publisher, the consumer manager requeues the
message after
RetryConfig.fallback_requeue_delay. - After
retry_maxattempts, the message is a hard failure. - A value of 0 for
retry_after,retry_maxorbackoff_basemeans "not set": the default of the consumer manager applies. RetryConfig.max_delay_capandRetryConfig.max_retries_caplimit the resolved delay and the resolved maximum.
paramreasonstrHuman-readable description of the failure.
paramretry_afterint | None= NoneSuggested initial delay in seconds before retry. If None, the consumer manager's default is used.
paramretry_maxint | None= NoneSuggested maximum retry attempts. If None, the consumer manager's default is used.
parambackoff_basefloat | None= NoneBase for exponential backoff calculation. Delay on attempt N = retry_after * backoff_base^(N-1). If None, the consumer manager's default is used.
paramjitterbool | None= NoneWhether to add random jitter to the delay. If None, the consumer manager's default is used.
paramcauseException | None= NoneOptional exception that caused this failure. Sets __cause__ for exception chaining.
paramretry_count_consumedbool= TrueIf True (default), incrementing the message's retry counter and checking against retry_max happens normally — the typical "this is a transient failure; try again, but give up after N attempts" semantics. If False, the consumer manager redelivers after the delay without incrementing the counter or enforcing the max — used when the worker is using the retry mechanism as generic delayed redelivery (e.g. polling a long-running external operation) rather than recovering from a failure. Workers must guarantee progress by their own means in that case (e.g. eventually succeeding or raising HardFailure).
WarrenError
errorWarrenErrorBase exception for the distributed processing framework.