redis/redis-py · error · DataError

``ex``, ``px``, ``exat``, ``pxat``, and ``persist`` are mutu

Error message

``ex``, ``px``, ``exat``, ``pxat``, and ``persist`` are mutually exclusive.

What it means

getex() sets or removes a key's TTL via exactly one mechanism. ex (seconds), px (ms), exat (unix-seconds), pxat (unix-ms), and persist are mutually exclusive; at most one may be set. The at_most_one_value_set check at core.py:3274 enforces this with DataError.

Source

Thrown at redis/commands/core.py:3283

        specified in unix time.

        ``pxat`` sets an expire flag on key ``name`` for ``ex`` milliseconds,
        specified in unix time.

        ``persist`` remove the time to live associated with ``name``.

        For more information, see https://redis.io/commands/getex
        """
        if not at_most_one_value_set(
            (
                ex is not None,
                px is not None,
                exat is not None,
                pxat is not None,
                persist,
            )
        ):
            raise DataError(
                "``ex``, ``px``, ``exat``, ``pxat``, "
                "and ``persist`` are mutually exclusive."
            )

        exp_options: list[EncodableT] = extract_expire_flags(ex, px, exat, pxat)

        if persist:
            exp_options.append("PERSIST")

        return self.execute_command("GETEX", name, *exp_options)

    def __getitem__(self, name: KeyT):
        """
        Return the value at key ``name``, raises a KeyError if the key
        doesn't exist.
        """
        value = self.get(name)
        if value is not None:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Choose one expiry unit and pass only it, e.g. r.getex('key', ex=60).
  2. Use persist=True alone to strip the TTL, or an expiry option alone to set it.
  3. Centralize expiry selection in a helper that returns exactly one of the five.

Example fix

# before
r.getex('key', ex=60, px=60000)

# after
r.getex('key', ex=60)  # pick one unit
Defensive patterns

Strategy: validation

Validate before calling

expiry = {'ex': ex, 'px': px, 'exat': exat, 'pxat': pxat}
if persist:
    expiry['persist'] = True
set_count = sum(v is not None and v is not False for v in expiry.values())
if set_count > 1:
    raise ValueError('getex: pass exactly one expiry option')
r.getex('key', **expiry)

Type guard

def valid_single_expiry(ex, px, exat, pxat, persist) -> bool:
    return sum(bool(x) for x in (ex, px, exat, pxat, persist)) <= 1

Try / catch

from redis.exceptions import DataError
try:
    r.getex('key', ex=ex, px=px)
except DataError as e:
    if 'mutually exclusive' in str(e):
        r.getex('key', ex=ex)  # keep one, drop the rest
    else:
        raise

Prevention

When it happens

Trigger: r.getex('key', ex=10, px=1000), r.getex('key', exat=..., persist=True), or any combination of two or more of the five options.

Common situations: Generic TTL helper that forwards multiple optional kwargs; config-driven expiry where seconds and ms were both populated; refactoring from set() which uses keepttl instead of persist.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/5676bf36fbc0698f.json. Report an issue: GitHub.