redis/redis-py · error · TypeError
target_nodes type can be one of the following: node_flag (PR
Error message
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)} What it means
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.
Source
Thrown at redis/asyncio/cluster.py:1048
return slots.pop()
def _is_node_flag(self, target_nodes: Any) -> bool:
return isinstance(target_nodes, str) and target_nodes in self.node_flags
def _parse_target_nodes(self, target_nodes: Any) -> List["ClusterNode"]:
if isinstance(target_nodes, list):
nodes = target_nodes
elif isinstance(target_nodes, ClusterNode):
# Supports passing a single ClusterNode as a variable
nodes = [target_nodes]
elif isinstance(target_nodes, dict):
# Supports dictionaries of the format {node_name: node}.
# It enables to execute commands with multi nodes as follows:
# rc.cluster_save_config(rc.get_primaries())
nodes = list(target_nodes.values())
else:
raise TypeError(
"target_nodes type can be one of the following: "
"node_flag (PRIMARIES, REPLICAS, RANDOM, ALL_NODES),"
"ClusterNode, list<ClusterNode>, or dict<any, ClusterNode>. "
f"The passed type is {type(target_nodes)}"
)
return nodes
async def _record_error_metric(
self,
error: Exception,
connection: Union[Connection, "ClusterNode"],
is_internal: bool = True,
retry_attempts: Optional[int] = None,
):
"""
Records error count metric directly.
Accepts either a Connection or ClusterNode object.
"""View on GitHub (pinned to da03cdc7e8)
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].
Example fix
// before
await rc.cluster_save_config(target_nodes=('10.0.0.1', 6379))
// after
node = rc.get_node(host='10.0.0.1', port=6379)
await rc.cluster_save_config(target_nodes=node) Defensive patterns
Strategy: type-guard
Validate before calling
from redis.asyncio.cluster import RedisCluster, ClusterNode
from typing import Union
NodeFlag = str # 'PRIMARIES' | 'REPLICAS' | 'RANDOM' | 'ALL_NODES'
TargetNodes = Union[NodeFlag, ClusterNode, list, dict]
VALID_FLAGS = {'PRIMARIES', 'REPLICAS', 'RANDOM', 'ALL_NODES'}
def coerce_target_nodes(t) -> list:
if isinstance(t, str):
if t not in VALID_FLAGS:
raise ValueError(f'Unknown node flag {t!r}; one of {VALID_FLAGS}')
return t
if isinstance(t, (ClusterNode, list, dict)):
return t
raise TypeError(f'target_nodes must be flag/ClusterNode/list/dict, got {type(t)}') Type guard
from redis.asyncio.cluster import ClusterNode
from typing import Any
VALID_FLAGS = {'PRIMARIES', 'REPLICAS', 'RANDOM', 'ALL_NODES'}
def is_valid_target_nodes(value: Any) -> bool:
if isinstance(value, str):
return value in VALID_FLAGS
if isinstance(value, ClusterNode):
return True
if isinstance(value, list):
return all(isinstance(v, ClusterNode) for v in value)
if isinstance(value, dict):
return all(isinstance(v, ClusterNode) for v in value.values())
return False Try / catch
try:
await rc.cluster_save_config(target_nodes=flag)
except TypeError as e:
if 'target_nodes type' in str(e):
raise ValueError('Use RedisCluster.PRIMARIES etc., a ClusterNode, list, or dict') from e
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- get_node requires one of the following: 1. node name 2. host
- Cannot execute FT.CURSOR commands without FT.AGGREGATE
- Invalid args in command: {command, *args}
- No way to dispatch this command to Redis Cluster. Missing ke
- {command} - all keys must map to the same key slot
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/3486b3fe8a4b5c4f.json.
Report an issue: GitHub.