{"record":{"id":"ae603c8e2bfee7e8","repo":"pypa/pip","slug":"expected-end-of-dependency-specifier","errorCode":null,"errorMessage":"Expected end of dependency specifier","messagePattern":"Expected end of dependency specifier","errorType":"exception","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":126,"sourceCode":"\n\ndef _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:\n    \"\"\"\n    requirement = WS? IDENTIFIER WS? extras WS? requirement_details\n    \"\"\"\n    tokenizer.consume(\"WS\")\n\n    name_token = tokenizer.expect(\n        \"IDENTIFIER\", expected=\"package name at the start of dependency specifier\"\n    )\n    name = name_token.text\n    tokenizer.consume(\"WS\")\n\n    extras = _parse_extras(tokenizer)\n    tokenizer.consume(\"WS\")\n\n    url, specifier, marker = _parse_requirement_details(tokenizer)\n    tokenizer.expect(\"END\", expected=\"end of dependency specifier\")\n\n    return ParsedRequirement(name, url, extras, specifier, marker)\n\n\ndef _parse_requirement_details(\n    tokenizer: Tokenizer,\n) -> tuple[str, str, MarkerList | None]:\n    \"\"\"\n    requirement_details = AT URL (WS requirement_marker?)?\n                        | specifier WS? (requirement_marker)?\n    \"\"\"\n\n    specifier = \"\"\n    url = \"\"\n    marker = None\n\n    if tokenizer.check(\"AT\"):\n        tokenizer.read()","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/_parser.py#L108-L144","documentation":"Raised by parse_requirement()/Requirement() as an InvalidRequirement (originating from a ParserSyntaxError) when tokenizer.expect(\"END\", ...) at line 126 fails: after parsing the name, extras, and requirement details, there is leftover input the grammar cannot consume. The parser expected end-of-input but found an unexpected token.","triggerScenarios":"A requirement string with trailing garbage after a valid specifier/marker, e.g. 'pkg >= 1.0 extra_token', 'pkg; python_version >= \"3.0\" )', or two specifier sets without a separator. The recursive descent parser finishes the recognized grammar but the tokenizer is not at END.","commonSituations":"Manually constructed or templated requirement strings with a stray character; concatenating fragments ('pkg>=1.0' + ' ' + stray); requirements.txt lines with invisible/unexpected characters; copy-paste introducing a trailing punctuation.","solutions":["Print repr() of the offending requirement string to spot trailing whitespace or stray characters.","Split compound lines on valid separators and parse each requirement individually.","Strip the string and validate it parses with packaging.requirements.Requirement() in a test before committing it."],"exampleFix":"// before\nRequirement('requests >= 2.0 ;')\n\n// after\nRequirement('requests >= 2.0')","handlingStrategy":"validation","validationCode":"from packaging.requirements import Requirement, InvalidRequirement\ndef valid_requirement(s):\n    try:\n        Requirement(s)\n        return True\n    except InvalidRequirement:\n        return False","typeGuard":null,"tryCatchPattern":"from packaging.requirements import Requirement, InvalidRequirement\ntry:\n    req = Requirement(raw)\nexcept InvalidRequirement as e:\n    raise ValueError(f'bad requirement {raw!r}: {e}') from e","preventionTips":["Always construct requirement strings programmatically and round-trip them through Requirement(...).str() to normalize.","Trim stray characters and avoid concatenating fragments that may leave trailing tokens."],"tags":["packaging","pep508","requirement","parser","syntax-error"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}