mrkeyoor.com_
Sat 19 Sept 06:45 UTC
PyPIDataupdated 19 Sept 2026

redis review

redis-py 8.1.0 is Redis's official Python client, with synchronous and asyncio command APIs plus pools, pipelines, transactions, pub/sub, streams, locks, Sentinel, Cluster, Search, Time Series, and Active-Active support. The client stays close to server command names instead of supplying an object model. Version 8.1.0 adds async maintenance notifications, list and set cardinality commands, stream limits, more Search and Time Series methods, and experimental HIMPORT fieldsets. It fixes hiredis push-data handling under RESP3 and Sentinel pool capacity after failover. Our base install was pure Python and typed.

Verdict

redis 8.1.0 installed in 0.2 seconds and occupied 3 MB as one package in our sandbox, with typed pure-Python code and no audit findings. Use redis-py when the service truly depends on Redis; keep it out when a process-local cache or a relational store answers the real requirement.

We installed it

Lab card: what happened when we installed redisScreenshot of redis documentation
Install✓ · 0.2s1 package on disk · 3 MB
Importimport redis in 1.04s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does redis install cleanly?

Yes. In a fresh container with an empty cache, pip install redis finished in 0.2s, leaving 1 package and 3 MB on disk. pip-audit reported no known vulnerabilities.

What does redis need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import redis succeeded in 1.04s, and the package ships py.typed for type checkers.

redis or valkey: which should you use?

valkey: Use it when the deployed servers and dependency policy follow the Valkey project. redis 8.1.0 installed in 0.2 seconds and occupied 3 MB as one package in our sandbox, with typed pure-Python code and no audit findings.

When should you not use redis?

The cache never leaves one process. functools.cache or a local cache removes a server, network hop, credentials, monitoring, and failure mode.

API stability4/5String, hash, list, set, sorted-set, stream, pool, pipeline, pub/sub, sync, and asyncio entry points retain command-shaped names. The 8.x line still changes decisions that reach application code: RESP3 is now the wire default, legacy response shapes have an opt-out migration flag, and Python 3.10 is the runtime floor. Version 8.1 adds commands without replacing core calls, while its HIMPORT fieldset API is marked experimental and may change in a future minor release.
Docs4/5Read the Docs covers URLs, TLS, pools, asyncio, pipelines, retries, locks, Sentinel, Cluster, RESP3, unified responses, and module APIs. The README states that replies are bytes by default and spells out the version 8 protocol migration. Developers still need server-command documentation for atomicity, memory, cluster slots, and blocking behavior. Lease safety, retry idempotency, pool capacity, pub/sub loss, and failover also require operational judgment beyond the client snippets.
Maintenance5/5Version 8.1.0 shipped on July 30, 2026, and GitHub showed the unarchived repository pushed on August 26, 2026 with 87 open issues and pull requests. The release adds async maintenance handling and current core, Search, Time Series, Stream, and Sentinel coverage. It also fixes pool loss after Sentinel failover, RESP3 plus hiredis push-data handling, decoding, geospatial query serialization, and connection cleanup. The supported-server table extends through Redis 8.8.
Ecosystem5/5The queue records 56,947,923 weekly downloads, and the repository has 13,618 GitHub stars. Framework caches, Celery and RQ deployments, session systems, internal services, and direct Redis applications commonly depend on redis-py. Its base install is one typed pure-Python package in our measurement, while optional hiredis and module clients expand capability and deployment surface. Valkey governance and object-mapping needs remain legitimate reasons to choose another client.

Use it if

  • A Python service already uses Redis for caching, counters, streams, queues, pub/sub, sessions, or coordination.
  • The project needs current Redis command coverage with similar method names in synchronous and asyncio code.
  • Connection pools, retry rules, Sentinel or Cluster routing, pipelines, and module commands should live in one official client.
  • The team is prepared to choose response decoding and RESP compatibility explicitly during the 8.x migration.
Skip it if

Setup reality

We installed redis 8.1.0 in a fresh Python 3.12 Bookworm sandbox. pip completed in 0.2 seconds and left one package using 3 MB. The distribution declares 11 direct dependencies, requires Python 3.10 or later, is pure Python, and ships py.typed. import redis succeeded in 1.04 seconds. pip-audit found zero known vulnerabilities, and the project uses the MIT License. Installing redis[hiredis] would add a compiled parser and change that deployment profile.

The package does not start Redis. Supply a reachable server URL, authentication, TLS trust, and database choice. redis:// is plaintext; rediss:// uses TLS. Replies are bytes unless decode_responses=True. In version 8, the default wire protocol is RESP3, while legacy_responses=True preserves older Python shapes. Set protocol=2 for RESP2 or legacy_responses=False when new code should adopt unified response values deliberately.

Each Redis client owns a pool unless one is supplied. Build a long-lived client or shared pool per process, bound max_connections, and close async clients with aclose(). Pipelines wrap commands in MULTI/EXEC by default; transaction=False only batches network round trips. Pub/sub consumes a dedicated connection and drops messages for disconnected subscribers. scan_iter avoids the blocking KEYS command, but it may repeat keys while the dataset changes.

Retries can duplicate non-idempotent writes or extend request latency. A Redis lock's timeout is its lease duration, not how long acquisition waits; work that outlives the lease can overlap another holder. Version 8.1.0's HIMPORT fieldset support is explicitly experimental and stores preparation state per physical connection. Direct calls can reprepare after reconnects, but a pipeline must include PREPARE and its SET operations on the same connection.

Patterns

Return strings instead of bytes connect-and-decode

import redis

client = redis.Redis(
    host='localhost',
    port=6379,
    decode_responses=True,
)
client.set('greeting', 'hello')
assert client.get('greeting') == 'hello'

Without `decode_responses=True`, string commands normally return bytes. Keep binary payload clients undecoded.

Connect with TLS from a URL tls-url

import os
import redis

client = redis.Redis.from_url(
    os.environ['REDIS_URL'],
    decode_responses=True,
    ssl_ca_certs='/etc/ssl/redis-ca.pem',
)

Use a `rediss://` URL for TLS. The URL path selects a logical database on deployments that support multiple databases.

Close an asyncio client async-lifecycle

import redis.asyncio as redis

async def round_trip() -> str | None:
    client = redis.Redis(decode_responses=True)
    try:
        await client.set('key', 'value')
        return await client.get('key')
    finally:
        await client.aclose()

The supported async namespace is `redis.asyncio`. Old code importing the separate aioredis package is obsolete.

Batch without a transaction pipeline-batch

with client.pipeline(transaction=False) as pipe:
    pipe.set('a', 1)
    pipe.incr('a')
    results = pipe.execute()

Pipelines default to MULTI/EXEC. Set `transaction=False` only when reducing round trips is enough and atomic execution is unnecessary.

Store and retrieve a hash hash-mapping

client.hset(
    'user:42',
    mapping={'name': 'Ada', 'plan': 'pro'},
)
user = client.hgetall('user:42')

Current code uses `hset(..., mapping=...)`; copied `hmset` examples target a deprecated command wrapper.

Create an expiring key once set-with-expiry

created = client.set(
    'job:claim:42',
    'worker-1',
    nx=True,
    ex=30,
)
if created is None:
    skip_claimed_job()

The 30-second expiry is a lease. If work can exceed it, another worker may acquire the same key before the first finishes.

Walk matching keys incrementally scan-keys

seen = set()
for key in client.scan_iter(match='session:*', count=500):
    if key in seen:
        continue
    seen.add(key)
    process(key)

SCAN's count is a hint and results may repeat while keys change. Avoid the blocking KEYS command on production datasets.

Listen on a pub/sub channel pubsub-listener

with client.pubsub(ignore_subscribe_messages=True) as pubsub:
    pubsub.subscribe('alerts')
    for message in pubsub.listen():
        handle(message['data'])

Pub/sub does not store messages for disconnected consumers. Use Redis streams when recovery and acknowledgement matter.

Bound a process-wide pool shared-pool

import redis

pool = redis.ConnectionPool(
    host='localhost',
    max_connections=20,
    decode_responses=True,
)
client = redis.Redis(connection_pool=pool)

Every Redis instance otherwise receives its own pool. Reuse the client or explicit pool instead of constructing one per web request.

Opt into unified RESP responses response-migration

client = redis.Redis(
    host='localhost',
    protocol=3,
    legacy_responses=False,
    decode_responses=True,
)

redis-py 8 defaults to RESP3 on the wire but keeps legacy Python shapes. This makes both migration choices explicit.

Update a value with WATCH optimistic-transaction

from redis.exceptions import WatchError

while True:
    try:
        with client.pipeline() as pipe:
            pipe.watch('balance:42')
            current = int(pipe.get('balance:42') or 0)
            pipe.multi()
            pipe.set('balance:42', current + 10)
            pipe.execute()
            break
    except WatchError:
        continue

WATCH detects a concurrent modification before EXEC. Keep the retry body free of external side effects because it may run more than once.

Read and acknowledge stream entries stream-consumer-group

messages = client.xreadgroup(
    groupname='workers',
    consumername='worker-1',
    streams={'jobs': '>'},
    count=10,
    block=5000,
)
for stream, entries in messages:
    for message_id, fields in entries:
        process(fields)
        client.xack(stream, 'workers', message_id)

A crash before XACK leaves the entry pending. Add pending-entry recovery instead of reading only new `>` messages forever.

Alternatives

PackageRegistryPick it when
valkeyPyPIUse it when the deployed servers and dependency policy follow the Valkey project.
coredisPyPIChoose it for an async-first, typing-focused Redis client after checking command and deployment coverage.
redis-omPyPIUse it when Pydantic-style models and indexed object access are the actual application contract.
aiocachePyPIUse it when code needs a cache abstraction that can swap Redis for memory or another backend.

More data guides

numpy · fsspec · pandas · pyarrow · sqlalchemy · s3fs · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.