{"id":"3486b3fe8a4b5c4f","repo":"redis/redis-py","slug":"target-nodes-type-can-be-one-of-the-following-nod","errorCode":null,"errorMessage":"target_nodes type can be one of the following: node_flag (PRIMARIES, REPLICAS, RANDOM, ALL_NODES),ClusterNode, list<ClusterNode>, or dict<any, ClusterNode>. The passed type is {type(target_nodes)}","messagePattern":"target_nodes type can be one of the following: node_flag \\(PRIMARIES, REPLICAS, RANDOM, ALL_NODES\\),ClusterNode, list<ClusterNode>, or dict<any, ClusterNode>\\. The passed type is (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/cluster.py","lineNumber":1048,"sourceCode":"\n        return slots.pop()\n\n    def _is_node_flag(self, target_nodes: Any) -> bool:\n        return isinstance(target_nodes, str) and target_nodes in self.node_flags\n\n    def _parse_target_nodes(self, target_nodes: Any) -> List[\"ClusterNode\"]:\n        if isinstance(target_nodes, list):\n            nodes = target_nodes\n        elif isinstance(target_nodes, ClusterNode):\n            # Supports passing a single ClusterNode as a variable\n            nodes = [target_nodes]\n        elif isinstance(target_nodes, dict):\n            # Supports dictionaries of the format {node_name: node}.\n            # It enables to execute commands with multi nodes as follows:\n            # rc.cluster_save_config(rc.get_primaries())\n            nodes = list(target_nodes.values())\n        else:\n            raise TypeError(\n                \"target_nodes type can be one of the following: \"\n                \"node_flag (PRIMARIES, REPLICAS, RANDOM, ALL_NODES),\"\n                \"ClusterNode, list<ClusterNode>, or dict<any, ClusterNode>. \"\n                f\"The passed type is {type(target_nodes)}\"\n            )\n        return nodes\n\n    async def _record_error_metric(\n        self,\n        error: Exception,\n        connection: Union[Connection, \"ClusterNode\"],\n        is_internal: bool = True,\n        retry_attempts: Optional[int] = None,\n    ):\n        \"\"\"\n        Records error count metric directly.\n        Accepts either a Connection or ClusterNode object.\n        \"\"\"","sourceCodeStart":1030,"sourceCodeEnd":1066,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L1030-L1066","documentation":"A TypeError raised by _parse_target_nodes() when the target_nodes argument is not one of the accepted shapes: a node-flag string (PRIMARIES/REPLICAS/RANDOM/ALL_NODES), a single ClusterNode, a list of ClusterNode, or a dict<any, ClusterNode>. The cluster command dispatcher must normalize target_nodes into a node list, and any other type cannot be interpreted as a routing target.","triggerScenarios":"Passing target_nodes='primary' (misspelled flag), target_nodes=('host', 6379) tuple, target_nodes='127.0.0.1' (raw host string), target_nodes=123, or a generator/iterator instead of a concrete list. The check is a pure isinstance ladder at cluster.py:1037-1053.","commonSituations":"Guessing flag names instead of using RedisCluster.PRIMARIES; passing a host:port tuple like the standalone client accepts; passing a single node wrapped incorrectly; refactoring that changes a list to a non-list iterable.","solutions":["Use the documented node-flag constants: target_nodes=RedisCluster.PRIMARIES / REPLICAS / RANDOM / ALL_NODES.","Pass a ClusterNode obtained from rc.get_node(...): target_nodes=rc.get_node(host=..., port=...).","If you have several nodes, pass them as a Python list of ClusterNode objects: target_nodes=[node1, node2]."],"exampleFix":"// before\nawait rc.cluster_save_config(target_nodes=('10.0.0.1', 6379))\n\n// after\nnode = rc.get_node(host='10.0.0.1', port=6379)\nawait rc.cluster_save_config(target_nodes=node)","handlingStrategy":"type-guard","validationCode":"from redis.asyncio.cluster import RedisCluster, ClusterNode\nfrom typing import Union\n\nNodeFlag = str  # 'PRIMARIES' | 'REPLICAS' | 'RANDOM' | 'ALL_NODES'\nTargetNodes = Union[NodeFlag, ClusterNode, list, dict]\n\nVALID_FLAGS = {'PRIMARIES', 'REPLICAS', 'RANDOM', 'ALL_NODES'}\n\ndef coerce_target_nodes(t) -> list:\n    if isinstance(t, str):\n        if t not in VALID_FLAGS:\n            raise ValueError(f'Unknown node flag {t!r}; one of {VALID_FLAGS}')\n        return t\n    if isinstance(t, (ClusterNode, list, dict)):\n        return t\n    raise TypeError(f'target_nodes must be flag/ClusterNode/list/dict, got {type(t)}')","typeGuard":"from redis.asyncio.cluster import ClusterNode\nfrom typing import Any\n\nVALID_FLAGS = {'PRIMARIES', 'REPLICAS', 'RANDOM', 'ALL_NODES'}\n\ndef is_valid_target_nodes(value: Any) -> bool:\n    if isinstance(value, str):\n        return value in VALID_FLAGS\n    if isinstance(value, ClusterNode):\n        return True\n    if isinstance(value, list):\n        return all(isinstance(v, ClusterNode) for v in value)\n    if isinstance(value, dict):\n        return all(isinstance(v, ClusterNode) for v in value.values())\n    return False","tryCatchPattern":"try:\n    await rc.cluster_save_config(target_nodes=flag)\nexcept TypeError as e:\n    if 'target_nodes type' in str(e):\n        raise ValueError('Use RedisCluster.PRIMARIES etc., a ClusterNode, list, or dict') from e\n    raise","preventionTips":["Always use the RedisCluster constants (RedisCluster.PRIMARIES, .ALL_NODES, etc.) rather than string literals.","Resolve nodes via rc.get_node() before passing them; never pass raw host:port tuples.","Wrap target_nodes construction in a helper that validates the type once."],"tags":["redis-cluster","target-nodes","typeerror","api-usage"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}