{"record":{"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":"exception","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/6a6b581b48225afa0b76912d1028c6035baee932/redis/utils.py#L364-L400","documentation":"Raised by extract_expire_flags() (redis/utils.py:382) as redis.exceptions.DataError. The `px` argument (relative expire in milliseconds) is accepted ONLY as datetime.timedelta or int (utils.py:377-380) — unlike `ex`, a digit-string is NOT accepted here. Any other type (float, string, list) raises DataError. A timedelta is converted via int(px.total_seconds()*1000).","triggerScenarios":"Calling a SET-family command with px=1500.5 (float), px='1500' (string — note: ex accepts digit-strings but px does NOT, a common surprise), px=[1500], or any non-int/non-timedelta value. The `else` branch at utils.py:381 fires.","commonSituations":"Reusing a string-typed config value for px ('1500' instead of 1500); passing a float millisecond value; assuming px accepts the same types as ex; computing px from a float seconds-to-ms multiplication without casting to int.","solutions":["Pass px as an int milliseconds: px=1500.","Pass px as datetime.timedelta(milliseconds=...) or timedelta(seconds=...).","Convert string input with int() before passing: px=int(cfg['px']).","If you have seconds as a float, compute px=int(sec*1000) to keep millisecond precision as an int."],"exampleFix":"# before\nr.set('k', 'v', px='1500')   # DataError: px must be datetime.timedelta or int\nr.set('k', 'v', px=1500.5)   # also raises (float)\n\n# after\nimport datetime\nr.set('k', 'v', px=int('1500'))                          # int ms\nr.set('k', 'v', px=datetime.timedelta(milliseconds=1500))  # timedelta\nr.set('k', 'v', px=int(1.5 * 1000))                       # coerce float sec to int ms","handlingStrategy":"validation","validationCode":"import datetime\n\ndef coerce_px(px):\n    # call BEFORE r.set(..., px=px); px does NOT accept digit-strings (unlike ex)\n    if px is None or isinstance(px, (int, datetime.timedelta)):\n        return px\n    if isinstance(px, str):\n        if px.isdigit():\n            return int(px)\n        raise TypeError('px string must be all digits')\n    raise TypeError('px must be int or timedelta')","typeGuard":"import datetime\n\ndef is_valid_px(px) -> bool:\n    return (\n        px is None\n        or isinstance(px, int)\n        or isinstance(px, datetime.timedelta)\n    )","tryCatchPattern":"import redis.exceptions\ntry:\n    r.set('k', 'v', px=ms_ttl)\nexcept redis.exceptions.DataError as e:\n    if 'px must be' in str(e):\n        ms_ttl = int(float(ms_ttl))  # px rejects strings/floats; coerce to int\n        r.set('k', 'v', px=ms_ttl)","preventionTips":["Pass px as int milliseconds only — it rejects strings and floats (unlike ex).","Convert any string/float ttl to int ms before the SET call.","Prefer datetime.timedelta(milliseconds=...) for readable, type-safe expiry."],"tags":["set","expiry","data-error","validation"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}