Skip to content

Client caching and informers

For controller and admission callbacks, start with explicit informer reads. This page covers cached clients and direct subscriptions outside the operator runtime.

Cached resource methods

from cloudcoil.caching import Cache
from cloudcoil.client import Config
from cloudcoil.models.kubernetes.core.v1 import Pod

config = Config(
    namespace="default",
    cache=Cache(
        resources=[Pod],
        namespaces=["default"],
        mode="strict",
        wait_for_sync=True,
        max_items_per_resource=0,
    ),
)

async def read_pods():
    async with config:
        return await Pod.async_list(namespace="default")

The context manages the cache lifecycle. Nested scopes share it until the last scope exits; use separate Config instances for synchronous and asynchronous scopes. with config: and Pod.list(...) provide the synchronous equivalent.

mode="strict" requires cache-backed reads. mode="fallback" allows API reads when the cache cannot serve a request. Neither mode makes cached data strongly consistent. Writes go to the API server; watches subsequently update cached state. Explicit live clients use await Pod.async_client(config, cached=False).

Configuration

Option Meaning
resources=[Pod, ...] Preconfigure informer kinds before startup
namespaces=["default"] Watch one namespace; None watches all namespaces
label_selector, field_selector Restrict the watch using server selectors
resync_period Periodically relist resources
wait_for_sync, sync_timeout Wait for initial snapshots and bound that wait
max_items_per_resource Capacity per informer; 0 disables eviction
per_resource Resource-specific selectors, resync and capacity

The cache supports one namespace or all namespaces, not a disjoint namespace list. A selector or capacity limit can exclude objects; an empty result is not proof of cluster-wide absence. Use live reads where that distinction matters.

config.cache.status() reports readiness and counts. with config.cache.pause(): temporarily bypasses caching. strict_mode() and fallback_mode() temporarily change the read policy and restore it on exit.

Direct subscriptions

Register handlers before entering the context so they receive initial objects:

import asyncio
from cloudcoil.caching import Cache
from cloudcoil.client import Config
from cloudcoil.models.kubernetes.core.v1 import Pod

async def monitor(stop: asyncio.Event) -> None:
    config = Config(cache=Cache(resources=[Pod], wait_for_sync=True))
    await config.async_initialize()
    informer = config.cache.get_informer(Pod, sync=False)

    @informer.on_add
    def added(pod):
        print("Added", pod.namespace, pod.name)

    @informer.on_update
    def updated(old, new):
        print("Updated", new.namespace, new.name)

    @informer.on_delete
    def deleted(pod):
        print("Deleted", pod.namespace, pod.name)

    async with config:
        # Async informer storage is local: get/list are synchronous.
        print(len(informer.list()))
        await stop.wait()

Async informers accept sync or async callbacks. For synchronous applications, use get_informer(Pod, sync=True) and register synchronous handlers before with config:. Direct informer objects are lower-level cache views; copy returned resources before editing. Controller ctx.cached(...) and admission request.cached(...) return deep copies. Use controllers when you need retry queues and reconciliation rather than raw events.