RESP2 and RESP3 response unification#4025
Merged
Merged
Conversation
🛡️ Jit Security Scan Results✅ No security findings were detected in this PR
Security scan by Jit
|
e39c3ab to
e20d0a7
Compare
vladvildanov
reviewed
Apr 8, 2026
vladvildanov
approved these changes
Apr 8, 2026
…JECT and Batch 9: bool vs raw string — BGREWRITEAOF, BGSAVE
…) and Batch 11: Module Commands — TimeSeries (TS)
… for older redis versions we should produce same object structure
5fb3a71 to
a08048c
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2301f33. Configure here.
petyaslavova
added a commit
that referenced
this pull request
Apr 16, 2026
petyaslavova
added a commit
that referenced
this pull request
Apr 30, 2026
This reverts commit de8a47a.
petyaslavova
added a commit
that referenced
this pull request
May 5, 2026
… default config compatible with current RESP2 shape. Adding possibility to opt-in for unified responses for both protocol. (#4052) * Revert "Changing the default RESP protocol configurations for Redis clients and connections to 3 (#4031)" This reverts commit 0984334. * Revert "RESP2 and RESP3 response unification (#4025)" This reverts commit de8a47a. * Fixing newly added test to be compatible with the old style responses * Default to RESP3 wire protocol, add legacy_responses opt-out The default RESP protocol on the wire is now RESP3 (was RESP2). To keep existing applications working unchanged, response shapes still match today's RESP2-style Python objects by default, controlled by a new legacy_responses=True parameter on the public clients. * redis/utils.py - DEFAULT_RESP_VERSION lives here (moved from redis.connection) and is set to 3 - check_protocol_version() resolves protocol=None to DEFAULT_RESP_VERSION before comparison, so a missing protocol is treated as RESP3-capable * redis/connection.py, redis/asyncio/connection.py - import DEFAULT_RESP_VERSION from redis.utils (still re-exported from redis.connection for backward compatibility) - AbstractConnection.__init__: protocol default 2 -> None, new legacy_responses: bool = True stored as self.legacy_responses - URL_QUERY_ARGUMENT_PARSERS accepts protocol and legacy_responses, so both can be passed in connection URLs via redis://...?protocol=3&legacy_responses=false * redis/client.py, redis/asyncio/client.py - Redis.__init__: protocol default 2 -> None, new legacy_responses parameter, both forwarded into the connection pool kwargs - client-side caching and maintenance-notifications gating switched to check_protocol_version(...) so a missing protocol is treated as RESP3 * redis/asyncio/cluster.py - RedisCluster.__init__: protocol default 2 -> None, new legacy_responses parameter, both placed in the cluster connection kwargs * redis/cluster.py - REDIS_ALLOWED_KEYS includes legacy_responses so it propagates through cleanup_kwargs() to per-node Redis instances A user-supplied protocol=None is preserved on the pool / connection kwargs; only the wire-level HELLO handshake resolves it to the concrete DEFAULT_RESP_VERSION. * Adding test fixes * Adding changes for first several commands and test helpers * Adding changes for batches 5 and 6 * Adding the rest of the base commands. * Adding changes for probabilistic and timeseries modules * Adding changes for search and json modules. Adding docs and test fixes. Updating some of the unified forms to the correct ones * Adding test_xreadgroup_with_claim_min_idle_time fix * Adding pipeline test coverage for all combinations of protocol and legacy_responses * Applying review comments * Applying review comments * Applying review comments and fixing failing tests * Applying review comments and fixing failing tests * Add flaky test fix * Add flaky test fix- second try, same test - fails for just one job * Fixing unstable in CI tests * Applying review comments * Fixing linters and spelling error * Applying review comments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

RESP2/RESP3 Response Unification
This PR unifies the return types of Redis commands across RESP2 and RESP3 protocols, ensuring that application code receives consistent data structures regardless of which protocol version the client is connected with. Previously, many commands returned fundamentally different types — tuples vs lists, bytes vs strings, flat arrays vs nested structures — forcing users to write protocol-aware branching logic. After this change, RESP2 callbacks normalize responses to match RESP3 native types, making the protocol version an internal transport detail rather than a leaky abstraction.
Core Commands
Sorted set commands (
ZRANGE,ZPOPMAX,ZSCORE,ZRANDMEMBER,ZRANK,ZSCAN, etc.) now consistently return[member, score]lists withfloatscores, andscore_cast_funcreceives afloatin both protocols instead of raw bytes in RESP2. Blocking pop commands (BLPOP,BRPOP,BZPOPMAX,BZPOPMIN) return lists instead of tuples.ZMPOP/BZMPOPscores are alwaysfloat.HRANDFIELDwith values returns[[field, value], ...]lists in both protocols.LCSwithIDXreturns a dict in RESP2, matching RESP3's native map.str_if_byteswrapping has been removed from RESP2 callbacks where RESP3 already returns raw types, eliminating unnecessary encoding discrepancies.Structural & Module Commands
Stream commands (
XINFO STREAM,XREAD,XREADGROUP), geo commands (GEOSEARCH,GEORADIUS), and client tracking info now return matching structures across protocols. Sentinel commands normalizeflagstosetand masters to consistent dict shapes. Complex introspection commands (COMMAND,COMMAND INFO,ACL LOG,ACL GETUSER,MEMORY STATS,DEBUG OBJECT) received dedicated RESP3 parsers that produce the same shape as their RESP2 counterparts. Module commands across Bloom/Cuckoo/CMS/TopK/TDigest, TimeSeries, JSON, and Search all follow the same pattern — RESP3 parsers were added or aligned to produce identical output types.Search Module Refactoring
The Search module received significant internal cleanup beyond response unification. A shared
decode_field_valueutility inredis/utils.pyreplaces five duplicated decode-or-preserve-bytes blocks acrossResult, hybrid parsers, andload_document. Callback registration was deduplicated by moving_RESP2_MODULE_CALLBACKS/_RESP3_MODULE_CALLBACKSconstruction into a shared_init_module_callbacks()method onSearchCommands, eliminating three identical 20-line blocks. The pipeline adapter callbacks (_search_response_callback,_aggregate_response_callback,_hybrid_response_callback) were removed entirely —_parse_resultsnow handles pipeline options directly viafunctools.partial, with a guard clause for commands queued without a query object.Testing
The test suite was substantially refactored to remove protocol-conditional assertions. Over 2,500 lines of duplicated
@pytest.mark.parametrize("protocol", [2, 3])branches andif protocol == 3:blocks were replaced with unified assertions that validate both protocols produce the same types. New tests coverscore_cast_funcwith scientific notation,load_documentwithfield_encodings, and edge cases across all affected command families. The net result is 42 files changed with ~2,600 lines removed, reflecting the elimination of both production-side protocol branching and test-side conditional logic.This change includes a fix for issue #4021
Note
High Risk
High risk because it introduces widespread breaking return-type/structure changes across many core and module commands (e.g., tuples→lists, bytes→str, lists→dicts, flag lists→sets) and updates pipeline callback behavior, which can break downstream code and tests that relied on protocol-specific shapes.
Overview
Unifies RESP2 and RESP3 command responses so callers receive the same Python types/structures regardless of protocol, including sorted-set score pairs (now
list[list[member, float]]withscore_cast_funcalways receivingfloat), blocking pops (now lists), streams (XREAD/XREADGROUPnow dicts), and other structural normalizations (e.g.,LCS idx→ dict,CLIENT TRACKINGINFO→ dict,COMMAND.flags→ set with optionalacl_categories, Sentinelflags→ set and RESP3SENTINEL MASTERS→ dict keyed by name).Adds/updates parsers and callbacks for RESP3 parity (e.g.,
MEMORY STATS,ACL LOG,DEBUG OBJECT,CLUSTER LINKS,CLUSTER SHARDSkey decoding) and normalizes geo coordinate outputs to lists.Extends the same unification to modules: Bloom/TopK/TDigest
INFOcommands now always return*Infoobjects; fixes incorrect numeric coercion forTOPK.ADD/INCRBY/LIST; JSON normalizesNUMINCRBY/NUMMULTBYto list results, convertsJSON.RESPfloat strings to native floats, and alignsJSON.TYPEmissing-key handling; Search adds RESP3 parsing to return rich result objects and refactors module pipeline callback registration so module pipelines inherit fullclient.response_callbacksand post-process consistently.Reviewed by Cursor Bugbot for commit e1de69d. Bugbot is set up for automated code reviews on this repo. Configure here.