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

  1. Pass an int: client.set('k', 'v', px=int(value)).
  2. Use datetime.timedelta: client.set('k', 'v', px=datetime.timedelta(milliseconds=value)).
  3. 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

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


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