pypa/pip · error · UndefinedComparison
Undefined on and .
Error message
Undefined {op!r} on {lhs!r} and {rhs!r}. What it means
Raised by _eval_op in markers.py:254-256 during Marker.evaluate. _operators supports only 'in','not in','<','<=','==','!=','>=','>'. Version-requiring keys (python_version, python_full_version, implementation_version, platform_release) are routed through a Specifier first, so any operator works for them. For all other keys, an operator not in the table — most notably '~=' (compatible-release) — yields UndefinedComparison (a ValueError).
Solutions
- Use '==' / '!=' (or 'in'/'not in') for non-version string markers.
- Reserve '~=' for version markers: python_version, python_full_version, implementation_version, platform_release.
Example fix
# before
Marker('os_name ~= "posix"').evaluate()
# after
Marker('os_name == "posix"').evaluate() Defensive patterns
Strategy: validation
Validate before calling
from packaging.markers import MARKERS_REQUIRING_VERSION
_ALLOWED_STRING_OPS = {"in", "not in", "<", "<=", "==", "!=", ">=", ">"}
def op_allowed_for_key(op_str: str, env_key: str) -> bool:
return env_key in MARKERS_REQUIRING_VERSION or op_str in _ALLOWED_STRING_OPS
Try / catch
from packaging.markers import Marker, UndefinedComparison
try:
Marker(expr).evaluate()
except UndefinedComparison as e:
# unsupported operator for this marker variable
...
Prevention
- Reserve '~=' for version markers (python_version, python_full_version, implementation_version, platform_release).
- Use ==/!=/in/not in for string-valued markers.
When it happens
Trigger: Evaluating Marker('os_name ~= "posix"'), Marker('sys_platform ~= "linux"'), or any marker using ~= against a non-version environment variable (os_name, sys_platform, platform_machine, platform_python_implementation, implementation_name, extra, etc.).
Common situations: Copying a PEP 440 version-specifier operator into a platform/OS marker; assuming every PEP 440 operator works on string-valued markers.
Related errors
- Cannot serialize marker value containing both quote…
- Invalid quoted string
- dev must be non-negative integer, got
- {e}
- epoch must be non-negative integer, got
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/80850c1101a5d096.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/markers.py:256
"!=": 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 f399c37189)