redis/redis-py · error · DataError
``ex``, ``px``, ``exat``, ``pxat``, and ``keepttl`` are mutu
Error message
``ex``, ``px``, ``exat``, ``pxat``, and ``keepttl`` are mutually exclusive.
What it means
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.
Source
Thrown at redis/commands/core.py:3884
specified in unix time.
``keepttl`` if True, retain the time to live associated with the keys.
Returns the number of fields that were added.
Available since Redis 8.4
For more information, see https://redis.io/commands/msetex
"""
if not at_most_one_value_set(
(
ex is not None,
px is not None,
exat is not None,
pxat is not None,
keepttl,
)
):
raise DataError(
"``ex``, ``px``, ``exat``, ``pxat``, "
"and ``keepttl`` are mutually exclusive."
)
exp_options: list[EncodableT] = []
if data_persist_option:
exp_options.append(data_persist_option.value)
exp_options.extend(extract_expire_flags(ex, px, exat, pxat))
if keepttl:
exp_options.append("KEEPTTL")
pieces = ["MSETEX", len(mapping)]
for pair in mapping.items():
pieces.extend(pair)
View on GitHub (pinned to da03cdc7e8)
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.
Example fix
# before
r.msetex({'a': '1', 'b': '2'}, ex=60, keepttl=True)
# after
r.msetex({'a': '1', 'b': '2'}, ex=60) Defensive patterns
Strategy: validation
Validate before calling
expiry = {'ex': ex, 'px': px, 'exat': exat, 'pxat': pxat}
if keepttl:
expiry['keepttl'] = True
if sum(bool(v) for v in expiry.values()) > 1:
raise ValueError('msetex: pass exactly one expiry option')
r.msetex(mapping, **expiry) Type guard
def valid_msetex_expiry(ex, px, exat, pxat, keepttl) -> bool:
return sum(bool(x) for x in (ex, px, exat, pxat, keepttl)) <= 1 Try / catch
from redis.exceptions import DataError
try:
r.msetex(mapping, ex=ex, keepttl=keepttl)
except DataError as e:
if 'mutually exclusive' in str(e):
r.msetex(mapping, ex=ex)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- ``ex``, ``px``, ``exat``, ``pxat``, and ``persist`` are mutu
- Both start and end must be specified
- start argument is not set, when end is specified
- Only one of IFEQ/IFNE/IFDEQ/IFDNE may be specified
- ``byfloat`` and ``byint`` are mutually exclusive.
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/c7117076d08a332a.json.
Report an issue: GitHub.