{"id":"0962d7bb66047d24","repo":"redis/redis-py","slug":"enx-requires-one-of-ex-px-exat","errorCode":null,"errorMessage":"``enx`` requires one of ``ex``, ``px``, ``exat``, or ``pxat``.","messagePattern":"``enx`` requires one of ``ex``, ``px``, ``exat``, or ``pxat``\\.","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":3501,"sourceCode":"        ):\n            raise DataError(\"``byfloat`` and ``byint`` are mutually exclusive.\")\n\n        if not at_most_one_value_set(\n            (\n                ex is not None,\n                px is not None,\n                exat is not None,\n                pxat is not None,\n                persist,\n            )\n        ):\n            raise DataError(\n                \"``ex``, ``px``, ``exat``, ``pxat``, \"\n                \"and ``persist`` are mutually exclusive.\"\n            )\n\n        if enx and ex is None and px is None and exat is None and pxat is None:\n            raise DataError(\n                \"``enx`` requires one of ``ex``, ``px``, ``exat``, or ``pxat``.\"\n            )\n\n        pieces: list[EncodableT] = [name]\n\n        if byfloat is not None:\n            pieces.extend((\"BYFLOAT\", byfloat))\n        elif byint is not None:\n            pieces.extend((\"BYINT\", byint))\n\n        if lbound is not None:\n            pieces.extend((\"LBOUND\", lbound))\n        if ubound is not None:\n            pieces.extend((\"UBOUND\", ubound))\n        if saturate:\n            pieces.append(\"SATURATE\")\n\n        pieces.extend(extract_expire_flags(ex, px, exat, pxat))","sourceCodeStart":3483,"sourceCodeEnd":3519,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L3483-L3519","documentation":"increx() supports enx (set expiration only when the key does not already have one), but enx is meaningless without an actual expiry value to set. The guard at core.py:3500 raises DataError when enx=True is passed without any of ex/px/exat/pxat.","triggerScenarios":"r.increx('k', byint=1, enx=True) with no expiry option. persist is not a valid companion for enx either.","commonSituations":"Forwarding enx from config without also forwarding the chosen expiry; misunderstanding enx as a standalone 'keep existing TTL' (that is the default behavior when no expiry is passed).","solutions":["Pair enx with exactly one expiry option, e.g. r.increx('k', byint=1, ex=60, enx=True).","If you only want to preserve an existing TTL, simply omit all expiry options (enx not needed).","When building kwargs dynamically, only set enx when an expiry option is also present."],"exampleFix":"# before\nr.increx('k', byint=1, enx=True)\n\n# after\nr.increx('k', byint=1, ex=60, enx=True)","handlingStrategy":"validation","validationCode":"if enx and ex is None and px is None and exat is None and pxat is None:\n    raise ValueError('increx: enx requires an expiry option (ex/px/exat/pxat)')\nr.increx('k', byint=1, ex=ex, enx=enx)","typeGuard":"def valid_enx_usage(enx, ex, px, exat, pxat) -> bool:\n    return not enx or any(x is not None for x in (ex, px, exat, pxat))","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    r.increx('k', byint=1, enx=True)\nexcept DataError as e:\n    if 'enx requires' in str(e):\n        r.increx('k', byint=1, ex=60, enx=True)\n    else:\n        raise","preventionTips":["Only set enx when you are also setting an expiry option.","If you just want to preserve existing TTL, omit all expiry args entirely."],"tags":["redis","increx","validation","expiry","experimental"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}