{"id":"70f3859428e3716a","repo":"pypa/pip","slug":"malformed-extra-s","errorCode":null,"errorMessage":"malformed extra: %s","messagePattern":"malformed extra: (.+?)","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":168,"sourceCode":"    if not remaining or remaining.startswith('#'):\n        return None\n    m = IDENTIFIER.match(remaining)\n    if not m:\n        raise SyntaxError('name expected: %s' % remaining)\n    distname = m.groups()[0]\n    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)","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L150-L186","documentation":"Raised by distlib's `parse_requirement` while parsing the contents of the extras list: each item must match the `IDENTIFIER` regex; if the first token inside `[...]` is not a valid identifier (e.g. starts with a digit or punctuation), the parser raises `SyntaxError: malformed extra`.","triggerScenarios":"Parsing a requirement like `pkg[1invalid]` or `pkg[+foo]` where an extras entry doesn't start with a letter/underscore. Triggered when pip parses the extras section of a requirement string.","commonSituations":"Typos in extras names; invalid characters inside extras; copy-paste of wrong content; tooling generating malformed extras lists.","solutions":["Use valid Python-identifier-like extra names (start with letter/underscore, then `[A-Za-z0-9_.-]`).","Cross-check the extra name against the package's declared extras (`pip show <pkg>` or the package docs).","Remove extras you don't actually need."],"exampleFix":"# before\nrequests[2security]\n\n# after\nrequests[security]","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\n_EXTRA = re.compile(r\"^[A-Za-z_][\\\\w.-]*$\")\ndef all_extras_valid(line: str) -> bool:\n    import re as _r\n    m = _r.search(r\"\\[([^\\]]*)\\]\", line)\n    if not m:\n        return True\n    parts = [p.strip() for p in m.group(1).split(\",\") if p.strip()]\n    return all(bool(_EXTRA.match(p)) for p in parts)","tryCatchPattern":"null","preventionTips":["Use identifier-like extra names (start with letter/underscore).","Cross-check extras against the package's declared extras list.","Validate requirements with `packaging.requirements.Requirement`."],"tags":["distlib","requirements-parsing","extras"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}