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
- Use only the supported operators for non-version markers: '==', '!=', 'in', 'not in', '<', '<=', '>', '>='.
- Move version-style comparisons onto a version marker key (e.g. python_version) so they go through Specifier instead.
- Sanitize/validate the marker string against an allow-list of operators before constructing Marker().
- 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
- Restrict marker operators to the allow-list ==, !=, in, not in, <, <=, >, >=.
- Keep version-style operators (~=, ===) on version marker keys only.
- Validate user-supplied marker strings before constructing Marker.
- Catch UndefinedComparison to degrade gracefully.
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
- Invalid requirement: {name.strip()!r}: {exc}
- Invalid requirement: {name!r}: {exc}
- Invalid requirement: {req_as_string!r}: {exc}
- Invalid requirement: {req_string!r}: {exc}
- invalid version: %s
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/80850c1101a5d096.json.
Report an issue: GitHub.