redis/redis-py · error · DataError
px must be datetime.timedelta or int
Error message
px must be datetime.timedelta or int
What it means
Raised by extract_expire_flags (redis/utils.py:382) as redis.exceptions.DataError when the `px` (relative expiry in milliseconds) argument is neither a datetime.timedelta nor an int. Unlike `ex`, the px branch does NOT accept numeric strings, so px='1000' raises DataError. Used to assemble PX expiry flags for commands like SET.
Source
Thrown at redis/utils.py:382
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_options
def truncate_text(txt, max_length=100):
return textwrap.shorten(
text=txt, width=max_length, placeholder="...", break_long_words=True
)
View on GitHub (pinned to da03cdc7e8)
Solutions
- Pass an int: client.set('k', 'v', px=int(value)).
- Use datetime.timedelta: client.set('k', 'v', px=datetime.timedelta(milliseconds=value)).
- If you have a string, convert explicitly: px=int(my_str).
Example fix
# before
client.set('k', 'v', px='1500') # str -> DataError (px does not accept strings)
# after
client.set('k', 'v', px=int('1500')) Defensive patterns
Strategy: type-guard
Validate before calling
import datetime
from typing import Any, Union
def coerce_px(px: Any) -> Union[int, datetime.timedelta, None]:
if px is None:
return None
if isinstance(px, (datetime.timedelta, int)):
return px
if isinstance(px, str) and px.isdigit():
return int(px) # NOTE: px does not accept str natively, so coerce here
if isinstance(px, float):
return int(px)
raise TypeError('px must be timedelta or int')
client.set('k', 'v', px=coerce_px(user_value)) Type guard
import datetime
from typing import Any
def is_valid_px(value: Any) -> bool:
# Note: stricter than ex -- px does NOT accept numeric strings natively
return value is None or isinstance(value, (datetime.timedelta, int)) Try / catch
from redis.exceptions import DataError
try:
client.set('k', 'v', px=value)
except DataError:
client.set('k', 'v', px=int(value)) Prevention
- Remember px is stricter than ex: it does not accept numeric strings.
- Always coerce string/float ms values to int before passing as px.
- Use datetime.timedelta(milliseconds=...) for clarity.
- Document the ex-vs-px type asymmetry in your team's Redis usage guide.
When it happens
Trigger: Calling client.set('k', 'v', px='1000') (string not accepted for px), client.set('k', 'v', px=1500.0) (float), or px=True. Note: px=datetime.timedelta(...) is accepted and converted via total_seconds()*1000.
Common situations: Assuming px mirrors ex's string acceptance (it does not); passing a float millisecond value; passing a bool; receiving a string ms value from config/JSON and passing it unconverted.
Related errors
- ex 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/e0a777eca28f26bd.json.
Report an issue: GitHub.