{"id":"c7117076d08a332a","repo":"redis/redis-py","slug":"ex-px-exat-pxat-and-keepttl","errorCode":null,"errorMessage":"``ex``, ``px``, ``exat``, ``pxat``, and ``keepttl`` are mutually exclusive.","messagePattern":"``ex``, ``px``, ``exat``, ``pxat``, and ``keepttl`` are mutually exclusive\\.","errorType":"validation","errorClass":"DataError","httpStatus":null,"severity":"error","filePath":"redis/commands/core.py","lineNumber":3884,"sourceCode":"            specified in unix time.\n\n        ``keepttl`` if True, retain the time to live associated with the keys.\n\n        Returns the number of fields that were added.\n\n        Available since Redis 8.4\n        For more information, see https://redis.io/commands/msetex\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                keepttl,\n            )\n        ):\n            raise DataError(\n                \"``ex``, ``px``, ``exat``, ``pxat``, \"\n                \"and ``keepttl`` are mutually exclusive.\"\n            )\n\n        exp_options: list[EncodableT] = []\n        if data_persist_option:\n            exp_options.append(data_persist_option.value)\n\n        exp_options.extend(extract_expire_flags(ex, px, exat, pxat))\n\n        if keepttl:\n            exp_options.append(\"KEEPTTL\")\n\n        pieces = [\"MSETEX\", len(mapping)]\n\n        for pair in mapping.items():\n            pieces.extend(pair)\n","sourceCodeStart":3866,"sourceCodeEnd":3902,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/commands/core.py#L3866-L3902","documentation":"msetex() (Redis 8.4+) sets multiple keys with an optional expiry policy. ex, px, exat, pxat, and keepttl are mutually exclusive because a key can only have one TTL directive. The at_most_one_value_set guard at core.py:3875 raises DataError when two or more are set.","triggerScenarios":"r.msetex({k1:v1, k2:v2}, ex=60, keepttl=True) or any pair among the five options. data_persist_option (NX/XX) is independent and not part of this check.","commonSituations":"Reusing an expiry-config object across set/msetex that includes keepttl alongside a relative expiry; migrating from set() (which uses keepttl) to msetex() (which treats keepttl as exclusive).","solutions":["Pass exactly one of ex/px/exat/pxat/keepttl, e.g. r.msetex(mapping, ex=60).","Use keepttl=True alone to retain existing TTLs on overwritten keys.","Build a single 'choose one expiry' resolver shared across all set-family calls."],"exampleFix":"# before\nr.msetex({'a': '1', 'b': '2'}, ex=60, keepttl=True)\n\n# after\nr.msetex({'a': '1', 'b': '2'}, ex=60)","handlingStrategy":"validation","validationCode":"expiry = {'ex': ex, 'px': px, 'exat': exat, 'pxat': pxat}\nif keepttl:\n    expiry['keepttl'] = True\nif sum(bool(v) for v in expiry.values()) > 1:\n    raise ValueError('msetex: pass exactly one expiry option')\nr.msetex(mapping, **expiry)","typeGuard":"def valid_msetex_expiry(ex, px, exat, pxat, keepttl) -> bool:\n    return sum(bool(x) for x in (ex, px, exat, pxat, keepttl)) <= 1","tryCatchPattern":"from redis.exceptions import DataError\ntry:\n    r.msetex(mapping, ex=ex, keepttl=keepttl)\nexcept DataError as e:\n    if 'mutually exclusive' in str(e):\n        r.msetex(mapping, ex=ex)\n    else:\n        raise","preventionTips":["keepttl is exclusive here (unlike set() where it coexists with other options in some clients); do not pair it with ex/px/exat/pxat.","Resolve to a single expiry kwarg before calling msetex."],"tags":["redis","msetex","validation","parameter-conflict","expiry"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}