{"id":"4721d4afd128b155","repo":"redis/redis-py","slug":"at-least-one-key-must-be-provided","errorCode":null,"errorMessage":"At least one key must be provided.","messagePattern":"At least one key must be provided\\.","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/timeseries/commands.py","lineNumber":1106,"sourceCode":"    def __n_range_params(\n        self,\n        keys: List[KeyT],\n        from_time: int | str,\n        to_time: int | str,\n        count: int | None,\n        aggregators: str | list[str] | None,\n        bucket_size_msec: int | None,\n        filter_by_ts: List[int] | None,\n        filter_by_min_value: int | None,\n        filter_by_max_value: int | None,\n        align: int | str | None,\n        latest: bool | None,\n        bucket_timestamp: str | None,\n        empty: bool | None,\n    ):\n        \"\"\"Create TS.NRANGE and TS.NREVRANGE arguments.\"\"\"\n        if not keys:\n            raise DataError(\"At least one key must be provided.\")\n        # numkeys is always derived from the key list and precedes the keys;\n        # key order and duplicates are preserved and map to output columns.\n        params: list[EncodableT] = [len(keys), *keys, from_time, to_time]\n        self._append_latest(params, latest)\n        self._append_filer_by_ts(params, filter_by_ts)\n        self._append_filer_by_value(params, filter_by_min_value, filter_by_max_value)\n        self._append_count(params, count)\n        self._append_align(params, align)\n        self._append_n_aggregation(params, aggregators, bucket_size_msec, len(keys))\n        self._append_bucket_timestamp(params, bucket_timestamp)\n        self._append_empty(params, empty)\n\n        return params\n\n    @overload\n    def nrange(\n        self: SyncClientProtocol,\n        keys: List[KeyT],","sourceCodeStart":1088,"sourceCodeEnd":1124,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/timeseries/commands.py#L1088-L1124","documentation":"Raised by the TS.NRANGE/TS.NREVRANGE parameter builder (__n_range_params) when the keys list is empty. These multi-key commands require at least one time-series key; an empty list would send numkeys=0, which the server rejects. The library validates client-side for a clearer error.","triggerScenarios":"Calling client.ts().nrange([], '-', '+') or client.ts().nrange(keys, '-', '+') where keys resolved to an empty list; same for nrevrange.","commonSituations":"Querying a list of keys produced by a label filter/lookup that matched nothing; fan-out from upstream that returned an empty set; defaulting keys to [] and forgetting to populate.","solutions":["Ensure the keys list is non-empty before calling: if keys: client.ts().nrange(keys, '-', '+').","Fix the upstream lookup so it returns the intended keys.","If you genuinely have no keys, skip the query and return an empty result."],"exampleFix":"// before\nclient.ts().nrange(keys, '-', '+')  # keys == []\n// after\nif keys:\n    client.ts().nrange(keys, '-', '+')","handlingStrategy":"validation","validationCode":"def safe_nrange(client, keys, frm, to, **kw):\n    if not keys:\n        return []\n    return client.ts().nrange(list(keys), frm, to, **kw)","typeGuard":"def has_keys(keys) -> bool:\n    return bool(keys)","tryCatchPattern":"try:\n    client.ts().nrange(keys, '-', '+')\nexcept Exception as e:\n    if 'At least one key' in str(e):\n        result = []\n    else:\n        raise","preventionTips":["Resolve the key list (e.g. via TS.QUERYINDEX) before calling nrange.","Short-circuit with an empty result when no keys match.","Validate upstream lookups return keys."],"tags":["redistimeseries","timeseries","nrange","argument-error","validation"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}