pypa/pip · error · UndefinedComparison

Undefined {op!r} on {lhs!r} and {rhs!r}.

Error message

Undefined {op!r} on {lhs!r} and {rhs!r}.

What it means

Raised as UndefinedComparison by packaging.markers._eval_op when a marker operator string is not present in the _operators table. The table only contains 'in', 'not in', '<', '<=', '==', '!=', '>=', '>'. Version-comparison keys (python_version etc.) are handled earlier via Specifier, so this error fires for non-version markers combined with an operator packaging does not support (e.g. '===', '~=', '<=>').

Source

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

    "!=": operator.ne,
    ">=": operator.eq,
    ">": lambda _lhs, _rhs: False,
}


def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool:
    op_str = op.serialize()
    if key in MARKERS_REQUIRING_VERSION:
        try:
            spec = Specifier(f"{op_str}{rhs}")
        except InvalidSpecifier:
            pass
        else:
            return spec.contains(lhs, prereleases=True)

    oper: Operator | None = _operators.get(op_str)
    if oper is None:
        raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")

    return oper(lhs, rhs)


def _normalize(
    lhs: str, rhs: str | AbstractSet[str], key: str
) -> tuple[str, str | AbstractSet[str]]:
    # PEP 685 - Comparison of extra names for optional distribution dependencies
    # https://peps.python.org/pep-0685/
    # > When comparing extra names, tools MUST normalize the names being
    # > compared using the semantics outlined in PEP 503 for names
    if key == "extra":
        assert isinstance(rhs, str), "extra value must be a string"
        # Both sides are normalized at this point already
        return (lhs, rhs)
    if key in MARKERS_ALLOWING_SET:
        if isinstance(rhs, str):  # pragma: no cover
            return (canonicalize_name(lhs), canonicalize_name(rhs))

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Use only the supported operators for non-version markers: '==', '!=', 'in', 'not in', '<', '<=', '>', '>='.
  2. Move version-style comparisons onto a version marker key (e.g. python_version) so they go through Specifier instead.
  3. Sanitize/validate the marker string against an allow-list of operators before constructing Marker().
  4. Catch UndefinedComparison and surface a clear error pointing at the offending operator.

Example fix

# before
Marker(\"platform_system === 'Linux'\")
# after
Marker(\"platform_system == 'Linux'\")
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED_OPS = {'==', '!=', 'in', 'not in', '<', '<=', '>', '>='}
import re
def is_safe_marker(expr: str) -> bool:
    # crude: extract operator tokens
    for tok in re.findall(r'\b(?:not in|in|===|==|!=|<=|>=|<|>|~=)\b', expr):
        if tok not in ALLOWED_OPS:
            return False
    return True

Try / catch

from packaging.markers import UndefinedComparison
try:
    Marker(expr).evaluate()
except UndefinedComparison as e:
    log.warning('unsupported marker op: %s', e)

Prevention

When it happens

Trigger: A Marker expression such as Marker(\"platform_system === 'Linux'\"), Marker(\"extra ~= 'foo'\"), or any marker using '===' or '~=' on a non-version field. Also reachable if a custom/abstract Op with an unknown serialize() value is constructed programmatically and passed into the evaluator.

Common situations: Hand-written marker strings authored against PEP 508 examples that include arbitrary operators; copy-pasting a specifier-style operator into a non-version marker; building markers from user input without whitelisting operators.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/80850c1101a5d096.json. Report an issue: GitHub.