redis/redis-py · error · DataError
ex must be datetime.timedelta or int
Error message
ex must be datetime.timedelta or int
What it means
Raised by extract_expire_flags (redis/utils.py:374) as redis.exceptions.DataError when the `ex` (relative expiry in seconds) argument is not a datetime.timedelta, an int, or a numeric string. The function explicitly handles timedelta (converted via total_seconds), int, and str.isdigit() values; any other type (float, bool, None is allowed at the top-level check, list, object) triggers DataError. Used to assemble EX expiry flags for commands like SET.
Source
Thrown at redis/utils.py:374
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:
raise DataError("ex must be datetime.timedelta or int")
elif px is not None:
exp_options.append("PX")
if isinstance(px, datetime.timedelta):
exp_options.append(int(px.total_seconds() * 1000))
elif isinstance(px, int):
exp_options.append(px)
else:
raise DataError("px must be datetime.timedelta or int")
elif exat is not None:
if isinstance(exat, datetime.datetime):
exat = int(exat.timestamp())
exp_options.extend(["EXAT", exat])
elif pxat is not None:
if isinstance(pxat, datetime.datetime):
pxat = int(pxat.timestamp() * 1000)
exp_options.extend(["PXAT", pxat])
return exp_optionsView on GitHub (pinned to da03cdc7e8)
Solutions
- Use an int: client.set('k', 'v', ex=int(value)).
- For sub-second precision use px (milliseconds): client.set('k', 'v', px=int(value * 1000)).
- Use datetime.timedelta for clarity: client.set('k', 'v', ex=datetime.timedelta(seconds=value)).
- Round/validate the value before passing: ex = max(1, int(round(value))).
Example fix
# before
client.set('k', 'v', ex=1.5) # float -> DataError
# after
client.set('k', 'v', px=1500) # milliseconds, sub-second safe
# or
import datetime
client.set('k', 'v', ex=datetime.timedelta(seconds=1.5)) Defensive patterns
Strategy: type-guard
Validate before calling
import datetime
from typing import Any, Union
def coerce_ex(ex: Any) -> Union[int, datetime.timedelta, None]:
if ex is None:
return None
if isinstance(ex, (datetime.timedelta, int)):
return ex
if isinstance(ex, str) and ex.isdigit():
return int(ex)
if isinstance(ex, float):
return int(ex) # or convert to timedelta for sub-second
raise TypeError('ex must be timedelta, int, or numeric string')
client.set('k', 'v', ex=coerce_ex(user_value)) Type guard
import datetime
from typing import Any
def is_valid_ex(value: Any) -> bool:
return (
value is None
or isinstance(value, (datetime.timedelta, int))
or (isinstance(value, str) and value.isdigit())
) Try / catch
from redis.exceptions import DataError
try:
client.set('k', 'v', ex=value)
except DataError:
client.set('k', 'v', ex=int(value)) # or px=int(value*1000) Prevention
- Always pass int or datetime.timedelta for ex; never a raw float.
- For sub-second TTLs, prefer px (milliseconds).
- Validate expiry values at the configuration layer before they reach Redis calls.
- Add a thin wrapper around set() that normalizes TTL arguments.
When it happens
Trigger: Calling client.set('k', 'v', ex=1.5) (float not allowed), client.set('k', 'v', ex=True), or ex=<some_object>. Note ex='100' (numeric string) is accepted but ex='1.5' is not (isdigit is False).
Common situations: Passing a float for sub-second expiry (use px instead); passing a bool by accident; passing a Decimal or numpy integer; computing ex from a division that yields a float.
Related errors
- px must be datetime.timedelta or int
- Subcommand {subcommand_name} not found in command {command_n
- Command {command_name} not found in commands
- ``ex``, ``px``, ``exat``, ``pxat``, and ``persist`` are mutu
- ``enx`` requires one of ``ex``, ``px``, ``exat``, or ``pxat`
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/66750a8f519d9fe7.json.
Report an issue: GitHub.