{"id":"1f0fcbc721888f2e","repo":"pypa/pip","slug":"comma-expected-in-extras-s","errorCode":null,"errorMessage":"comma expected in extras: %s","messagePattern":"comma expected in extras: (.+?)","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":174,"sourceCode":"    remaining = remaining[m.end():]\n    extras = mark_expr = versions = uri = None\n    if remaining and remaining[0] == '[':\n        i = remaining.find(']', 1)\n        if i < 0:\n            raise SyntaxError('unterminated extra: %s' % remaining)\n        s = remaining[1:i]\n        remaining = remaining[i + 1:].lstrip()\n        extras = []\n        while s:\n            m = IDENTIFIER.match(s)\n            if not m:\n                raise SyntaxError('malformed extra: %s' % s)\n            extras.append(m.groups()[0])\n            s = s[m.end():]\n            if not s:\n                break\n            if s[0] != ',':\n                raise SyntaxError('comma expected in extras: %s' % s)\n            s = s[1:].lstrip()\n        if not extras:\n            extras = None\n    if remaining:\n        if remaining[0] == '@':\n            # it's a URI\n            remaining = remaining[1:].lstrip()\n            m = NON_SPACE.match(remaining)\n            if not m:\n                raise SyntaxError('invalid URI: %s' % remaining)\n            uri = m.groups()[0]\n            t = urlparse(uri)\n            # there are issues with Python and URL parsing, so this test\n            # is a bit crude. See bpo-20271, bpo-23505. Python doesn't\n            # always parse invalid URLs correctly - it should raise\n            # exceptions for malformed URLs\n            if not (t.scheme and t.netloc):\n                raise SyntaxError('Invalid URL: %s' % uri)","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L156-L192","documentation":"Raised by distlib's `parse_requirement` while parsing a multi-value extras list: after consuming one extra, the next character must be `,` to separate further extras. If it isn't (e.g. a stray letter or punctuation), the parser raises `SyntaxError: comma expected in extras`.","triggerScenarios":"Parsing a requirement like `pkg[extra1 extra2]` (space instead of comma) or `pkg[extra1;extra2]`. Triggered when the extras list contains more than one entry without comma separators.","commonSituations":"Using spaces, semicolons, or other delimiters instead of commas between extras; typos; copy-paste from sources that used a different separator.","solutions":["Separate multiple extras with commas: `pkg[extra1,extra2]`.","Remove whitespace/punctuation that isn't a comma between extras.","Validate with `packaging.requirements.Requirement(...)`."],"exampleFix":"# before\nrequests[security socks]\n\n# after\nrequests[security,socks]","handlingStrategy":"validation","validationCode":"from packaging.requirements import Requirement, InvalidRequirement\n\ndef validate_requirement(line: str) -> None:\n    try:\n        Requirement(line)\n    except InvalidRequirement as e:\n        raise ValueError(f\"Bad requirement: {e}\") from e","typeGuard":"import re\ndef extras_use_commas(line: str) -> bool:\n    m = re.search(r\"\\[([^\\]]*)\\]\", line)\n    if not m:\n        return True\n    inner = m.group(1).strip()\n    if not inner:\n        return True\n    # multiple extras must be comma-separated\n    parts = inner.split(\",\")\n    return all(re.match(r\"^[A-Za-z_][\\\\w.-]*$\", p.strip()) for p in parts)","tryCatchPattern":"null","preventionTips":["Separate multiple extras with commas only: `pkg[a,b,c]`.","Avoid spaces/semicolons between extras.","Validate with `packaging.requirements.Requirement`."],"tags":["distlib","requirements-parsing","extras"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}