pypa/pip · error · DistlibException

Ill-formed name/version string: '%s'

Error message

Ill-formed name/version string: '%s'

What it means

Raised by parse_name_and_version() when its input does not match NAME_VERSION_RE, the expected 'name (version)' form used in metadata fields like Provides-Dist / Obsoletes-Dist. A DistlibException 'Ill-formed name/version string' indicates the string is not in the canonical 'foo (1.0)' shape.

Source

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


# Allow spaces in name because of legacy dists like "Twisted Core"
NAME_VERSION_RE = re.compile(r'(?P<name>[\w .-]+)\s*'
                             r'\(\s*(?P<ver>[^\s)]+)\)$')


def parse_name_and_version(p):
    """
    A utility method used to get name and version from a string.

    From e.g. a Provides-Dist value.

    :param p: A value in a form 'foo (1.0)'
    :return: The name and version as a tuple.
    """
    m = NAME_VERSION_RE.match(p)
    if not m:
        raise DistlibException('Ill-formed name/version string: \'%s\'' % p)
    d = m.groupdict()
    return d['name'].strip().lower(), d['ver']


def get_extras(requested, available):
    result = set()
    requested = set(requested or [])
    available = set(available or [])
    if '*' in requested:
        requested.remove('*')
        result |= available
    for r in requested:
        if r == '-':
            result.add(r)
        elif r.startswith('-'):
            unwanted = r[1:]
            if unwanted not in available:
                logger.warning('undeclared extra: %s' % unwanted)

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Provide the string in 'Name (Version)' form: 'foo (1.0)'.
  2. If parsing a filename, use a filename parser instead and assemble 'name (version)' explicitly.
  3. Pre-validate with the NAME_VERSION_RE regex (or packaging.utils) before calling parse_name_and_version().

Example fix

// before
parse_name_and_version('requests-2.31.0')
// after
parse_name_and_version('requests (2.31.0)')
Defensive patterns

Strategy: validation

Validate before calling

from distlib.util import NAME_VERSION_RE
def safe_parse_nv(p):
    if not NAME_VERSION_RE.match(p):
        raise ValueError('not in Name (Version) form: %r' % p)
    from distlib.util import parse_name_and_version
    return parse_name_and_version(p)

Try / catch

from distlib.util import parse_name_and_version, DistlibException
try:
    name, version = parse_name_and_version(value)
except DistlibException as e:
    if 'Ill-formed name/version string' in str(e):
        name, version = alternative_parse(value)
    else:
        raise

Prevention

When it happens

Trigger: parse_name_and_version('foo 1.0') (no parens), parse_name_and_version('foo'), parse_name_and_version('foo-1.0.tar.gz'), or any value not matching 'Name (Version)'.

Common situations: Reading metadata fields that are inconsistently formatted, parsing sdist filenames instead of metadata values, or legacy metadata that omits parentheses.

Related errors


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