{"id":"04a64b0fc2b3a91f","repo":"pypa/pip","slug":"expected-end-of-marker-expression","errorCode":null,"errorMessage":"Expected end of marker expression","messagePattern":"Expected end of marker expression","errorType":"validation","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":298,"sourceCode":"        tokenizer.consume(\"WS\")\n        if not tokenizer.check(\"COMMA\"):\n            break\n        parsed_specifiers += tokenizer.read().text\n        tokenizer.consume(\"WS\")\n\n    return parsed_specifiers\n\n\n# --------------------------------------------------------------------------------------\n# Recursive descent parser for marker expression\n# --------------------------------------------------------------------------------------\ndef parse_marker(source: str) -> MarkerList:\n    return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))\n\n\ndef _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:\n    retval = _parse_marker(tokenizer)\n    tokenizer.expect(\"END\", expected=\"end of marker expression\")\n    return retval\n\n\ndef _parse_marker(tokenizer: Tokenizer) -> MarkerList:\n    \"\"\"\n    marker = marker_atom (BOOLOP marker_atom)+\n    \"\"\"\n    expression = [_parse_marker_atom(tokenizer)]\n    while tokenizer.check(\"BOOLOP\"):\n        token = tokenizer.read()\n        expr_right = _parse_marker_atom(tokenizer)\n        expression.extend((token.text, expr_right))\n    return expression\n\n\ndef _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:\n    \"\"\"\n    marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/_parser.py#L280-L316","documentation":"Raised at src/pip/_vendor/packaging/_parser.py:298 by _parse_full_marker: after _parse_marker returns, tokenizer.expect(\"END\", expected=\"end of marker expression\") demands the input be fully consumed. Because END is the `$` anchor (_tokenizer.py:87), the error means a complete marker expression is followed by leftover tokens — typically a dangling boolean operator, a stray parenthesis, or an orphaned token. It surfaces to library users as packaging.markers.InvalidMarker (markers.py:366-367 re-raises ParserSyntaxError).","triggerScenarios":"Marker('python_version >= \"3.8\" and') (trailing 'and' with no right operand), Marker('(sys_platform == \"linux\") )') (extra close paren), Marker('python_version >= \"3.8\" extra'), or any marker string whose boolean chain is incomplete.","commonSituations":"Partially-deleted marker expressions in pyproject.toml/setup.cfg, templated markers with an empty variable, or copy-paste that drops the right-hand side of an 'and'/'or'.","solutions":["Complete the boolean expression with a right-hand operand: Marker('python_version >= \"3.8\" and sys_platform == \"linux\"').","Balance parentheses — every '(' needs a matching ')'.","Use packaging.markers.Marker(...).serialize() after constructing programmatically so the string is always well-formed."],"exampleFix":"# before\nMarker('python_version >= \"3.8\" and')\n\n# after\nMarker('python_version >= \"3.8\" and sys_platform == \"linux\"')","handlingStrategy":"try-catch","validationCode":"import re\n\n# A complete marker must not end with a boolean operator or an unbalanced paren.\n_TRAILING_BOOL = re.compile(r'\\b(and|or|not)\\s*$', re.IGNORECASE)\n\ndef looks_complete_marker(s: str) -> bool:\n    return (s.count('(') == s.count(')')\n            and not _TRAILING_BOOL.search(s.strip()))","typeGuard":"from typing import TypeGuard\nfrom pip._vendor.packaging.markers import Marker, InvalidMarker\n\ndef is_valid_marker(s: str) -> TypeGuard[str]:\n    try:\n        Marker(s)\n    except InvalidMarker:\n        return False\n    return True","tryCatchPattern":"from pip._vendor.packaging.markers import Marker, InvalidMarker\n\ntry:\n    m = Marker(marker_str)\nexcept InvalidMarker as e:\n    raise UserFacingError(f'Bad marker {marker_str!r}: {e}') from e","preventionTips":["Construct markers programmatically and call Marker(...).serialize() rather than hand-writing strings.","Before saving a marker, balance '(' and ')' and ensure it does not end with 'and'/'or'/'not'.","Lint pyproject.toml/setup.cfg markers with is_valid_marker in CI."],"tags":["packaging","parsing","markers","pep508","syntax"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}