pypa/pip · error · DistlibException

Ill-formed name/version string

Error message

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

What it means

Raised as DistlibException by distlib.util.parse_name_and_version when the input string does not match NAME_VERSION_RE, the 'name (version)' form used in Provides-Dist metadata. The regex expects a name (word chars, spaces, dots, hyphens) followed by a parenthesized version with no spaces inside the parens. Anything else (missing parens, empty version, stray characters) is rejected.

Solutions

  1. Convert the input to 'Name (Version)' form first, e.g. via split_filename() to get (name, version) then format it.
  2. Use parse_requirement() for PEP 508 requirement strings instead of parse_name_and_version.
  3. Pre-validate with a regex: re.match(r'[\w .-]+\s*\(\s*[^\s)]+\)\s*$', s).

Example fix

# before
parse_name_and_version('requests-2.25.1')
# after
name, ver, _ = split_filename('requests-2.25.1')
parse_name_and_version('%s (%s)' % (name, ver))
Defensive patterns

Strategy: validation

Validate before calling

from distlib.util import NAME_VERSION_RE

def is_name_version(s):
    return bool(NAME_VERSION_RE.match(s))

Type guard

def is_provides_dist_form(s: str) -> bool:
    import re
    return isinstance(s, str) and bool(re.match(r'[\w .-]+\s*\(\s*[^\s)]+\)\s*$', s))

Try / catch

from distlib.util import DistlibException
try:
    parse_name_and_version(s)
except DistlibException:
    name, ver, _ = split_filename(s)
    parse_name_and_version('%s (%s)' % (name, ver))

Prevention

When it happens

Trigger: parse_name_and_version('requests-2.25.1') (missing parens), parse_name_and_version('pkg ()') (empty version), parse_name_and_version('pkg 1.0'), or any string not in the canonical 'Name (Version)' metadata form.

Common situations: Feeding filename-derived strings or 'Name-Version' tuples into a function that expects metadata 'Provides-Dist' format; parsing project list output that uses dashes instead of parens.

Related errors


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

Appendix: 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 f399c37189)