pypa/pip · error · ParserSyntaxError

Expected end of dependency specifier

Error message

Expected end of dependency specifier

What it means

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.

Solutions

  1. Print repr() of the offending requirement string to spot trailing whitespace or stray characters.
  2. Split compound lines on valid separators and parse each requirement individually.
  3. Strip the string and validate it parses with packaging.requirements.Requirement() in a test before committing it.

Example fix

// before
Requirement('requests >= 2.0 ;')

// after
Requirement('requests >= 2.0')
Defensive patterns

Strategy: validation

Validate before calling

from packaging.requirements import Requirement, InvalidRequirement
def valid_requirement(s):
    try:
        Requirement(s)
        return True
    except InvalidRequirement:
        return False

Try / catch

from packaging.requirements import Requirement, InvalidRequirement
try:
    req = Requirement(raw)
except InvalidRequirement as e:
    raise ValueError(f'bad requirement {raw!r}: {e}') from e

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/ae603c8e2bfee7e8. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/_parser.py:126


def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
    """
    requirement = WS? IDENTIFIER WS? extras WS? requirement_details
    """
    tokenizer.consume("WS")

    name_token = tokenizer.expect(
        "IDENTIFIER", expected="package name at the start of dependency specifier"
    )
    name = name_token.text
    tokenizer.consume("WS")

    extras = _parse_extras(tokenizer)
    tokenizer.consume("WS")

    url, specifier, marker = _parse_requirement_details(tokenizer)
    tokenizer.expect("END", expected="end of dependency specifier")

    return ParsedRequirement(name, url, extras, specifier, marker)


def _parse_requirement_details(
    tokenizer: Tokenizer,
) -> tuple[str, str, MarkerList | None]:
    """
    requirement_details = AT URL (WS requirement_marker?)?
                        | specifier WS? (requirement_marker)?
    """

    specifier = ""
    url = ""
    marker = None

    if tokenizer.check("AT"):
        tokenizer.read()

View on GitHub (pinned to f399c37189)