Skip to main content

varco-redis

PyPI version Redis backend for varco.

Provides two independent subsystems backed by Redis:

  • RedisEventBus — Pub/Sub event bus implementing AbstractEventBus from varco_core
  • RedisCache — async cache backend implementing CacheBackend from varco_core

Installation

pip install varco-redis
# or with uv:
uv add varco-redis

Event bus quick start

from varco_redis import RedisEventBus, RedisEventBusSettings
from varco_core.event import BusEventProducer, EventConsumer, listen, Event


# Define your events
class OrderPlacedEvent(Event):
    __event_type__ = "order.placed"
    order_id: str
    total: float


# Configure the bus
settings = RedisEventBusSettings(url="redis://localhost:6379/0")


async def main():
    async with RedisEventBus(settings) as bus:
        # --- Consumer side ---
        class OrderConsumer(EventConsumer):
            @listen(OrderPlacedEvent, channel="orders")
            async def on_placed(self, event: OrderPlacedEvent) -> None:
                print(f"Order placed: {event.order_id}")

        OrderConsumer().register_to(bus)

        # Give the Pub/Sub subscription time to establish
        import asyncio

        await asyncio.sleep(0.1)

        # --- Producer side ---
        producer = BusEventProducer(bus)
        await producer._produce(
            OrderPlacedEvent(order_id="abc", total=99.0),
            channel="orders",
        )

Event bus configuration

from varco_redis import RedisEventBusSettings

settings = RedisEventBusSettings(
    url="redis://redis.internal:6379/0",  # Redis connection URL
    channel_prefix="prod:",  # optional — "orders" → "prod:orders"
    socket_timeout=5.0,  # seconds, None = no timeout
)
Field Default Env var Description
url "redis://localhost:6379/0" VARCO_REDIS_URL Redis connection URL
channel_prefix "" VARCO_REDIS_CHANNEL_PREFIX Prepended to every channel name
decode_responses False — Must be False — bus expects raw bytes
socket_timeout None VARCO_REDIS_SOCKET_TIMEOUT Socket operation timeout in seconds
redis_kwargs {} — Extra kwargs for redis.asyncio.from_url()

Event bus lifecycle

# Explicit lifecycle
bus = RedisEventBus(settings)
await bus.start()  # connects to Redis, starts listener task
# ... use bus ...
await bus.stop()  # cancels listener, closes connection

# Context manager (recommended)
async with RedisEventBus(settings) as bus:
    ...

Wildcard subscriptions

Subscribing with channel=CHANNEL_ALL (the default) uses Redis PSUBSCRIBE "*" — the handler receives events from every channel published to this Redis instance. Use channel_prefix to scope channels to your service and avoid cross-service interference:

# All events with prefix "svc-a:" on this Redis
settings = RedisEventBusSettings(channel_prefix="svc-a:")
bus.subscribe(MyEvent, handler)  # receives from all "svc-a:*" channels

Cache quick start

from varco_redis.cache import RedisCache, RedisCacheSettings

cache_settings = RedisCacheSettings(
    url="redis://localhost:6379/0",
    key_prefix="myapp:",  # all keys stored as "myapp:<key>"
    default_ttl=300,  # seconds; None = no expiry
)

async with RedisCache(cache_settings) as cache:
    await cache.set("user:42", {"name": "Alice"})
    user = await cache.get("user:42")  # returns dict or None
    await cache.delete("user:42")
    await cache.clear()  # removes all "myapp:*" keys

Cache configuration

from varco_redis.cache import RedisCacheSettings

settings = RedisCacheSettings(
    url="redis://localhost:6379/0",
    key_prefix="prod:",
    default_ttl=600,
    socket_timeout=2.0,
)
Field Default Env var Description
url "redis://localhost:6379/0" VARCO_REDIS_CACHE_URL Redis connection URL
key_prefix "" VARCO_REDIS_CACHE_KEY_PREFIX Prepended to every stored key
default_ttl None VARCO_REDIS_CACHE_DEFAULT_TTL Default TTL in seconds; None = no expiry
decode_responses False — Must be False — cache stores raw bytes
socket_timeout None VARCO_REDIS_CACHE_SOCKET_TIMEOUT Socket operation timeout in seconds
redis_kwargs {} — Extra kwargs for redis.asyncio.from_url()

Layered cache (L1 memory + L2 Redis)

from varco_core.cache import InMemoryCache, LayeredCache, TTLStrategy
from varco_redis.cache import RedisCache, RedisCacheSettings

l1 = InMemoryCache(strategy=TTLStrategy(60))  # fast in-process layer
l2 = RedisCache(RedisCacheSettings(key_prefix="app:"))  # shared Redis layer

async with LayeredCache(l1, l2, promote_ttl=60) as cache:
    await cache.set("product:1", product, ttl=300)
    # First read: L1 miss → L2 hit → promote to L1
    result = await cache.get("product:1")
    # Second read: served from L1 (no network round-trip)

Cache + CachedService with cross-process invalidation

from varco_core.cache import CachedService, LayeredCache, TTLStrategy, InMemoryCache
from varco_redis.cache import RedisCache, RedisCacheSettings
from varco_redis import RedisEventBus, RedisEventBusSettings

bus_settings = RedisEventBusSettings(url="redis://localhost:6379/0")
cache_settings = RedisCacheSettings(url="redis://localhost:6379/0", key_prefix="posts:")

async with (
    RedisEventBus(bus_settings) as bus,
    LayeredCache(
        InMemoryCache(strategy=TTLStrategy(60)),
        RedisCache(cache_settings),
        promote_ttl=60,
    ) as cache,
):
    cached = CachedService(
        post_service,
        cache,
        namespace="posts",
        default_ttl=300,
        bus=bus,  # publish invalidation events
        bus_channel="posts.invalidations",  # other nodes subscribe here
    )

    post = await cached.get(42)  # cached
    posts = await cached.list()  # cached
    await cached.update(42, {"title": "New"})  # invalidates + publishes event

Distributed rate limiting

RedisRateLimiter (varco_redis.rate_limit) implements varco_core.resilience.RateLimiter as a distributed sliding window over a Redis sorted set with an atomic Lua script — use it instead of InMemoryRateLimiter whenever more than one process/pod enforces the same limit (an in-memory limiter's counter is per-process, so N pods each allow the configured rate, not the total).

from varco_redis.rate_limit import RedisRateLimiter
from varco_core.resilience import RateLimitConfig

limiter = RedisRateLimiter(redis_client, RateLimitConfig(rate=100, period=1.0))


@rate_limit(limiter)
async def call_external() -> None: ...

Exported from varco_redis.__init__.


Distributed concurrency limiting

RedisBulkhead (varco_redis.bulkhead, Plan 005 Phase 8 / U-7's second leg) is the distributed sibling of varco_core.resilience.Bulkhead — a fleet-wide semaphore instead of a per-process one. Rate limiting and concurrency limiting are different primitives: a service can be well within its RedisRateLimiter budget and still overwhelm a downstream dependency with concurrent in-flight calls (e.g. a burst of long-running requests within the same second). Same public surface as Bulkhead (call()/protect()/available_slots()), backed by a Redis sorted set of holders scored by acquisition time and an atomic Lua acquire/release mirroring varco_redis.lock's token-guarded pattern. A holder that crashes without releasing has its slot reclaimed once slot_ttl elapses — no heartbeat process or cleanup job required.

from varco_redis.bulkhead import RedisBulkhead
from varco_core.resilience.bulkhead import BulkheadConfig

db_bulkhead = RedisBulkhead(BulkheadConfig(max_concurrent=10, max_wait=0.5))
await db_bulkhead.connect()

result = await db_bulkhead.call(fetch_user, user_id)


@db_bulkhead.protect
async def fetch_order(order_id: str) -> Order: ...

DI: RedisBulkheadConfiguration is an opt-in @Configuration (not auto-scanned by container.scan("varco_redis") — a bulkhead's max_concurrent is app-specific tuning, not a package-wide default):

from providify import DIContainer
from varco_redis.bulkhead import RedisBulkheadConfiguration

container = DIContainer()
await container.ainstall(RedisBulkheadConfiguration)
db_bulkhead = await container.aget(RedisBulkhead)

Exported from varco_redis.__init__.


DI integration

from providify import DIContainer
from varco_core.cache import CacheBackend
from varco_redis.cache import RedisCacheConfiguration

container = DIContainer()
await container.ainstall(RedisCacheConfiguration)

cache = await container.aget(CacheBackend)  # RedisCache singleton
await container.ashutdown()

Running tests

# Unit tests (no Redis required)
uv sync
uv run pytest

# Integration tests (requires Docker)
VARCO_RUN_INTEGRATION=1 uv run pytest -m integration

Connection settings

RedisConnectionSettings is a structured, env-var loadable config object that produces a URL and kwargs for redis.asyncio.

Plain connection

import redis.asyncio
from varco_redis.connection import RedisConnectionSettings

conn = RedisConnectionSettings(host="my-redis", port=6379, db=0)

client = redis.asyncio.from_url(conn.to_url(), **conn.to_redis_kwargs())
# to_url()  → "redis://my-redis:6379/0"

From environment variables

REDIS_HOST=my-redis
REDIS_PORT=6379
REDIS_DB=1
conn = RedisConnectionSettings.from_env()
client = redis.asyncio.from_url(conn.to_url(), **conn.to_redis_kwargs())

With password (AUTH)

conn = RedisConnectionSettings(host="my-redis", password="s3cret")
# to_url() → "redis://:s3cret@my-redis:6379/0"
client = redis.asyncio.from_url(conn.to_url(), **conn.to_redis_kwargs())

With ACL username + password (Redis 6+)

conn = RedisConnectionSettings(
    host="my-redis",
    username="alice",
    password="s3cret",
)
# to_url() → "redis://alice:s3cret@my-redis:6379/0"

With TLS / SSL

from varco_core.connection import SSLConfig
from pathlib import Path

ssl = SSLConfig(ca_cert=Path("/etc/ssl/redis-ca.pem"), verify=True)
conn = RedisConnectionSettings.with_ssl(ssl, host="prod-redis")
# to_url()           → "rediss://prod-redis:6379/0"
# to_redis_kwargs()  → {"decode_responses": False, "ssl": <SSLContext>}

client = redis.asyncio.from_url(conn.to_url(), **conn.to_redis_kwargs())

Or from env:

REDIS_HOST=prod-redis
REDIS_SSL__CA_CERT=/etc/ssl/redis-ca.pem
REDIS_SSL__VERIFY=true

With mTLS (client certificates)

ssl = SSLConfig(
    ca_cert=Path("/etc/ssl/ca.pem"),
    client_cert=Path("/etc/ssl/client.crt"),
    client_key=Path("/etc/ssl/client.key"),
)
conn = RedisConnectionSettings.with_ssl(ssl, host="prod-redis")

Connection settings reference

Env var Default Description
REDIS_HOST localhost Redis server hostname
REDIS_PORT 6379 Redis server port
REDIS_DB 0 Database index (0–15)
REDIS_PASSWORD — AUTH password
REDIS_USERNAME — ACL username (Redis 6+)
REDIS_DECODE_RESPONSES false Return strings instead of bytes
REDIS_SOCKET_TIMEOUT — Socket timeout in seconds
REDIS_SSL__CA_CERT — Path to CA certificate
REDIS_SSL__CLIENT_CERT — Path to client certificate (mTLS)
REDIS_SSL__CLIENT_KEY — Path to client private key (mTLS)
REDIS_SSL__VERIFY true TLS peer verification

Note: RedisConnectionSettings is a general-purpose connection config. RedisEventBusSettings (used by RedisEventBus) is a separate, independent class — existing code that uses the event bus is unaffected.


Delivery semantics

Redis Pub/Sub provides at-most-once delivery — messages published while no subscriber is connected are silently dropped. If you need at-least-once or exactly-once delivery, use Redis Streams (planned as varco_redis.streams in a future release) or switch to varco_kafka.


Migration 1.x → 2.0

The no-op @Configuration aliases (RedisEventBusConfiguration, RedisChannelManagerConfiguration, RedisStreamConfiguration) were removed. Register the bus via scan instead:

# Before (1.x)
await container.ainstall(RedisEventBusConfiguration)

# After (2.0)
from varco_redis.di import bootstrap

bootstrap(container)  # or: container.scan("varco_redis", recursive=True)

The opt-in cache (RedisCacheConfiguration, RedisLayeredCacheConfiguration) is unchanged — still installed with await container.ainstall(...).

Metadata

Release files for varco-redis 3.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for varco-redis 3.2.0
File Size Uploaded Provenance Actions
varco_redis-3.2.0.tar.gz 198.9 kB With provenance Details for varco_redis-3.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for varco-redis 3.2.0
File Interpreter ABI Platform Actions
varco_redis-3.2.0-py3-none-any.whl Python 3 none any Details for varco_redis-3.2.0-py3-none-any.whl

Total release size: 342.1 kB

Release files / varco_redis-3.2.0.tar.gz

Download URL varco_redis-3.2.0.tar.gz
Size 198.9 kB
Tags Source
SHA-256 checksum
How to use checksums
3281f67aa9edf681b91a481030f27b79ef36d9c7cd3748515933e514ab5447cc
BLAKE2b-256 checksum
How to use checksums
c46a27498f35c59a107ad81d0465dedcee6894921ed9d3474ce27ca7b9491d0a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Release files / varco_redis-3.2.0-py3-none-any.whl

Download URL varco_redis-3.2.0-py3-none-any.whl
Size 143.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dfd6e379362572195764b7f62c1e22689951505d210b1a786bd91474af34f2b0
BLAKE2b-256 checksum
How to use checksums
2f7bca93bc9acc4c3e7568d1e1d3e11d631f8d71b2b6ff133052cf9c1e3ef2bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

Security for varco-redis 3.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for varco_redis-3.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page