Reddish
An async redis client for python with minimal API
Install / Use
/learn @stereobutter/ReddishREADME
reddish - a friendly redis client with sync and async api
Features
- both sync and async API
- bring your own connection - supports TCP, TCP+TLS and Unix domain sockets
- supports multiple networking libaries and event loops
- sync API using the standard library
socketmodule - async API using
asyncio's,trio's oranyio's stream primitives
- sync API using the standard library
- minimal API so you don't have to relearn how to write redis commands
- supports allmost every redis command (including modules) except for
SUBSCRIBE,PSUBSCRIBE,SSUBSCRIBE,MONITORandCLIENT TRACKING[^footnote] - parses replies into python types if you like (powered by pydantic)
- works with every redis version and supports both
RESP2andRESP3protocols
[^footnote]: Commands like SUBSCRIBE or MONITOR take over the redis connection for listening to new events barring regular commands from being issued over the connection.
Installation
pip install reddish # install just with support for socket and asyncio
pip install reddish[trio] # install with support for trio
pip install reddish[anyio] # install with support for anyio
Minimal Example - sync version
import socket
from reddish import Command
from reddish.clients.socket import Redis
redis = Redis(socket.create_connection(('localhost', 6379)))
assert b'PONG' == redis.execute(Command('PING'))
Minimal Example - async version (asyncio)
import asyncio
from reddish.clients.asyncio import Redis
redis = Redis(await asyncio.open_connection('localhost', 6379))
assert b'PONG' == await redis.execute(Command('PING'))
Minimal Example - async version (trio)
import trio
from reddish.clients.trio import Redis
redis = Redis(await trio.open_tcp_stream('localhost', 6379))
assert b'PONG' == await redis.execute(Command('PING'))
Minimal Example - async version (anyio)
import trio
from reddish.backends.anyio import Redis
redis = Redis(await anyio.connect_tctp('localhost', 6379))
assert b'PONG' == await redis.execute(Command('PING'))
Usage
Command with a fixed number of arguments
# simple command without any arguments
Command('PING')
# commands with positional arguments
Command('ECHO {}', 'hello world')
# commands with keyword arguments
Command('SET {key} {value}', key='foo', value=42)
Handling invalid commands
from reddish import CommandError
try:
await redis.execute(Command("foo"))
except CommandError as error:
print(error.message) # >>> ERR unknown command `foo`, with args beginning with:
Parsing replies
# return response unchanged from redis
assert b'42' == await redis.execute(Command('ECHO {}', 42))
# parse response as type
assert 42 == await redis.execute(Command('ECHO {}', 42).into(int))
# handling replies that won't parse correctly
from reddish import ParseError
try:
await redis.execute(Command('PING').into(int))
except ParseError as error:
print(error.reply)
# use any type that works with pydantic
from pydantic import Json
import json
data = json.dumps({'alice': 30, 'bob': 42})
response == await redis.execute(Command('ECHO {}', data).into(Json))
assert response == json.loads(data)
Commands with variadic arguments
from reddish import Args
# inlining arguments
Command('DEL {keys}', keys=Args(['foo', 'bar'])) # DEL foo bar
# inlining pairwise arguments
data = {'name': 'bob', 'age': 42}
Command('XADD foo * {fields}', fields=Args.from_dict(data)) # XADD foo * name bob age 42
Pipelining commands
foo, bar = await redis.execute_many(Command('GET', 'foo'), Command('GET', 'bar'))
# handling errors in a pipeline
from reddish import PipelineError
try:
foo, bar = await redis.execute_many(*commands)
except PipelineError as error:
for outcome in error.outcomes:
try:
# either returns the reply if it was successful
# or raises the original exception if not
value = outcome.unwrap()
...
except CommandError:
...
except ParseError:
...
Transactions
from reddish import MultiExec
tx = MultiExec(
Command('ECHO {}', 'foo'),
Command('ECHO {}', 'bar')
)
foo, bar = await redis.execute(tx)
# handling errors with transactions
try:
await redis.execute(some_tx)
except CommandError as error:
# The exception as a whole failed and redis replied with an EXECABORT error
cause = error.__cause__ # original CommandError that caused the EXECABORT
print(f'Exception aborted due to {cause.code}:{cause.message}')
except TransactionError as error:
# Some command inside the transaction failed
# but the transaction as a whole succeeded
# to get at the partial results of the transaction and the errors
# you can iterate over the outcomes of the transaction
for outcome in error.outcomes:
try:
outcome.unwrap() # get the value or raise the original error
except CommandError:
...
except ParseError:
...
# pipelining together with transactions
[foo, bar], baz = await redis.execute_many(tx, Command('ECHO {}', 'baz'))
# handling errors with transactions inside a pipeline
try:
res: list[int] = await redis.execute_many(*cmds)
except PipelineError as error:
# handle the outcomes of the pipeline
for outcome in error.outcomes:
try:
outcome.unwrap()
except CommandError:
...
except ParseError:
...
except TransactionError as tx_error:
# handle errors inside the transaction
for tx_outcome in tx_error.outcomes:
...
Related Skills
node-connect
346.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
346.8kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
346.8kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
