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
- Convert the value before calling: ensure_string(str(key)) for ints, or encode to bytes.
- Validate key type at the API boundary and reject/coerce non-string types early.
- 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
- Centralize key construction in a helper that always returns str/bytes (e.g. f'user:{id}').
- Validate input types at API boundaries and coerce early.
- Never pass None or raw ORM objects as keys.
- Add unit tests for the key-building path with int/float/None inputs.
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
- Cache must implement CacheInterface
- ex must be datetime.timedelta or int
- px must be datetime.timedelta or int
- Subcommand {subcommand_name} not found in command {command_n
- Command {command_name} not found in commands
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/b16da8bde9cf8249.json.
Report an issue: GitHub.