Skip to content

Infrastructure

Ready-made warmers and health checks for the usual dependencies. They are all duck-typed on the client you pass in, so wrappers and cluster clients work as long as the shape matches.

spec.warmers.append(PostgresWarmer(session_manager))
spec.health.add_check("postgres", PostgresHealthCheck(session_maker))

Postgres

pip install "servicewright[postgres]"
from servicewright.adapters.health.postgres import PostgresHealthCheck
from servicewright.adapters.warmers.postgres import PostgresWarmer
Signature Expects
Warmer PostgresWarmer(session_manager, query="SELECT 1", timeout=10.0, priority=0, raise_on_failure=True) an object with get_session() returning an async context manager
Check PostgresHealthCheck(session_maker, timeout=5.0) a callable returning an async context manager, e.g. async_sessionmaker

The two take different objects

The warmer calls session_manager.get_session() — the shape used by sqlalchemy-postgres-kit. The health check calls session_maker() directly — the shape of a plain SQLAlchemy async_sessionmaker. Pass each what it asks for.

Both run a trivial query and translate failures: the warmer raises PostgresWarmupError (which aborts startup unless raise_on_failure=False), the check returns False.

Redis

pip install "servicewright[redis]"
from servicewright.adapters.health.redis import RedisHealthCheck
from servicewright.adapters.warmers.redis import RedisWarmer
Signature
Warmer RedisWarmer(redis_client, max_connections=None, timeout=10.0, priority=0, raise_on_failure=True)
Check RedisHealthCheck(client, timeout=5.0)

The warmer pings once to verify connectivity, then fires max_connections concurrent pings (default 10) to actually fill the pool. Any failed ping raises RedisWarmupError — a pool that is half-broken at startup is not something to discover under traffic.

Both accept anything with an awaitable ping(), so Redis, RedisCluster and your own wrapper all fit. Cluster clients returning a per-node dict are handled: every node must report healthy.

If redis-client-kit happens to be installed, the warmer uses its richer health check instead of a bare ping.

Kafka

pip install "servicewright[kafka]"
from servicewright.adapters.warmers.kafka import KafkaProducerWarmer

KafkaProducerWarmer(producer, timeout=10.0, priority=0, raise_on_failure=True) fetches cluster metadata through the producer's client, which is what forces the connection and the metadata cache to be ready before the first real send. Failures become KafkaProducerWarmupError.

There is no bundled Kafka health check — a producer that cannot reach the cluster usually should not make the pod unready on its own. Register your own check if your service disagrees.

Import paths

Import from the submodule, as shown above. The package-level convenience imports fall back to None when an extra is missing:

from servicewright.adapters.warmers import KafkaProducerWarmer   # None without [kafka]
from servicewright.adapters.warmers.kafka import KafkaProducerWarmer   # ImportError, as it should

servicewright.adapters.health exports nothing at package level for the same reason — importing it never requires an extra.

Writing your own

Neither is worth a base class. A warmer is one method and a priority:

from servicewright import AsyncWarmer


class SearchIndexWarmer(AsyncWarmer):
    def __init__(self, client: SearchClient) -> None:
        super().__init__(raise_on_failure=False)   # nice to have, not required
        self._client = client

    @property
    def priority(self) -> int:
        return 10

    async def warmup(self) -> None:
        await self._client.load_index()

A health check is one method and no base class at all:

class SearchIndexCheck:
    def __init__(self, client: SearchClient) -> None:
        self._client = client

    async def check(self) -> bool:
        return await self._client.is_ready()
spec.warmers.append(SearchIndexWarmer(client))
spec.health.add_check("search", SearchIndexCheck(client))

Give both a timeout of their own. The warmup phase has a 60-second ceiling and readiness probes have whatever interval Kubernetes was configured with — neither is a good place to hang.