redis/redis-py · error · TypeError

Key must be either a string or bytes

Error message

Key must be either a string or bytes

What it means

Raised by ensure_string (redis/utils.py:355) as a built-in TypeError. The helper normalizes a key to str: it decodes bytes, returns str unchanged, and rejects anything else. Passing a non-str/non-bytes value (int, float, None, bool, custom object) raises TypeError('Key must be either a string or bytes'). It is a strict input-type guard used by internal normalization paths.

Source

Thrown at redis/utils.py:355

        for _ in range(diff):
            num_versions1.append(0)

    for i, ver in enumerate(num_versions1):
        if num_versions1[i] > num_versions2[i]:
            return -1
        elif num_versions1[i] < num_versions2[i]:
            return 1

    return 0


def ensure_string(key):
    if isinstance(key, bytes):
        return key.decode("utf-8")
    elif isinstance(key, str):
        return key
    else:
        raise TypeError("Key must be either a string or bytes")


def extract_expire_flags(
    ex: Optional[ExpiryT] = None,
    px: Optional[ExpiryT] = None,
    exat: Optional[AbsExpiryT] = None,
    pxat: Optional[AbsExpiryT] = None,
) -> List[EncodableT]:
    exp_options: list[EncodableT] = []
    if ex is not None:
        exp_options.append("EX")
        if isinstance(ex, datetime.timedelta):
            exp_options.append(int(ex.total_seconds()))
        elif isinstance(ex, int):
            exp_options.append(ex)
        elif isinstance(ex, str) and ex.isdigit():
            exp_options.append(int(ex))
        else:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Convert the value before calling: ensure_string(str(key)) for ints, or encode to bytes.
  2. Validate key type at the API boundary and reject/coerce non-string types early.
  3. For numeric keys, prefer a consistent key-building helper, e.g. f'user:{id}'.

Example fix

# before
ensure_string(user_id)        # user_id is an int -> TypeError
# after
ensure_string(str(user_id))   # or build a namespaced key
ensure_string(f'user:{user_id}')
Defensive patterns

Strategy: type-guard

Validate before calling

from typing import Any

def ensure_string_safe(key: Any) -> str:
    if isinstance(key, bytes):
        return key.decode('utf-8')
    if isinstance(key, str):
        return key
    # Coerce ints/floats/Decimal instead of raising
    if isinstance(key, (int, float)):
        return str(key)
    raise TypeError(f'Key must be str, bytes, or a number; got {type(key).__name__}')

Type guard

from typing import Any

def is_valid_key(key: Any) -> bool:
    return isinstance(key, (str, bytes))

# Usage
if not is_valid_key(maybe_key):
    maybe_key = str(maybe_key)

Try / catch

try:
    normalized = ensure_string(value)
except TypeError:
    normalized = str(value)

Prevention

When it happens

Trigger: Calling ensure_string with a value that is neither str nor bytes, e.g. ensure_string(123), ensure_string(None), ensure_string(1.5), or ensure_string(some_object). This typically surfaces from application code that pipes unconverted identifiers or numeric keys through code paths that end up in ensure_string.

Common situations: Numeric primary keys used directly as Redis keys without str() conversion; None passed due to a missing dict lookup; ORM/model objects whose __str__ is relied upon but not invoked; JSON-decoded integer keys not normalized.

Related errors


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