{"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":"validation","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":118,"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":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/_parser.py#L100-L136","documentation":"Raised by the recursive-descent requirement parser at src/pip/_vendor/packaging/_parser.py:118, where _parse_requirement calls tokenizer.expect(\"END\", ...) after a name, optional extras, URL/version-specifier, and optional marker have all been parsed. \"END\" is the `$` anchor (src/pip/_vendor/packaging/_tokenizer.py:87), so the error means the parser finished a grammatically complete requirement but the input string still has unconsumed, non-whitespace characters. It surfaces to library users as packaging.requirements.InvalidRequirement (requirements.py:56-57 re-raises ParserSyntaxError).","triggerScenarios":"Calling Requirement(\"...\") (or pip parsing a requirement line) with trailing junk after a complete spec: e.g. 'requests==2.0 bar', 'django>=3.0 ; python_version>=\"3.8\" )' (stray paren), 'foo @ https://x.org/w ; extra_token', or a character no token rule matches (such as '#', '*', or '@' in an unexpected position) left over once the grammar is satisfied.","commonSituations":"Hand-edited requirements.txt lines that concatenate two requirements on one line, copy-pasted specs that include a trailing comment char without a separator, programmatically-built requirement strings with an accidental extra fragment, or a stray closing bracket/paren left after editing. Also seen when a URL-style requirement has a trailing fragment the grammar does not accept.","solutions":["Trim the offending requirement string to a single specifier and re-parse; the error's span (e.span) points at the first unconsumed character.","Split one physical line into multiple requirements (one per line) instead of concatenating with spaces.","If you intended a marker, put it after a single ';' with one space; if you intended extras, use '[a,b]' immediately after the name.","Move inline comments to their own line prefixed with '#' (pip strips those; the packaging parser does not)."],"exampleFix":"# before\nRequirement('requests==2.0 bar')\n\n# after\nRequirement('requests==2.0')","handlingStrategy":"try-catch","validationCode":"import re\nfrom pip._vendor.packaging.requirements import Requirement, InvalidRequirement\n\n_REQ_RE = re.compile(r'^\\s*[A-Za-z0-9][A-Za-z0-9._-]*\\s*(\\[[^\\]]*\\])?\\s*([^;]*)(;.*)?$')\n\ndef looks_complete_requirement(s: str) -> bool:\n    # Cheap pre-check: no obvious trailing junk after the marker/specifier.\n    return bool(_REQ_RE.match(s)) and not s.rstrip().endswith((',', ')', '('))","typeGuard":"from typing import TypeGuard\nfrom pip._vendor.packaging.requirements import Requirement, InvalidRequirement\n\ndef is_valid_requirement(s: str) -> TypeGuard[str]:\n    try:\n        Requirement(s)\n    except InvalidRequirement:\n        return False\n    return True","tryCatchPattern":"from pip._vendor.packaging.requirements import Requirement, InvalidRequirement\n\ntry:\n    req = Requirement(user_string)\nexcept InvalidRequirement as e:\n    raise UserFacingError(f\"Bad requirement {user_string!r}: {e}\") from e","preventionTips":["Parse requirement strings at config-load time with Requirement(...) and fail fast, never at install time.","Never concatenate multiple requirements on one line; keep one requirement per line.","When templating, strip trailing commas/whitespace and assert the result parses before writing it to requirements.txt."],"tags":["packaging","parsing","requirements","pip","syntax"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}