{"id":"7339376c9970e3ad","repo":"pypa/pip","slug":"expected-extra-name-after-comma","errorCode":null,"errorMessage":"Expected extra name after comma","messagePattern":"Expected extra name after comma","errorType":"validation","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":237,"sourceCode":"    \"\"\"\n    extras: list[str] = []\n\n    if not tokenizer.check(\"IDENTIFIER\"):\n        return extras\n\n    extras.append(tokenizer.read().text)\n\n    while True:\n        tokenizer.consume(\"WS\")\n        if tokenizer.check(\"IDENTIFIER\", peek=True):\n            tokenizer.raise_syntax_error(\"Expected comma between extra names\")\n        elif not tokenizer.check(\"COMMA\"):\n            break\n\n        tokenizer.read()\n        tokenizer.consume(\"WS\")\n\n        extra_token = tokenizer.expect(\"IDENTIFIER\", expected=\"extra name after comma\")\n        extras.append(extra_token.text)\n\n    return extras\n\n\ndef _parse_specifier(tokenizer: Tokenizer) -> str:\n    \"\"\"\n    specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS\n              | WS? version_many WS?\n    \"\"\"\n    with tokenizer.enclosing_tokens(\n        \"LEFT_PARENTHESIS\",\n        \"RIGHT_PARENTHESIS\",\n        around=\"version specifier\",\n    ):\n        tokenizer.consume(\"WS\")\n        parsed_specifiers = _parse_version_many(tokenizer)\n        tokenizer.consume(\"WS\")","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/_parser.py#L219-L255","documentation":"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).","triggerScenarios":"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.","commonSituations":"Hand-edited extras lists with a trailing or duplicated comma, lists generated by ''.join without filtering empties, or refactoring that leaves a dangling comma.","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."],"exampleFix":"# before\nRequirement('requests[security,]')\n\n# after\nRequirement('requests[security]')","handlingStrategy":"validation","validationCode":"import re\n\n_EXTRA_NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9._-]*')\n\ndef build_extras(extras: list[str]) -> str:\n    clean = [e for e in (_EXTRA_NAME.fullmatch(x.strip()).group(0)\n                        for x in extras)\n             if _EXTRA_NAME.fullmatch(e)]\n    return '' if not clean else f\"[{','.join(clean)}]\"","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(req_str)\nexcept InvalidRequirement as e:\n    if 'extra name after comma' in str(e):\n        raise ValueError(f'malformed extras in {req_str!r}: trailing/duplicate comma') from e\n    raise","preventionTips":["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 [...]."],"tags":["packaging","parsing","requirements","extras","syntax"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}