redis/redis-py · error · AttributeError

Did not receive a Filter object.

Error message

Did not receive a Filter object.

What it means

Raised by Query._get_args_tags (invoked via get_args()) when an entry in the query's internal _filters list is not an instance of the Filter base class (NumericFilter or GeoFilter). This guards the query builder from emitting a malformed FILTER/GEOFILTER clause. Normally add_filter() is the only way to populate _filters, so this indicates direct manipulation or a non-Filter object slipped through add_filter.

Source

Thrown at redis/commands/search/query.py:213

        args += ["LIMIT", self._offset, self._num]
        return args

    def _get_args_tags(self) -> List[Union[str, int, float]]:
        args: List[Union[str, int, float]] = []
        if self._no_content:
            args.append("NOCONTENT")
        if self._fields:
            args.append("INFIELDS")
            args.append(len(self._fields))
            args += self._fields
        if self._verbatim:
            args.append("VERBATIM")
        if self._no_stopwords:
            args.append("NOSTOPWORDS")
        if self._filters:
            for flt in self._filters:
                if not isinstance(flt, Filter):
                    raise AttributeError("Did not receive a Filter object.")
                args += flt.args
        if self._with_payloads:
            args.append("WITHPAYLOADS")
        if self._scorer:
            args += ["SCORER", self._scorer]
        if self._with_scores:
            args.append("WITHSCORES")
        if self._ids:
            args.append("INKEYS")
            args.append(len(self._ids))
            args += self._ids
        if self._slop >= 0:
            args += ["SLOP", self._slop]
        if self._timeout is not None:
            args += ["TIMEOUT", self._timeout]
        if self._in_order:
            args.append("INORDER")
        if self._return_fields:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Wrap the field in a NumericFilter or GeoFilter: from redis.commands.search.query import NumericFilter, GeoFilter; query.add_filter(NumericFilter('price', 0, 100)).
  2. Stop mutating query._filters directly; only use add_filter().
  3. If you need a text/expression filter, put it in the query string itself, not in add_filter().

Example fix

// before
query.add_filter('price')
// after
from redis.commands.search.query import NumericFilter
query.add_filter(NumericFilter('price', 0, 100))
Defensive patterns

Strategy: type-guard

Validate before calling

from redis.commands.search.query import Filter

def add_safe_filter(query, flt):
    if not isinstance(flt, Filter):
        raise TypeError("filter must be a Filter instance")
    query.add_filter(flt)

Type guard

from redis.commands.search.query import Filter

def is_filter(val) -> bool:
    return isinstance(val, Filter)

Try / catch

try:
    query.add_filter(candidate)
    query.get_args()
except AttributeError:
    # candidate was not a Filter; build one explicitly
    pass

Prevention

When it happens

Trigger: Calling query.add_filter('price') (passing a field name string instead of a NumericFilter/GeoFilter), or directly assigning query._filters = ['something'], then triggering argument serialization (e.g. via client.ft().search(query)).

Common situations: Assuming add_filter takes a field name or a raw expression string instead of a Filter object; migrating code from a hand-built query string; third-party wrappers that inject into _filters.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/1a4861e89e6028a9.json. Report an issue: GitHub.