Repository navigation
fix(index): return a Result from search() on RESP3 clients - #830
Open
hyeonsang010716 wants to merge 1 commit into
Open
hyeonsang010716 wants to merge 1 commit into
hyeonsang010716 wants to merge 1 commit into
Conversation
A client created with protocol=3 gets FT.SEARCH replies as a dict, and query(), paginate() and clear() read them as a Result, so they raised AttributeError. search() now converts the dict the way batch_search already does, for standalone and cluster clients, sync and async. Fixes redis#825
This branch has not been deployed
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.
Motivation
Fixes #825. On a client created with
protocol=3, redis-py returns theFT.SEARCHreply as a dict, andprocess_resultsreads it as aResult.query()raisesAttributeError: 'dict' object has no attribute 'docs'forFilterQuery,VectorQuery,VectorRangeQueryandTextQuery, and'total'forCountQuery.paginate(),clear()anddelete(drop=True), which go throughquery(), fail the same way, sync and async. I reproduced it on redis-py 6.4.0, 7.4.1 and 8.1.0 against Redis 8.4.Changes
Convert RESP3 replies in
search()SearchIndex.search()andAsyncSearchIndex.search()now pass a dict reply through the same conversionbatch_searchalready uses for pipelined replies, so they return aResulton every protocol. Everything that readssearch()gets the fix, includingquery(),paginate(),clear()and the migration validators that read.total. The conversion is moved out of_parse_batch_search_resultinto_resp3_search_to_resultso both paths share it. The cluster branch goes through it too.Notes
The conversion sits in
search()rather than only in_query(), so thatsearch()returns theResultits signature and docstring promise.FT.AGGREGATE(AggregateHybridQuery) andFT.HYBRID(HybridQuery) also fail on RESP3 clients, with'rows'and'results'; their replies have different shapes, so they're left for a follow-up.Release Notes
query(),paginate(),search(),clear()anddelete(drop=True)now work onSearchIndexandAsyncSearchIndexwith a client created withprotocol=3. On such a client,search()now returns a redis-pyResultinstead of the raw reply dict.AggregateHybridQueryandHybridQueryare still unsupported on RESP3 clients.Reviewer Brief
Start here:
_search_reply_to_resultinredisvl/index/index.py, called fromSearchIndex.search()andAsyncSearchIndex.search(). It decides when a reply is converted and rebuilds the query that the conversion needs.Where it could fail
redisvl/index/index.py:2156and:3469: code that read the raw dict fromsearch()on aprotocol=3client, such asindex.search("*")["total_results"], now gets aResultand raisesTypeError.redisvl/index/index.py:155: on redis-py 6.x and 7.x the reply is rebuilt by hand, and everyTextQuerynow goes through itsWITHSCORESbranch; check it against the scoredTextQueryassertion in the new integration tests.Not verified: a real Redis Cluster on RESP3 (only the mocked unit tests in
tests/unit/test_search_resp3.pycover that branch, and cluster tests don't run in CI); Python versions other than 3.12; Redis Software and Redis Cloud.Beyond CI: the new tests fail on the code before the fix (the three unit tests and both
protocol-3integration cases); removing theWITHSCORESbranch of the hand-rebuilt reply fails bothprotocol-3integration cases on redis-py 7.4.1.Reviewed the working tree on
930c14e, fixes incc03141· Claude Code (claude-opus-5-5) · lenses: correctness, security, design, tests, docs, Redis behaviourNote
Medium Risk
Changes the core FT.SEARCH return type on RESP3 clients and relies on redis-py private parsers; cluster RESP3 is only covered by mocks in CI.
Overview
Fixes broken
query(),paginate(),clear(), and related paths when the Redis client uses RESP3 (protocol=3), where redis-py returned a rawFT.SEARCHdict instead of aResult.SearchIndex.search()andAsyncSearchIndex.search()(standalone and cluster) now normalize dict replies through shared helpers_search_reply_to_result/_resp3_search_to_result, reusing the same logicbatch_searchalready used via_parse_batch_search_result. On redis-py 6.x/7.x without_parse_search_resp3, the code still hand-builds a RESP2-shaped reply (includingWITHSCORESfor scored queries).Integration tests cover user-provided clients across default,
protocol=3, and redis-py 8legacy_responses; unit tests mock cluster search with a RESP3-shaped reply.Note: Callers that treated
search()on RESP3 as a dict (e.g.["total_results"]) now get aResultand may seeTypeError.HybridQuery/ aggregate paths on RESP3 remain out of scope.Reviewed by Cursor Bugbot for commit cc03141. Bugbot is set up for automated code reviews on this repo. Configure here.