{"id":"e0a777eca28f26bd","repo":"redis/redis-py","slug":"px-must-be-datetime-timedelta-or-int","errorCode":null,"errorMessage":"px must be datetime.timedelta or int","messagePattern":"px must be datetime\\.timedelta or int","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/utils.py","lineNumber":382,"sourceCode":"    exp_options: list[EncodableT] = []\n    if ex is not None:\n        exp_options.append(\"EX\")\n        if isinstance(ex, datetime.timedelta):\n            exp_options.append(int(ex.total_seconds()))\n        elif isinstance(ex, int):\n            exp_options.append(ex)\n        elif isinstance(ex, str) and ex.isdigit():\n            exp_options.append(int(ex))\n        else:\n            raise DataError(\"ex must be datetime.timedelta or int\")\n    elif px is not None:\n        exp_options.append(\"PX\")\n        if isinstance(px, datetime.timedelta):\n            exp_options.append(int(px.total_seconds() * 1000))\n        elif isinstance(px, int):\n            exp_options.append(px)\n        else:\n            raise DataError(\"px must be datetime.timedelta or int\")\n    elif exat is not None:\n        if isinstance(exat, datetime.datetime):\n            exat = int(exat.timestamp())\n        exp_options.extend([\"EXAT\", exat])\n    elif pxat is not None:\n        if isinstance(pxat, datetime.datetime):\n            pxat = int(pxat.timestamp() * 1000)\n        exp_options.extend([\"PXAT\", pxat])\n\n    return exp_options\n\n\ndef truncate_text(txt, max_length=100):\n    return textwrap.shorten(\n        text=txt, width=max_length, placeholder=\"...\", break_long_words=True\n    )\n\n","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/utils.py#L364-L400","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"# before\nclient.set('k', 'v', px='1500')   # str -> DataError (px does not accept strings)\n# after\nclient.set('k', 'v', px=int('1500'))","handlingStrategy":"type-guard","validationCode":"import datetime\nfrom typing import Any, Union\n\ndef coerce_px(px: Any) -> Union[int, datetime.timedelta, None]:\n    if px is None:\n        return None\n    if isinstance(px, (datetime.timedelta, int)):\n        return px\n    if isinstance(px, str) and px.isdigit():\n        return int(px)           # NOTE: px does not accept str natively, so coerce here\n    if isinstance(px, float):\n        return int(px)\n    raise TypeError('px must be timedelta or int')\n\nclient.set('k', 'v', px=coerce_px(user_value))","typeGuard":"import datetime\nfrom typing import Any\n\ndef is_valid_px(value: Any) -> bool:\n    # Note: stricter than ex -- px does NOT accept numeric strings natively\n    return value is None or isinstance(value, (datetime.timedelta, int))","tryCatchPattern":"from redis.exceptions import DataError\n\ntry:\n    client.set('k', 'v', px=value)\nexcept DataError:\n    client.set('k', 'v', px=int(value))","preventionTips":["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."],"tags":["types","validation","expiry","commands","set"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}