{"record":{"id":"92d2cc47443f41fe","repo":"pypa/pip","slug":"set-valued-marker-environment-key-r-can-only-be","errorCode":null,"errorMessage":"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}.","messagePattern":"Set-valued marker (.+?) can only be used with the membership form \\(e\\.g\\. \"<name>\" in (.+?)\\); it cannot appear on the left-hand side of (.+?)\\.","errorType":"exception","errorClass":"UndefinedComparison","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/markers.py","lineNumber":312,"sourceCode":"    groups: list[list[bool]] = [[]]\n\n    for marker in markers:\n        if isinstance(marker, list):\n            groups[-1].append(_evaluate_markers(marker, environment))\n        elif isinstance(marker, tuple):\n            lhs, op, rhs = marker\n\n            if isinstance(lhs, Variable):\n                environment_key = lhs.value\n                lhs_value = _lookup_environment(environment, environment_key)\n                rhs_value = rhs.value\n            else:\n                lhs_value = lhs.value\n                environment_key = rhs.value\n                rhs_value = _lookup_environment(environment, environment_key)\n\n            if not isinstance(lhs_value, str):\n                raise UndefinedComparison(\n                    f\"Set-valued marker {environment_key!r} can only be used \"\n                    f'with the membership form (e.g. \"<name>\" in '\n                    f\"{environment_key}); it cannot appear on the left-hand \"\n                    f\"side of {op.serialize()!r}.\"\n                )\n            lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)\n            groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))\n        elif marker == \"or\":\n            groups.append([])\n        elif marker == \"and\":\n            pass\n        else:  # pragma: nocover\n            raise TypeError(f\"Unexpected marker {marker!r}\")\n\n    return any(all(item) for item in groups)\n\n\ndef _format_full_version(info: sys._version_info) -> str:","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/markers.py#L294-L330","documentation":"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.","triggerScenarios":"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').","commonSituations":"Writing extras markers like ordinary equality comparisons; following pre-PEP-685 examples; tools auto-generating markers in the wrong direction.","solutions":["Rewrite as a membership test with the literal on the left: '\"test\" in extras'.","For negation use '\"test\" not in extras'."],"exampleFix":"# before\nMarker('extras == \"test\"')\n# after\nMarker('\"test\" in extras')","handlingStrategy":"validation","validationCode":"from packaging.markers import MARKERS_ALLOWING_SET\n\ndef membership_form_for_set_marker(marker_str: str) -> bool:\n    low = marker_str.lower()\n    for key in MARKERS_ALLOWING_SET:\n        # the set-valued variable must be the RIGHT operand of 'in'/'not in'\n        if key in low and f\"{key} ==\" in low or f\"{key}!=\" in low or f\"{key}<\" in low or f\"{key}>\" in low:\n            return False\n    return True\n","typeGuard":null,"tryCatchPattern":"from packaging.markers import Marker, UndefinedComparison\n\ntry:\n    Marker(expr).evaluate()\nexcept UndefinedComparison as e:\n    # rewrite extras/dependency_groups as membership form\n    ...\n","preventionTips":["Always write '\"value\" in extras' / '\"value\" in dependency_groups' (literal on the left).","Never put extras or dependency_groups on the left of a comparison."],"tags":["markers","extras","dependency-groups","pep685","pep735"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}