pypa/pip · error · UndefinedComparison

Set-valued marker {environment_key!r} can only be used with

Error message

Set-valued marker {environment_key!r} can only be used with the membership form (e.g. "<name>" in {environment_key}); it cannot appear on the left-hand side of {op.serialize()!r}.

What it means

Raised by _evaluate_markers in markers.py:311-317. The set-valued markers 'extras' and 'dependency_groups' (MARKERS_ALLOWING_SET) are stored as frozensets in the evaluation environment and may only be the right-hand operand of a membership test ('<value>' in extras). If such a variable appears on the left of any comparison, its value is a non-str set and UndefinedComparison is raised.

Source

Thrown at src/pip/_vendor/packaging/markers.py:312

    groups: list[list[bool]] = [[]]

    for marker in markers:
        if isinstance(marker, list):
            groups[-1].append(_evaluate_markers(marker, environment))
        elif isinstance(marker, tuple):
            lhs, op, rhs = marker

            if isinstance(lhs, Variable):
                environment_key = lhs.value
                lhs_value = _lookup_environment(environment, environment_key)
                rhs_value = rhs.value
            else:
                lhs_value = lhs.value
                environment_key = rhs.value
                rhs_value = _lookup_environment(environment, environment_key)

            if not isinstance(lhs_value, str):
                raise UndefinedComparison(
                    f"Set-valued marker {environment_key!r} can only be used "
                    f'with the membership form (e.g. "<name>" in '
                    f"{environment_key}); it cannot appear on the left-hand "
                    f"side of {op.serialize()!r}."
                )
            lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
            groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
        elif marker == "or":
            groups.append([])
        elif marker == "and":
            pass
        else:  # pragma: nocover
            raise TypeError(f"Unexpected marker {marker!r}")

    return any(all(item) for item in groups)


def _format_full_version(info: sys._version_info) -> str:

View on GitHub (pinned to f399c37189)

Solutions

  1. Rewrite as a membership test with the literal on the left: '"test" in extras'.
  2. For negation use '"test" not in extras'.

Example fix

# before
Marker('extras == "test"')
# after
Marker('"test" in extras')
Defensive patterns

Strategy: validation

Validate before calling

from packaging.markers import MARKERS_ALLOWING_SET

def membership_form_for_set_marker(marker_str: str) -> bool:
    low = marker_str.lower()
    for key in MARKERS_ALLOWING_SET:
        # the set-valued variable must be the RIGHT operand of 'in'/'not in'
        if key in low and f"{key} ==" in low or f"{key}!=" in low or f"{key}<" in low or f"{key}>" in low:
            return False
    return True

Try / catch

from packaging.markers import Marker, UndefinedComparison

try:
    Marker(expr).evaluate()
except UndefinedComparison as e:
    # rewrite extras/dependency_groups as membership form
    ...

Prevention

When it happens

Trigger: Evaluating Marker('extras == "test"'), Marker('dependency_groups != "dev"'), or 'extras in something' — i.e. extras/dependency_groups used as the left operand instead of the membership form. Triggers once the environment populates them as sets (context 'lock_file' or 'requirement').

Common situations: Writing extras markers like ordinary equality comparisons; following pre-PEP-685 examples; tools auto-generating markers in the wrong direction.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/92d2cc47443f41fe. Report an issue: GitHub.