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.
paramexchangeRMQExchangeConfigThe 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_collectionstrA collection name for application use. Warren does not read this field.
paramcompletion_collectionstrA collection name for application use. Warren does not read this field.
paramfinal_data_typestrThe 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= NoneOptional 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.
paramfactoryWorkerFactoryCallable that creates the worker given a WorkerFactoryContext.
parambinding_keystr | None= NoneBackward-compatible constructor alias for one key.
parampublishPublishSpec | None= NoneHow 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= NoneDeclared output data_type (None if terminal/none).
paramneeds_document_fetcherbool= FalseIf True, the runner passes a GetDocumentFunc to the factory. Otherwise passes None.
paramneeds_document_storebool= FalseIf 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 bothbinding_keyandbinding_keysare 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= NoneStatic routing key (direct/topic only).
paramroute_funcRouteFunc | None= NonePer-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_namestrRunner-assigned unique name for this worker.
paramworker_typestrThe 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 | NoneRaw 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 | NoneRaw async Redis client — same rationale.
paramdatabase_namestrMongo 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= NoneDeclared output data_type from the spec.
paramget_document_funcGetDocumentFunc | None= NonePresent when needs_document_fetcher is set on the spec. None otherwise.
paramdocument_storeDocumentStoreInterface | None= NonePresent when needs_document_store is set. None otherwise.
paramscoped_databaseScopedDatabase | None= Noneattributecurrent_scopestr | NoneRun
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).
paramconfigRuntimeConfigruntime infrastructure configuration.
paramworker_namestrunique worker instance identifier.
paramworker_typestrname of the worker type (used for queue naming).
paramworker_specWorkerSpecspec defining collections, factory, exchange wiring (binding_keys, publish), and flags.
paramexchangeRMQExchangeConfigthe pipeline's exchange (from PipelineSpec.exchange) this worker consumes from and publishes to.
paramdocument_fetcherGetDocumentFunc | None= Noneoptional 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= Noneoptional 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= Noneoptional override for the results stores. Default: one DefaultResultsStore per collection in worker_spec.collections.
paraminfraRuntimeInfra | None= Noneoptional 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 () -> NoneWire connections, stores, worker, publishers, and consumer.
- Set up infrastructure (pubsub connection, MongoDB, Redis)
- Build default stores/fetcher for any not injected
- Create worker via spec's factory (async)
- Wrap worker (subclass hook, e.g. for failure injection)
- Run worker-level setup (start owned async resources)
- Create publishers for downstream routing
- 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. Onbackend: 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.
paramconfigRuntimeConfigRuntime configuration with backend: memory.
parampipelinePipelineSpecThe pipeline to run.
paraminfraRuntimeInfraShared infrastructure. The caller creates and closes it.
paramstoresMemoryStoreRegistryShared store registry.
paraminstancesdict[str, int] | None= NoneInstance count per worker type; unspecified types get one.
Returns
list[WorkerRunnerBase]Runners, not yet set up.
Raises:
ValueError: Ifinstancesnames 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) -> NoneSet 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= NoneCalled 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
modelRuntimeConfigTop-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) -> RuntimeConfigLoad configuration from a YAML file.
String values can use ${NAME}. The value comes from the
environment.
parampathPathPath to the YAML config file.
Returns
RuntimeConfigPopulated RuntimeConfig instance.
Raises:
FileNotFoundError: If the config file doesn't exist.ValueError: If an environment variableNAMEis not set.pydantic.ValidationError: For an unknown key or a bad value.
RuntimeRMQConfig
modelRuntimeRMQConfigRabbitMQ 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
modelRuntimeKafkaConfigKafka 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
modelMongoDBConfigattributeuriSecretStr | None= Noneattributehoststr= 'localhost'attributeportint= 27017attributedatabasestr= 'distributed_processing'attributeusernamestr | None= NoneattributepasswordSecretStr | None= Noneattributeauth_sourcestr | None= Noneattributetlsbool= Falseattributemax_pool_sizeint | None= Noneattributeserver_selection_timeout_msint | None= NoneRedisConfig
modelRedisConfigattributeurlSecretStr | None= Noneattributehoststr= 'localhost'attributeportint= 6379attributeusernamestr | None= NoneattributepasswordSecretStr | None= Noneattributedbint= 0attributesslbool= Falseattributemax_connectionsint | None= Noneattributesocket_timeoutfloat | None= NoneCacheConfig
modelCacheConfigExpiry for cached payloads, in seconds.
attributecache_ttl_secondsint= 3600DocumentsConfig
modelDocumentsConfigFetched payload cache expiry and local-file resolution.
attributepath_base_dirPath | None= NoneDirectory path document locations are read from. Unset (the default) disables the path resolver.
RetentionConfig
modelRetentionConfigMongoDB job record expiry, in seconds.
attributejob_records_ttl_secondsint | None= NoneDelete 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= NoneDelete 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
modelRuntimeRetryConfigRetry worker toggle plus the retry policy every consumer manager applies.
attributeenabledbool= FalseIn-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= 30Lifetime 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
modelStartupConfigConnection attempts and bounded exponential backoff at startup.
attributeattemptsint= 1Number of connection attempts. After the last failed attempt, the connection error is raised.
attributeinitial_delay_secondsfloat= 1.0Delay before the second attempt.
attributemax_delay_secondsfloat= 30.0The delay doubles after each attempt, up to this value.
ScopingConfig
modelScopingConfigContent namespace settings (see warren/docs/scoping.md).
attributeenabledbool= FalseStore content in a namespace for each message scope.
attributerequiredbool= TrueA 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
classRuntimeInfraAll 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 | Noneattributeredis_clientRedis | Noneattributepubsub_connection_managerAnycreate_runtime_infrastructure
functioncreate_runtime_infrastructureasync (config, *, sleep=asyncio.sleep) -> RuntimeInfraCreate 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).
paramconfigRuntimeConfigRuntime infrastructure configuration.
paramsleepCallable[[float], Awaitable[None]]= asyncio.sleepDelay between startup attempts.
Returns
RuntimeInfraInitialized connections ready for use.
Raises:
Exception: The connection error of the last attempt, re-raised unchanged.
close_runtime_infrastructure
functionclose_runtime_infrastructureasync (infra) -> NoneClose all connections. Best-effort — logs and continues on errors.
paraminfraRuntimeInfraInfrastructure to close.
require_injected
functionrequire_injected(config, **components) -> NoneFail 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.
paramconfigRuntimeConfigRuntime configuration.
paramcomponentsobjectName to injected value, for each component the runner needs. None means "not injected".
Raises:
WarrenError: On the memory backend when any value isNone.
create_connection_manager
functioncreate_connection_manager(config) -> AnyCreate the (unset-up) pubsub connection manager for the backend.
The caller is responsible for setup() / teardown().
paramconfigRuntimeConfigRuntime configuration; config.backend selects the backend.
Returns
AnyRMQConnectionManager, 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) -> PublisherInterfaceCreate a publisher onto exchange for the configured backend.
paramconfigRuntimeConfigRuntime configuration; config.backend selects the backend.
paramconnection_managerAnyConnection manager from create_connection_manager (must match the backend).
paramexchangeRMQExchangeConfigThe 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= NoneStatic routing key (RabbitMQ topic/direct only).
paramroute_funcRouteFunc | None= NonePer-message routing function (RabbitMQ topic/direct; a no-op on fanout). Kafka ignores route and route_func: the pipeline is fanout.
paramnamestr | None= NoneOptional publisher name for logging.
Returns
PublisherInterfaceAn unset-up RMQPublisher, KafkaPublisher or MemoryPublisher implementing PublisherInterface.
Raises:
WarrenError: ifexchangeis 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) -> ConsumerManagerInterfaceCreate 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 withbinding_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.
paramconfigRuntimeConfigRuntime configuration; config.backend selects the backend.
paramconnection_managerAnyConnection manager from create_connection_manager (must match the backend).
paramexchangeRMQExchangeConfigThe exchange this worker consumes from. On Kafka, only the type is used (it must be fanout). The topic comes from config.kafka.topic.
paramworker_typestrWorker type name — the queue suffix (RMQ) or the group suffix (Kafka).
paramconsumerMessageConsumerInterfaceThe worker that processes each message.
parambinding_keystr | None= NoneBackward-compatible alias for one binding key.
parambinding_keystuple[str, ...]= ()Queue binding patterns; ignored on fanout.
paramdata_publisherPublisherInterface | None= NoneDownstream publisher for successful results, or None if the worker is terminal.
paramcontrol_publisherPublisherInterface | None= NonePublisher for lifecycle envelopes (soft/hard-failure), or None for seek-back/requeue fallback.
paramobserver_publisherPublisherInterface | None= NonePublisher echoing successful results to the observer exchange (RabbitMQ direct pipelines only).
paramretry_configRetryConfig | None= NoneRetry policy override (defaults applied by the consumer manager when None).
paramextract_identity_funcExtractMessageIdentityFunc | None= NoneMessage-identity extractor override.
parampublish_hard_failuresbool= TrueWhether to publish a hard-failure envelope on terminal failure.
Returns
ConsumerManagerInterfaceAn unset-up RMQConsumerManager, KafkaConsumerManager or MemoryConsumerManager implementing ConsumerManagerInterface.
Raises:
ValueError: if bothbinding_keyandbinding_keysare set.WarrenError: ifexchangeis 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= NoneDirectory 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) -> NoneValidate a pipeline's exchange wiring; raise on any error.
parampipelinePipelineSpecthe pipeline to validate.
paramloggerlogging.Logger | None= Noneoptional 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) -> NoneValidate 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.
paramplanRoutingPlanparamregistryCapabilityRegistryparamentry_data_typestr | None= NoneRaises:
RoutingPlanValidationError: with every problem found.
build_capability_registry
functionbuild_capability_registry(pipeline) -> CapabilityRegistryMap each worker type to its declared (accepts, produces) capability.
parampipelinePipelineSpecCapabilityRegistry
type aliasCapabilityRegistry = dict[str, tuple[frozenset[str], str | None]]PipelineValidationError
errorPipelineValidationErrorA PipelineSpec is internally inconsistent. Carries all findings.
RoutingPlanValidationError
errorRoutingPlanValidationErrorA job's RoutingPlan is invalid for the deployed pipeline.