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

  1. Use the documented node-flag constants: target_nodes=RedisCluster.PRIMARIES / REPLICAS / RANDOM / ALL_NODES.
  2. Pass a ClusterNode obtained from rc.get_node(...): target_nodes=rc.get_node(host=..., port=...).
  3. 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

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


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