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
- Remove the trailing/duplicate comma: Requirement('requests[security,extras]').
- When generating programmatically, filter falsy entries: f"pkg[{','.join(e for e in extras if e)}]".
- 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
- Build extras lists with a filtered join: ','.join(e for e in extras if e).
- Validate each extra against [A-Za-z0-9][A-Za-z0-9._-]* before joining.
- Never leave a trailing comma inside [...].
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
- Expected end of dependency specifier
- Expected URL after @
- Expected whitespace after URL
- Expected end of marker expression
- Requested {ireq} has inconsistent Provides-Extra between its
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/7339376c9970e3ad.json.
Report an issue: GitHub.