soev for developers
WarrenReference

warren.runtime

Runtime layer for the distributed processing framework.

Provides the concrete wiring that turns a PipelineSpec into running worker processes. The runtime runs a pipeline on RabbitMQ (aio-pika) or Kafka (aiokafka), with MongoDB and Redis for storage. backend: memory runs all workers in one process, with no infrastructure.

Pipeline spec

Declarative pipeline composition: the exchange, the worker types and how each worker type is built.

PipelineSpec

dataclassPipelineSpec(workers, exchange, result_collections, reference_collection, completion_collection, final_data_type, publication=None)

Full pipeline composition: exchange, workers + completion criteria.

A pipeline uses a single exchange. Multi-exchange deployments (a worker publishing to several exchanges at once) are deferred — see warren/docs/routing.md.

paramworkersdict[str, WorkerSpec]

Maps worker type name to its WorkerSpec.

paramexchangeRMQExchangeConfig

The exchange all workers consume from and publish to.

paramresult_collectionslist[str]

MongoDB collection names of the pipeline results, for application use. Warren does not read this field.

paramreference_collectionstr

A collection name for application use. Warren does not read this field.

paramcompletion_collectionstr

A collection name for application use. Warren does not read this field.

paramfinal_data_typestr

The data_type that marks a document as done. Pass it to the job store when you create a job: JobStatusWorker reads the copy in the job record, not this field.

parampublicationPublishSpec | None
= None

Optional routing for the publication worker's document messages. Set route_func; static route is not supported. None preserves exchange-based default routing.

WorkerSpec

dataclassWorkerSpec(collections, factory, binding_key=None, publish=None, accepts=frozenset(), produces=None, needs_document_fetcher=False, needs_document_store=False, *, binding_keys=())

One worker type: how it is wired into the pipeline's exchange.

paramcollectionsdict[str, str]

Maps role ("read", "write") to MongoDB collection name. The runner creates a DefaultResultsStore per role.

paramfactoryWorkerFactory

Callable that creates the worker given a WorkerFactoryContext.

parambinding_keystr | None
= None

Backward-compatible constructor alias for one key.

parampublishPublishSpec | None
= None

How the worker publishes its result, or None if it publishes nothing downstream (terminal). Job completion does not key off this — see PipelineSpec.final_data_type.

paramacceptsfrozenset[str]
= frozenset()

Declared input data_types this worker can handle. Nominal (by-name) capability metadata, used by routing-plan validation and by CapabilityWorkerBase.should_process.

paramproducesstr | None
= None

Declared output data_type (None if terminal/none).

paramneeds_document_fetcherbool
= False

If True, the runner passes a GetDocumentFunc to the factory. Otherwise passes None.

paramneeds_document_storebool
= False

If True, the runner passes a DocumentStoreInterface for direct reads/writes against the "documents" collection (distinct from the GetDocumentFunc path, which fetches bytes via location resolvers). Used by workers that read or write document records by doc_id.

parambinding_keystuple[str, ...]
= ()

Queue binding patterns for direct/topic exchanges. Required for direct/topic and ignored on fanout.

Raises:

  • ValueError: If both binding_key and binding_keys are set.

PublishSpec

dataclassPublishSpec(route=None, route_func=None)

How a worker publishes its result to the pipeline's exchange.

The exchange's type decides how the routing key is set: fanout ignores it (leave route/route_func unset); direct/topic require exactly one of route (a static key) or route_func (computed per message, e.g. MessageFieldRouter).

paramrouteRoute | None
= None

Static routing key (direct/topic only).

paramroute_funcRouteFunc | None
= None

Per-message routing function (direct/topic only).

WorkerFactory

type aliasWorkerFactory = Callable[[WorkerFactoryContext], Awaitable[MessageConsumerInterface]]

Async factory callable: receives a context, returns a worker.

Async because factories may need to construct stores whose setup() is async (index creation, schema validation). Simple factories that don't need async work can still be async def with no await inside.

WorkerFactoryContext

dataclassWorkerFactoryContext(worker_name, worker_type, stores, mongo_client, redis_client, database_name, accepts=frozenset(), produces=None, get_document_func=None, document_store=None, scoped_database=None)

Bundle of resources the runner hands to each worker factory.

Workers need different resources: some need a document fetcher, some need direct document storage, and a factory can build more stores (like BinaryResultsStore) from the raw clients. A single context keeps factory signatures stable as more concerns get added.

paramworker_namestr

Runner-assigned unique name for this worker.

paramworker_typestr

The worker type (its key in PipelineSpec.workers). Pass to the worker so its type / message origin matches the spec key (the node id used by addressed/job-defined routing).

paramstoresdict[str, ResultsStoreInterface]

Pre-built ResultsStoreInterface per role (keys come from WorkerSpec.collections). Factories that don't use them ignore the dict.

parammongo_clientAsyncMongoClient | None

Raw async Mongo client, passed through so factories can construct additional specialised stores (e.g. BinaryResultsStore). Populated by the runner on the RabbitMQ and Kafka backends; None on backend: memory, where no MongoDB or Redis exists.

paramredis_clientRedis | None

Raw async Redis client — same rationale.

paramdatabase_namestr

Mongo database name to use for any factory-constructed stores.

paramacceptsfrozenset[str]
= frozenset()

Declared input data_types from the spec. Pass to a CapabilityWorkerBase so its should_process can derive from it.

paramproducesstr | None
= None

Declared output data_type from the spec.

paramget_document_funcGetDocumentFunc | None
= None

Present when needs_document_fetcher is set on the spec. None otherwise.

paramdocument_storeDocumentStoreInterface | None
= None

Present when needs_document_store is set. None otherwise.

paramscoped_databaseScopedDatabase | None
= None
attributecurrent_scopestr | None

Run

DefaultWorkerRunner runs one worker. create_in_process_runners and run_in_process run a whole pipeline in one process on backend: memory.

DefaultWorkerRunner

classDefaultWorkerRunner(config, worker_name, *, worker_type, worker_spec, exchange, document_fetcher=None, document_store=None, results_stores=None, infra=None)

Concrete runner that wires the pubsub backend and the stores for one worker.

All worker-specific decisions are driven by the WorkerSpec — no branching on worker type name.

Creates infrastructure from RuntimeConfig and builds default components in setup(). Inject custom components to override defaults (e.g. for testing).

paramconfigRuntimeConfig

runtime infrastructure configuration.

paramworker_namestr

unique worker instance identifier.

paramworker_typestr

name of the worker type (used for queue naming).

paramworker_specWorkerSpec

spec defining collections, factory, exchange wiring (binding_keys, publish), and flags.

paramexchangeRMQExchangeConfig

the pipeline's exchange (from PipelineSpec.exchange) this worker consumes from and publishes to.

paramdocument_fetcherGetDocumentFunc | None
= None

optional override for the document fetcher. Default: CachedDocumentFetcher with the resolvers of create_default_resolvers(path_base_dir=config.documents.path_base_dir) (created when worker_spec.needs_document_fetcher is True).

paramdocument_storeDocumentStoreInterface | None
= None

optional override for the document store. Default: MongoDBDocumentStore on the documents collection (created when worker_spec.needs_document_store is True).

paramresults_storesdict[str, ResultsStoreInterface] | None
= None

optional override for the results stores. Default: one DefaultResultsStore per collection in worker_spec.collections.

paraminfraRuntimeInfra | None
= None

optional shared infrastructure. When given, the runner uses it and does not close it; the caller owns its lifetime. Needed when several runners share one process (backend: memory).

methodsetupasync () -> None

Wire connections, stores, worker, publishers, and consumer.

  1. Set up infrastructure (pubsub connection, MongoDB, Redis)
  2. Build default stores/fetcher for any not injected
  3. Create worker via spec's factory (async)
  4. Wrap worker (subclass hook, e.g. for failure injection)
  5. Run worker-level setup (start owned async resources)
  6. Create publishers for downstream routing
  7. Create and set up the consumer manager

On backend: memory, inject results_stores, and also document_fetcher or document_store when the spec needs them: the memory backend has no MongoDB or Redis to build them.

Raises:

  • WarrenError: If a phase fails. The error names the phase and chains the original exception. On backend: memory, also if a store that the spec needs is not injected.

create_in_process_runners

functioncreate_in_process_runnersasync (config, pipeline, *, infra, stores, instances=None) -> list[WorkerRunnerBase]

Build worker instances, plus the status and retry runners.

The one place that knows which registry store goes into which runner parameter. Every runner shares infra (and with it the broker) and draws its stores from stores, so a stage reads what the previous stage wrote. The retry runner is included when config.retry.enabled.

paramconfigRuntimeConfig

Runtime configuration with backend: memory.

parampipelinePipelineSpec

The pipeline to run.

paraminfraRuntimeInfra

Shared infrastructure. The caller creates and closes it.

paramstoresMemoryStoreRegistry

Shared store registry.

paraminstancesdict[str, int] | None
= None

Instance count per worker type; unspecified types get one.

Returns

list[WorkerRunnerBase]

Runners, not yet set up.

Raises:

  • ValueError: If instances names a worker type that is not in the pipeline, or gives a count that is not an integer of 1 or more.

run_in_process

functionrun_in_processasync (runners, *, until=None) -> None

Set up and run several runners in this event loop until one stops.

Every runner is set up before any of them runs, so every queue is bound before anything publishes (the in-process broker, like RabbitMQ, drops a message nobody is bound for). The run ends when the first runner returns or raises, or when until returns. Everything is then cancelled and torn down, and the first failure is re-raised.

WorkerRunnerBase.run() installs SIGINT/SIGTERM handlers per runner, so with several runners only the last one's handler survives. That is enough: on Ctrl-C that one runner returns, which ends the run for all. The handlers in place before the run are restored afterwards.

paramrunnersSequence[WorkerRunnerBase]

Runners to co-host. Not yet set up.

paramuntilCallable[[], Awaitable[None]] | None
= None

Called after every runner is set up, so every queue is bound. Runners can still be starting their consumers. Publish work and wait for it here. A callable, not a coroutine object, so nothing is left un-awaited if setup fails.

Configuration

RuntimeConfig loads the settings of a deployment from YAML. The other models are its sections.

RuntimeConfig

modelRuntimeConfig

Top-level runtime configuration.

attributebackendLiteral['rabbitmq', 'kafka', 'memory']
= 'rabbitmq'

Pubsub backend the runtime wires up. Explicit (not presence-based): defaults to "rabbitmq" so every existing YAML stays valid, and only the matching backend section is used. "memory" runs everything in one process with no infrastructure and needs no external connection settings (see warren/docs/memory.md).

attributerabbitmqRuntimeRMQConfig
= RuntimeRMQConfig()

RabbitMQ connection and consumer settings. The exchange is on PipelineSpec.exchange.

attributekafkaRuntimeKafkaConfig
= RuntimeKafkaConfig()

Kafka connection, topic, and consumer settings.

attributememoryMemoryConsumerConfig
= MemoryConsumerConfig()

In-process consumer settings.

attributemongodbMongoDBConfig
= MongoDBConfig()

MongoDB connection settings.

attributeredisRedisConfig
= RedisConfig()

Redis connection settings.

attributedocumentsDocumentsConfig
= DocumentsConfig(cache_ttl_seconds=86400)

Fetched payload cache expiry and path base directory.

attributeresultsCacheConfig
= CacheConfig()

Processing result cache expiry.

attributeretentionRetentionConfig
= RetentionConfig()

MongoDB job record expiry.

attributeretryRuntimeRetryConfig
= RuntimeRetryConfig()

Retry worker toggle and collection name.

attributehealthHealthConfig
= HealthConfig()

Watchdog and readiness endpoint settings.

attributestartupStartupConfig
= StartupConfig()

Connection attempts and backoff settings.

attributescopingScopingConfig
= ScopingConfig()

Content namespace settings.

methodfrom_yaml(path) -> RuntimeConfig

Load configuration from a YAML file.

String values can use ${NAME}. The value comes from the environment.

parampathPath

Path to the YAML config file.

Returns

RuntimeConfig

Populated RuntimeConfig instance.

Raises:

  • FileNotFoundError: If the config file doesn't exist.
  • ValueError: If an environment variable NAME is not set.
  • pydantic.ValidationError: For an unknown key or a bad value.

RuntimeRMQConfig

modelRuntimeRMQConfig

RabbitMQ connection + consumer settings (environment-specific).

Exchange definitions (topology) live on the PipelineSpec, not here. Config holds only what varies per deployment: where the broker is and how to consume.

attributeconnectionRMQConnectionConfig
= RMQConnectionConfig()
attributeconsumerRMQConsumerConfig
= RMQConsumerConfig()

RuntimeKafkaConfig

modelRuntimeKafkaConfig

Kafka settings composed from framework config models.

The default topic is jobs — the Kafka counterpart of the RMQ jobs fanout exchange. topic.create_if_missing defaults to False, so setup fails when the topic does not exist. Create the topic out-of-band, or set topic.create_if_missing.

attributeconnectionKafkaConnectionConfig
= KafkaConnectionConfig()
attributetopicKafkaTopicConfig
= KafkaTopicConfig(name='jobs')
attributeconsumerKafkaConsumerConfig
= KafkaConsumerConfig()

MongoDBConfig

modelMongoDBConfig
attributeuriSecretStr | None
= None
attributehoststr
= 'localhost'
attributeportint
= 27017
attributedatabasestr
= 'distributed_processing'
attributeusernamestr | None
= None
attributepasswordSecretStr | None
= None
attributeauth_sourcestr | None
= None
attributetlsbool
= False
attributemax_pool_sizeint | None
= None
attributeserver_selection_timeout_msint | None
= None

RedisConfig

modelRedisConfig
attributeurlSecretStr | None
= None
attributehoststr
= 'localhost'
attributeportint
= 6379
attributeusernamestr | None
= None
attributepasswordSecretStr | None
= None
attributedbint
= 0
attributesslbool
= False
attributemax_connectionsint | None
= None
attributesocket_timeoutfloat | None
= None

CacheConfig

modelCacheConfig

Expiry for cached payloads, in seconds.

attributecache_ttl_secondsint
= 3600

DocumentsConfig

modelDocumentsConfig

Fetched payload cache expiry and local-file resolution.

attributepath_base_dirPath | None
= None

Directory path document locations are read from. Unset (the default) disables the path resolver.

RetentionConfig

modelRetentionConfig

MongoDB job record expiry, in seconds.

attributejob_records_ttl_secondsint | None
= None

Delete a job record this many seconds after the job completes, and a job results record this many seconds after its last write. JobStatusWorkerRunner passes it to its default stores. Pass it to the stores that you build. None means no expiry.

attributejob_records_max_age_secondsint | None
= None

Delete a job record this many seconds after it is created, also when the job does not complete. Must be at least job_records_ttl_seconds. None means no expiry.

RuntimeRetryConfig

modelRuntimeRetryConfig

Retry worker toggle plus the retry policy every consumer manager applies.

attributeenabledbool
= False

In-process runs only (create_in_process_runners): add a retry runner. A multi-process deployment ignores this flag. Start a retry worker (start_retry_worker) to retry soft failures there.

attributecollection_namestr
= 'retries'

MongoDB collection for messages awaiting retry.

attributelease_ttl_secondsint
= 30

Lifetime in seconds of the retry lease. One retry worker instance at a time holds the lease and schedules the retries.

attributepolicyRetryConfig
= RetryConfig()

Defaults and caps for soft-failure retries. Passed to every consumer manager the runner builds, so max_delay_cap and friends are deployment settings rather than library constants.

StartupConfig

modelStartupConfig

Connection attempts and bounded exponential backoff at startup.

attributeattemptsint
= 1

Number of connection attempts. After the last failed attempt, the connection error is raised.

attributeinitial_delay_secondsfloat
= 1.0

Delay before the second attempt.

attributemax_delay_secondsfloat
= 30.0

The delay doubles after each attempt, up to this value.

ScopingConfig

modelScopingConfig

Content namespace settings (see warren/docs/scoping.md).

attributeenabledbool
= False

Store content in a namespace for each message scope.

attributerequiredbool
= True

A message without scope is a hard failure when it uses scoped storage. If False, a message without scope uses the default database and cache keys without a prefix.

attributedatabase_prefixstr
= 'wr_'

Prefix of the MongoDB database for each scope: content goes to <database_prefix><scope>.

Infrastructure and backends

RuntimeInfra

classRuntimeInfra

All infrastructure connections for a worker process.

On backend: memory there is no MongoDB or Redis, so both clients are None and stores are injected into the runners instead.

attributemongo_clientAsyncMongoClient | None
attributeredis_clientRedis | None
attributepubsub_connection_managerAny

create_runtime_infrastructure

functioncreate_runtime_infrastructureasync (config, *, sleep=asyncio.sleep) -> RuntimeInfra

Create and initialize all connections from runtime config.

The caller is responsible for closing via close_runtime_infrastructure.

The function tries config.startup.attempts times to connect to the pubsub backend, MongoDB and Redis. After a failed attempt, it closes the connections of that attempt and waits (see StartupConfig). On backend: memory, it creates only the in-process connection manager and returns RuntimeInfra(None, None, manager).

paramconfigRuntimeConfig

Runtime infrastructure configuration.

paramsleepCallable[[float], Awaitable[None]]
= asyncio.sleep

Delay between startup attempts.

Returns

RuntimeInfra

Initialized connections ready for use.

Raises:

  • Exception: The connection error of the last attempt, re-raised unchanged.

close_runtime_infrastructure

functionclose_runtime_infrastructureasync (infra) -> None

Close all connections. Best-effort — logs and continues on errors.

paraminfraRuntimeInfra

Infrastructure to close.

require_injected

functionrequire_injected(config, **components) -> None

Fail clearly when the memory backend would need a default store.

Default stores are built from the Mongo and Redis clients. The memory backend has neither, so every store a runner needs must be injected. Without this check the failure is an AttributeError on None deep inside a store constructor.

paramconfigRuntimeConfig

Runtime configuration.

paramcomponentsobject

Name to injected value, for each component the runner needs. None means "not injected".

Raises:

  • WarrenError: On the memory backend when any value is None.

create_connection_manager

functioncreate_connection_manager(config) -> Any

Create the (unset-up) pubsub connection manager for the backend.

The caller is responsible for setup() / teardown().

paramconfigRuntimeConfig

Runtime configuration; config.backend selects the backend.

Returns

Any

RMQConnectionManager, KafkaConnectionManager or MemoryConnectionManager.

Raises:

  • OptionalDependencyError: if the selected backend's transport extra is not installed.

create_publisher

functioncreate_publisher(config, connection_manager, *, exchange, route=None, route_func=None, name=None) -> PublisherInterface

Create a publisher onto exchange for the configured backend.

paramconfigRuntimeConfig

Runtime configuration; config.backend selects the backend.

paramconnection_managerAny

Connection manager from create_connection_manager (must match the backend).

paramexchangeRMQExchangeConfig

The pipeline exchange to publish to (from the PipelineSpec, or a framework-derived observer exchange). On Kafka, only the type is used (it must be fanout). The topic comes from config.kafka.topic.

paramrouteRoute | None
= None

Static routing key (RabbitMQ topic/direct only).

paramroute_funcRouteFunc | None
= None

Per-message routing function (RabbitMQ topic/direct; a no-op on fanout). Kafka ignores route and route_func: the pipeline is fanout.

paramnamestr | None
= None

Optional publisher name for logging.

Returns

PublisherInterface

An unset-up RMQPublisher, KafkaPublisher or MemoryPublisher implementing PublisherInterface.

Raises:

  • WarrenError: if exchange is not fanout on Kafka.
  • OptionalDependencyError: if the selected backend's transport extra is not installed.

create_consumer_manager

functioncreate_consumer_manager(config, connection_manager, *, exchange, worker_type, consumer, binding_key=None, binding_keys=(), data_publisher=None, control_publisher=None, observer_publisher=None, retry_config=None, extract_identity_func=None, publish_hard_failures=True) -> ConsumerManagerInterface

Create the consumer manager for worker_type on the backend.

Owns the queue/group naming convention for all call sites:

  • RabbitMQ: queue f"{exchange.name}.{worker_type}", bound with binding_keys.
  • Memory: queue f"{exchange.name}.{worker_type}", same as RabbitMQ.
  • Kafka: consumer group config.kafka.consumer.group_id or f"\{topic.name\}.\{worker_type\}" (pre-resolved here); binding keys are ignored on fanout.

The publisher split (data / control / observer — see warren/docs/routing.md) is forwarded verbatim to the backend consumer manager; both backends share the same constructor shape.

paramconfigRuntimeConfig

Runtime configuration; config.backend selects the backend.

paramconnection_managerAny

Connection manager from create_connection_manager (must match the backend).

paramexchangeRMQExchangeConfig

The exchange this worker consumes from. On Kafka, only the type is used (it must be fanout). The topic comes from config.kafka.topic.

paramworker_typestr

Worker type name — the queue suffix (RMQ) or the group suffix (Kafka).

paramconsumerMessageConsumerInterface

The worker that processes each message.

parambinding_keystr | None
= None

Backward-compatible alias for one binding key.

parambinding_keystuple[str, ...]
= ()

Queue binding patterns; ignored on fanout.

paramdata_publisherPublisherInterface | None
= None

Downstream publisher for successful results, or None if the worker is terminal.

paramcontrol_publisherPublisherInterface | None
= None

Publisher for lifecycle envelopes (soft/hard-failure), or None for seek-back/requeue fallback.

paramobserver_publisherPublisherInterface | None
= None

Publisher echoing successful results to the observer exchange (RabbitMQ direct pipelines only).

paramretry_configRetryConfig | None
= None

Retry policy override (defaults applied by the consumer manager when None).

paramextract_identity_funcExtractMessageIdentityFunc | None
= None

Message-identity extractor override.

parampublish_hard_failuresbool
= True

Whether to publish a hard-failure envelope on terminal failure.

Returns

ConsumerManagerInterface

An unset-up RMQConsumerManager, KafkaConsumerManager or MemoryConsumerManager implementing ConsumerManagerInterface.

Raises:

  • ValueError: if both binding_key and binding_keys are set.
  • WarrenError: if exchange is not fanout on Kafka.
  • OptionalDependencyError: if the selected backend's transport extra is not installed.

create_default_resolvers

functioncreate_default_resolvers(*, path_base_dir=None) -> dict[str, ResolveDocumentFunc]

Build the default document resolver registry.

path only when path_base_dir is given, confined to it; cloud (GCS, S3) and url when their optional extras are installed. A module function so callers that are not a runner instance can build a document fetcher too.

parampath_base_dirPath | None
= None

Directory local-file reads are confined to. None leaves the path resolver unregistered.

Returns

dict[str, ResolveDocumentFunc]

Mapping of location type to resolver function.

Validation

validate_pipeline

functionvalidate_pipeline(pipeline, *, logger=None) -> None

Validate a pipeline's exchange wiring; raise on any error.

parampipelinePipelineSpec

the pipeline to validate.

paramloggerlogging.Logger | None
= None

optional logger for the success / not-yet-validated note.

Raises:

  • PipelineValidationError: with every problem found, not just the first.

validate_routing_plan

functionvalidate_routing_plan(plan, registry, *, entry_data_type=None) -> None

Validate a job's RoutingPlan against deployed worker capabilities.

Run at job-submission time, before any message is published.

Checks: every node maps to a deployed worker; every edge u -> v is nominally type-compatible (produces[u] in accepts[v]); and — if given — every entry node accepts entry_data_type.

paramplanRoutingPlan
paramregistryCapabilityRegistry
paramentry_data_typestr | None
= None

Raises:

  • RoutingPlanValidationError: with every problem found.

build_capability_registry

functionbuild_capability_registry(pipeline) -> CapabilityRegistry

Map each worker type to its declared (accepts, produces) capability.

parampipelinePipelineSpec

CapabilityRegistry

type aliasCapabilityRegistry = dict[str, tuple[frozenset[str], str | None]]

PipelineValidationError

errorPipelineValidationError

A PipelineSpec is internally inconsistent. Carries all findings.

RoutingPlanValidationError

errorRoutingPlanValidationError

A job's RoutingPlan is invalid for the deployed pipeline.

On this page