Skip to main content

Greyhorse Redis library

Greyhorse framework library for Redis support.

The primary API is the pieces, not a ready-made module:

Piece Sync Async
material (the engine's own factory) RedisSyncFragment RedisAsyncFragment
lifecycle (start/stop/health) RedisSyncBorder RedisAsyncBorder
access (hand out clients/pipelines) RedisSyncConnections RedisAsyncConnections

An application lists the ones it needs on its own Module, alongside pieces from any other storage library. RedisSyncModule/RedisAsyncModule bundle the trio for the common single-storage case -- sugar, not the primary surface. See examples/.

Usage

Every snippet below is a runnable program, checked against a live Redis. The longer, commented versions live in examples/ and are executed by the test suite, so they cannot rot silently.

One engine, one client

RedisSyncModule is the ready-made bundle for the single-storage case. The config reaches the engine's constructor through the same args={Type: value} door every greyhorse.strand resource uses -- there is no redis-specific wiring.

from greyhorse.run import wrap_sync
from greyhorse.strand import running

from greyhorse_redis import EngineConf, RedisSyncClientCtx, RedisSyncModule


def main() -> None:
    conf = EngineConf(dsn='redis://localhost:6379/0')

    with running(RedisSyncModule, args={EngineConf: conf}) as module:
        client_ctx = module.get(RedisSyncClientCtx).unwrap()
        with client_ctx as client:
            client.set('greeting', 'hello')
            print(client.get('greeting'))


wrap_sync(main)

The engine starts when the module starts and stops when it stops; client is an ordinary redis.Redis borrowed for the length of the with block.

Async

Same declaration, async twin of each piece. running() stays a plain sync context manager even here -- it starts a module, it is not an I/O operation.

from greyhorse.run import run
from greyhorse.strand import running

from greyhorse_redis import EngineConf, RedisAsyncClientCtx, RedisAsyncModule


async def main() -> None:
    conf = EngineConf(dsn='redis://localhost:6379/0')

    with running(RedisAsyncModule, args={EngineConf: conf}) as module:
        client_ctx = module.get(RedisAsyncClientCtx).unwrap()
        async with client_ctx as client:
            await client.set('greeting', 'hello')
            print(await client.get('greeting'))


run(main)

Pipelines

A client is Shared: every command takes effect the moment the server sees it, so there is nothing to commit. A pipeline is Mut -- commands buffer while the window is open and apply() sends MULTI/EXEC.

pipe_ctx = module.get(RedisSyncPipelineCtx).unwrap()

with pipe_ctx as pipe:
    pipe.set('a', '1')
    pipe.set('b', '2')
    pipe_ctx.apply()          # both writes land here, or neither

A forgotten apply() and an exception inside the block both discard the buffer without sending anything. That is the whole reason a pipeline is Mut and a client is not.

A consumer that knows nothing about greyhorse

The point of the split: the class that uses Redis takes a context by TYPE and imports nothing from this package. Only the component says how it is wired.

from typing import ClassVar

from greyhorse.strand import (
    Application, Component, Handle, HttpBinding, HttpGateway, Shared, Use,
)

from greyhorse_redis import (
    EngineConf, RedisSyncClientCtx, RedisSyncConnections, RedisSyncModule,
    SyncRedisEngine,
)


class PingApi:
    def __init__(self, conn: RedisSyncClientCtx) -> None:
        self._conn = conn

    def ping(self) -> bool:
        with self._conn as client:
            return bool(client.ping())


class PingComponent(Component):
    imports: ClassVar = Shared[SyncRedisEngine]
    providers: ClassVar = RedisSyncConnections
    exports: ClassVar = PingApi
    handlers: ClassVar = Handle(PingApi.ping, HttpBinding.Route(verb='GET', path=''))


class App(RedisSyncModule):
    name = 'ping-app'
    components: ClassVar = {'ping': Use(PingComponent)}


app = Application(App, args={EngineConf: EngineConf(dsn='redis://localhost:6379/0')},
                  gateways=(HttpGateway(),))

with app.started() as running_app:
    print(running_app.gateway(HttpGateway).dispatch('GET', '/ping', {}))

imports/providers are the demand side -- the component asks for a window onto the engine and names the provider that turns it into a context. exports/handlers are what it hands back out. Note that an export reaches consumers through the wiring, NOT through module.get(): that door serves the module's own products (the contexts above), not a component's exports.

Subclassing RedisSyncModule fits exactly this shape: one storage, one consumer, same floor. For two independent Redis instances, list the pieces (RedisSyncFragment, RedisSyncBorder, RedisSyncConnections) on your own Module instead -- examples/04_multi_storage.py does that in full.

Configuration

EngineConf is what the engine is built from:

EngineConf(
    dsn='redis://user:password@localhost:6379/0',
    timeout_seconds=5,
    connect_timeout_seconds=5,
    pool_max_connections=4,
    client_name='greyhorse-redis',
    decode_responses=False,
)

rediss:// (TLS) and unix:///var/run/redis.sock are accepted too.

RedisSettings is the environment side. It reads REDIS_* (and .env), and assembles a DSN from the parts when REDIS_DSN is not given:

from greyhorse_redis import EngineConf, RedisSettings

settings = RedisSettings()                    # REDIS_HOST, REDIS_PORT, ...
conf = EngineConf(dsn=settings.dsn)

REDIS_PASSWORD_FILE points at a Docker/Kubernetes secret file and wins over REDIS_PASSWORD. A missing, unreadable or blank file is an error at config time, not a silent fallback to no password.

Credentials. repr(), str() and model_dump_json() come out redacted -- host and user stay visible, the secret does not -- so a config that reaches a log, an f-string or a traceback does not leak. model_dump() and dict(settings) deliberately keep the credential: that is what builds the connection pool. Dumps are for machines; do not log one.

How to build

  • Install the project

    uv venv

    uv sync --extra hiredis

    source .venv/bin/activate

  • Format code commands

    ruff check --unsafe-fixes --fix

    ruff format

  • Run the tests

    Everything except the live-server tests runs with no Redis at all. For the rest, bring one up and point the suite at it:

    docker compose -f tests/docker-compose.yml up -d --wait

    REDIS_TEST_URI=redis://localhost:6380/15 uv run pytest tests -q

    docker compose -f tests/docker-compose.yml down -v

    Host port 6380 is a default, not a fixture: if it is taken on your machine, set REDIS_HOST_PORT and point the suite at the same number.

    REDIS_HOST_PORT=6390 docker compose -f tests/docker-compose.yml up -d --wait

    REDIS_TEST_URI=redis://localhost:6390/15 uv run pytest tests -q

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

greyhorse_redis-0.5.5.tar.gz (175.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

greyhorse_redis-0.5.5-py3-none-any.whl (59.2 kB view details)

Uploaded Python 3

File details

Details for the file greyhorse_redis-0.5.5.tar.gz.

File metadata

  • Download URL: greyhorse_redis-0.5.5.tar.gz
  • Upload date:
  • Size: 175.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.0

File hashes

Hashes for greyhorse_redis-0.5.5.tar.gz
Algorithm Hash digest
SHA256 5fb76f7bbea8173532c88cf55160849ba982f8b1c7c1393376d82c9086092d41
MD5 7d1f829959ebeea51a1e0c600a1f7895
BLAKE2b-256 b56eddcc0cf10e87b6467901d299c91325ddea9fdc4886dbbc22b83d8550a6e5

See more details on using hashes here.

File details

Details for the file greyhorse_redis-0.5.5-py3-none-any.whl.

File metadata

File hashes

Hashes for greyhorse_redis-0.5.5-py3-none-any.whl
Algorithm Hash digest
SHA256 441490d3d3a0f716543528b4dede43e05aea6b22415bdff9764b2a90c7c8855d
MD5 e9ca03273f1625bd7f03875bfe918d71
BLAKE2b-256 aea380802daa9571130721950d8f57ab8e1331360f458dbfb67f402ca785bdb6

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.5 This release

2 files

0.1.1

2 files

0.1

2 files

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