pypa/pip · error · ParserSyntaxError

Expected extra name after comma

Error message

Expected extra name after comma

What it means

Raised at src/pip/_vendor/packaging/_parser.py:237 inside _parse_extras_list. The extras grammar is identifier (',' identifier)*; after consuming a comma (lines 234-235) the parser requires an IDENTIFIER token. expect("IDENTIFIER", expected="extra name after comma") fails when the comma is not followed by a valid extra name (close-bracket, end-of-input, another comma, or an invalid first character).

Source

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

    """
    extras: list[str] = []

    if not tokenizer.check("IDENTIFIER"):
        return extras

    extras.append(tokenizer.read().text)

    while True:
        tokenizer.consume("WS")
        if tokenizer.check("IDENTIFIER", peek=True):
            tokenizer.raise_syntax_error("Expected comma between extra names")
        elif not tokenizer.check("COMMA"):
            break

        tokenizer.read()
        tokenizer.consume("WS")

        extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
        extras.append(extra_token.text)

    return extras


def _parse_specifier(tokenizer: Tokenizer) -> str:
    """
    specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
              | WS? version_many WS?
    """
    with tokenizer.enclosing_tokens(
        "LEFT_PARENTHESIS",
        "RIGHT_PARENTHESIS",
        around="version specifier",
    ):
        tokenizer.consume("WS")
        parsed_specifiers = _parse_version_many(tokenizer)
        tokenizer.consume("WS")

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Remove the trailing/duplicate comma: Requirement('requests[security,extras]').
  2. When generating programmatically, filter falsy entries: f"pkg[{','.join(e for e in extras if e)}]".
  3. Validate each extra name matches the IDENTIFIER rule ([a-zA-Z0-9][a-zA-Z0-9._-]*) before joining.

Example fix

# before
Requirement('requests[security,]')

# after
Requirement('requests[security]')
Defensive patterns

Strategy: validation

Validate before calling

import re

_EXTRA_NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9._-]*')

def build_extras(extras: list[str]) -> str:
    clean = [e for e in (_EXTRA_NAME.fullmatch(x.strip()).group(0)
                        for x in extras)
             if _EXTRA_NAME.fullmatch(e)]
    return '' if not clean else f"[{','.join(clean)}]"

Type guard

from typing import TypeGuard
from pip._vendor.packaging.requirements import Requirement, InvalidRequirement

def is_valid_requirement(s: str) -> TypeGuard[str]:
    try:
        Requirement(s)
    except InvalidRequirement:
        return False
    return True

Try / catch

from pip._vendor.packaging.requirements import Requirement, InvalidRequirement

try:
    req = Requirement(req_str)
except InvalidRequirement as e:
    if 'extra name after comma' in str(e):
        raise ValueError(f'malformed extras in {req_str!r}: trailing/duplicate comma') from e
    raise

Prevention

When it happens

Trigger: Requirement('requests[security,]') (trailing comma), Requirement('requests[a,,b]') (double comma), Requirement('requests[a,]'), Requirement('requests[a, ]'), or Requirement('requests[a,+b]') where the post-comma token is not an IDENTIFIER.

Common situations: Hand-edited extras lists with a trailing or duplicated comma, lists generated by ''.join without filtering empties, or refactoring that leaves a dangling comma.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/7339376c9970e3ad.json. Report an issue: GitHub.