Migrate to Unified Responses#
redis-py can return protocol-independent Python response shapes. When unified responses are enabled, redis-py applies the same public response structure for the affected commands whether the connection uses RESP2 or RESP3.
Unified responses are selected with legacy_responses=False. The protocol
still controls the wire format; legacy_responses controls the Python shape
returned by redis-py.
Unified responses are the recommended mode for new projects and for applications that can update their response handling. They provide the most portable application-facing API because command results keep the same Python shape when you change the Redis serialization protocol.
Enable Unified Responses#
Use legacy_responses=False when constructing a client.
import redis
# Default wire protocol, unified Python responses.
r = redis.Redis(legacy_responses=False)
# RESP2 wire, unified Python responses.
r_resp2 = redis.Redis(protocol=2, legacy_responses=False)
# RESP3 wire, unified Python responses.
r_resp3 = redis.Redis(protocol=3, legacy_responses=False)
The same option is available for asyncio and cluster clients.
import redis.asyncio as redis_async
from redis.cluster import RedisCluster
async_r = redis_async.Redis(legacy_responses=False)
cluster = RedisCluster(host="localhost", port=6379, legacy_responses=False)
Connection URLs can also select unified responses.
r = redis.from_url("redis://localhost:6379?legacy_responses=false")
r = redis.from_url(
"redis://localhost:6379?protocol=2&legacy_responses=false"
)
Response Modes#
Client options |
Wire protocol |
Python response shape |
|---|---|---|
|
Default RESP3 wire |
Legacy RESP2-compatible shape |
|
RESP2 |
Legacy RESP2 shape |
|
RESP3 |
Native RESP3 shape |
|
Default RESP3 wire |
Unified shape |
|
RESP2 |
Unified shape |
|
RESP3 |
Unified shape |
decode_responses is independent from unified responses. It still controls
bulk-string decoding where the command parser does not otherwise normalize a
structural key or value.
Migration Checklist#
Find the redis-py client construction points in your application, including background workers, admin scripts, asyncio clients, and cluster clients.
Enable
legacy_responses=Falsefor one environment or service path first. The server protocol can stay unchanged unless you also want to pinprotocol=2orprotocol=3.Check the Redis commands and
execute_command()calls your application depends on against the tables below. Update application API type hints, response models, serializers, and assertions to reflect the unified response shapes.Review module command responses such as JSON, TimeSeries, RediSearch, and probabilistic commands separately; several of them return richer objects or nested containers in unified mode.
Keep
decode_responsesdecisions separate. Unified responses normalize response structures, whiledecode_responsesstill controls how bulk string data is decoded.Roll the change out gradually and monitor application paths that parse Redis responses manually.
RESP2 Legacy to Unified#
The following tables summarize the main differences when moving from
protocol=2 with legacy responses to unified responses.
Core Commands#
Command |
Change |
RESP2 legacy example |
Unified example |
|---|---|---|---|
|
Flat or tuple score pairs normalize to list pairs with float scores. |
|
|
|
Tuple pairs become list pairs; scores are floats. |
|
|
|
Tuple pairs become list pairs; scores are floats. |
|
|
|
Tuple result becomes list result; score is a float. |
|
|
|
Rank response score is a float. |
|
|
|
Score pairs are lists; |
|
|
|
Flat interleaved values become nested score pairs. |
|
|
|
Flat interleaved values become nested field/value pairs. |
|
|
|
Tuple result becomes list result. |
|
|
|
Score values are floats inside nested list pairs. |
|
|
|
List of stream entries becomes a dict keyed by stream. |
|
|
|
Flat key/value list becomes a dict with string keys. |
|
|
|
Match ranges use lists and string keys. |
|
|
|
Flat list becomes dict; structural strings are decoded. |
|
|
|
Flags and ACL categories are sets of strings. |
|
|
|
Selector lists become selector dicts. |
|
|
|
|
|
|
|
|
|
|
|
Link dictionaries use string keys. |
|
|
|
Shard and node dictionaries use string keys. |
|
|
|
Coordinates are lists. |
|
|
|
RESP2 and RESP3 unified responses use tuple coordinates. |
|
|
|
Flat function data becomes nested dictionaries. |
|
|
|
Structural keys are decoded and numeric values use native types. |
|
|
JSON#
Command |
Change |
RESP2 legacy example |
Unified example |
|---|---|---|---|
|
Legacy scalar paths are normalized with JSONPath array behavior. |
|
|
|
Numeric string leaves that represent floats become Python floats. |
|
|
|
Key values respect |
|
|
TimeSeries#
Command |
Change |
RESP2 legacy example |
Unified example |
|---|---|---|---|
|
Tuple becomes list. |
|
|
|
Sample tuples become sample lists. |
|
|
|
Sorted list of dicts becomes a key/value dict. |
|
|
|
Sorted list of dicts becomes a dict with metadata slot. |
|
|
|
Key strings are preserved instead of coerced to numbers. |
|
|
RediSearch Commands#
Command |
Change |
RESP2 legacy example |
Unified example |
|---|---|---|---|
|
Attribute sublists become structured dictionaries. |
|
|
|
Keys and values are strings. |
|
|
|
|
|
|
|
|
|
|
|
Returns parsed result plus |
|
|
|
Returns |
|
|
Probabilistic#
Command |
Change |
RESP2 legacy example |
Unified example |
|---|---|---|---|
|
Item names are preserved instead of coerced through numeric parsing. |
|
|
RESP3 Legacy to Unified#
The following tables summarize the main differences when moving from
protocol=3 with legacy responses to unified responses.
Core Commands#
Command |
Change |
RESP3 legacy example |
Unified example |
|---|---|---|---|
Sorted-set score commands |
Scores pass through the same callback normalization as RESP2 unified. |
|
|
|
Score pairs are lists and |
|
|
|
Byte strings are decoded to strings. |
|
|
|
Byte scalar becomes string scalar. |
|
|
|
|
|
|
|
Structural keys and string lists are decoded. |
|
|
|
Flags and ACL categories are sets of strings. |
|
|
|
Dict keys are strings at the exposed structural levels. |
|
|
|
Hash strings are decoded. |
|
|
|
Keeps RESP3-style list coordinates. |
|
|
|
Coordinates normalize to tuples. |
|
|
|
Dict keys are strings and match ranges use the unified list shape. |
|
|
|
Native RESP3 state maps normalize to the unified state dictionaries. |
|
|
Module Commands#
Command |
Change |
RESP3 legacy example |
Unified example |
|---|---|---|---|
|
Raw module info maps become rich info objects. |
|
|
|
Raw lists are parsed with numeric and special-value handling. |
|
|
|
Raw TimeSeries info map becomes |
|
|
|
Keeps the metadata slot in the unified three-element value. |
|
|
|
Wrapped missing value becomes bare |
|
|
|
Numeric float leaves are normalized consistently. |
|
|
|
Raw RESP3 result map becomes |
|
|
|
Raw RESP3 aggregate map becomes |
|
|
|
Single |
|
|
|
Native nested RESP3 spellcheck map becomes normalized term suggestions. |
|
|
|
Structural keys are strings. |
|
|
|
Raw native response becomes |
|
|
HYBRID Command Loaded Fields#
FT.HYBRID is experimental. Unified responses for the HYBRID command
preserve loaded field values as bytes by default to keep binary data, such as
vector fields, intact.
Use decode_field=True only for fields that are known text values.
post = HybridPostProcessingConfig()
post.load("@title", decode_field=True)
post.load("@embedding", decode_field=False)