{"id":"e177c6e184b3d5ca","repo":"redis/redis-py","slug":"collect-sort-by-must-contain-at-least-one-field","errorCode":null,"errorMessage":"collect sort_by must contain at least one field","messagePattern":"collect sort_by must contain at least one field","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"redis/commands/search/reducers.py","lineNumber":253,"sourceCode":"            if not names or any(not n.strip() for n in names):\n                raise ValueError(\n                    \"collect fields must be '*' or a non-empty list of names\"\n                )\n            names = [_ensure_at_prefix(n) for n in names]\n            args += [\"FIELDS\", str(len(names))] + names\n\n        # DISTINCT (optional)\n        if distinct:\n            args += [\"DISTINCT\"]\n\n        # SORTBY (optional)\n        if sort_by is not None:\n            sort_fields = [sort_by] if isinstance(sort_by, (Asc, Desc)) else sort_by\n            sort_args: list[str] = []\n            for f in sort_fields:\n                sort_args += [_ensure_at_prefix(f.field), f.DIRSTRING]\n            if not sort_args:\n                raise ValueError(\"collect sort_by must contain at least one field\")\n            args += [\"SORTBY\", str(len(sort_args))] + sort_args\n\n        # LIMIT (optional)\n        if limit is not None:\n            offset, count = limit\n            args += [\"LIMIT\", str(offset), str(count)]\n\n        super().__init__(*args)\n","sourceCodeStart":235,"sourceCodeEnd":262,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/search/reducers.py#L235-L262","documentation":"Raised by the collect reducer when sort_by is provided as an iterable but contains no Asc/Desc entries. Sorting within COLLECT requires at least one field directive; an empty iterable is a no-op the library rejects rather than silently dropping the SORTBY clause.","triggerScenarios":"collect(fields='*', sort_by=[]) or collect(fields='*', sort_by=sort_dirs) where sort_dirs is an empty list/generator.","commonSituations":"Building sort directives dynamically from user choices that resolved to none; unpacking an empty list into sort_by; refactoring that left a default empty list.","solutions":["Pass at least one Asc/Desc instance: from redis.commands.search.aggregation import Asc, Desc; collect(fields='*', sort_by=Asc('price')).","If sorting is optional, omit sort_by entirely (pass None) when there is nothing to sort by.","Guard dynamic inputs: only pass sort_by when the list is non-empty."],"exampleFix":"// before\ncollect(fields='*', sort_by=sort_dirs or [])\n// after\ncollect(fields='*', sort_by=sort_dirs if sort_dirs else None)","handlingStrategy":"validation","validationCode":"def safe_sort_by(sort_dirs):\n    if not sort_dirs:\n        return None\n    if isinstance(sort_dirs, (Asc, Desc)):\n        return sort_dirs\n    return list(sort_dirs) if len(list(sort_dirs)) else None\n\n# usage: collect(fields='*', sort_by=safe_sort_by(dirs))","typeGuard":"from redis.commands.search.aggregation import Asc, Desc\n\ndef has_sort_entries(sort_by) -> bool:\n    if sort_by is None:\n        return True\n    if isinstance(sort_by, (Asc, Desc)):\n        return True\n    return len(list(sort_by)) > 0","tryCatchPattern":"try:\n    collect(fields='*', sort_by=dirs)\nexcept ValueError:\n    collect(fields='*', sort_by=None)","preventionTips":["Pass None instead of an empty list when there is nothing to sort by.","Materialize generators before checking length.","Keep sort_by optional in your config schema."],"tags":["search","search-and-query","aggregation","reducer","collect","sort","argument-error"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}