pypa/pip · error · SyntaxError

malformed extra: %s

Error message

malformed extra: %s

What it means

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`.

Source

Thrown at src/pip/_vendor/distlib/util.py:168

    if not remaining or remaining.startswith('#'):
        return None
    m = IDENTIFIER.match(remaining)
    if not m:
        raise SyntaxError('name expected: %s' % remaining)
    distname = m.groups()[0]
    remaining = remaining[m.end():]
    extras = mark_expr = versions = uri = None
    if remaining and remaining[0] == '[':
        i = remaining.find(']', 1)
        if i < 0:
            raise SyntaxError('unterminated extra: %s' % remaining)
        s = remaining[1:i]
        remaining = remaining[i + 1:].lstrip()
        extras = []
        while s:
            m = IDENTIFIER.match(s)
            if not m:
                raise SyntaxError('malformed extra: %s' % s)
            extras.append(m.groups()[0])
            s = s[m.end():]
            if not s:
                break
            if s[0] != ',':
                raise SyntaxError('comma expected in extras: %s' % s)
            s = s[1:].lstrip()
        if not extras:
            extras = None
    if remaining:
        if remaining[0] == '@':
            # it's a URI
            remaining = remaining[1:].lstrip()
            m = NON_SPACE.match(remaining)
            if not m:
                raise SyntaxError('invalid URI: %s' % remaining)
            uri = m.groups()[0]
            t = urlparse(uri)

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Use valid Python-identifier-like extra names (start with letter/underscore, then `[A-Za-z0-9_.-]`).
  2. Cross-check the extra name against the package's declared extras (`pip show <pkg>` or the package docs).
  3. Remove extras you don't actually need.

Example fix

# before
requests[2security]

# after
requests[security]
Defensive patterns

Strategy: validation

Validate before calling

from packaging.requirements import Requirement, InvalidRequirement

def validate_requirement(line: str) -> None:
    try:
        Requirement(line)
    except InvalidRequirement as e:
        raise ValueError(f"Bad requirement: {e}") from e

Type guard

import re
_EXTRA = re.compile(r"^[A-Za-z_][\\w.-]*$")
def all_extras_valid(line: str) -> bool:
    import re as _r
    m = _r.search(r"\[([^\]]*)\]", line)
    if not m:
        return True
    parts = [p.strip() for p in m.group(1).split(",") if p.strip()]
    return all(bool(_EXTRA.match(p)) for p in parts)

Try / catch

null

Prevention

When it happens

Trigger: 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.

Common situations: Typos in extras names; invalid characters inside extras; copy-paste of wrong content; tooling generating malformed extras lists.

Related errors


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