{"record":{"id":"96377fd1d5d99732","repo":"pypa/pip","slug":"ill-formed-name-version-string-s","errorCode":null,"errorMessage":"Ill-formed name/version string: '%s'","messagePattern":"Ill-formed name/version string: '(.+?)'","errorType":"exception","errorClass":"DistlibException","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":897,"sourceCode":"\n\n# Allow spaces in name because of legacy dists like \"Twisted Core\"\nNAME_VERSION_RE = re.compile(r'(?P<name>[\\w .-]+)\\s*'\n                             r'\\(\\s*(?P<ver>[^\\s)]+)\\)$')\n\n\ndef parse_name_and_version(p):\n    \"\"\"\n    A utility method used to get name and version from a string.\n\n    From e.g. a Provides-Dist value.\n\n    :param p: A value in a form 'foo (1.0)'\n    :return: The name and version as a tuple.\n    \"\"\"\n    m = NAME_VERSION_RE.match(p)\n    if not m:\n        raise DistlibException('Ill-formed name/version string: \\'%s\\'' % p)\n    d = m.groupdict()\n    return d['name'].strip().lower(), d['ver']\n\n\ndef get_extras(requested, available):\n    result = set()\n    requested = set(requested or [])\n    available = set(available or [])\n    if '*' in requested:\n        requested.remove('*')\n        result |= available\n    for r in requested:\n        if r == '-':\n            result.add(r)\n        elif r.startswith('-'):\n            unwanted = r[1:]\n            if unwanted not in available:\n                logger.warning('undeclared extra: %s' % unwanted)","sourceCodeStart":879,"sourceCodeEnd":915,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/distlib/util.py#L879-L915","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Convert the input to 'Name (Version)' form first, e.g. via split_filename() to get (name, version) then format it.","Use parse_requirement() for PEP 508 requirement strings instead of parse_name_and_version.","Pre-validate with a regex: re.match(r'[\\w .-]+\\s*\\(\\s*[^\\s)]+\\)\\s*$', s)."],"exampleFix":"# before\nparse_name_and_version('requests-2.25.1')\n# after\nname, ver, _ = split_filename('requests-2.25.1')\nparse_name_and_version('%s (%s)' % (name, ver))","handlingStrategy":"validation","validationCode":"from distlib.util import NAME_VERSION_RE\n\ndef is_name_version(s):\n    return bool(NAME_VERSION_RE.match(s))","typeGuard":"def is_provides_dist_form(s: str) -> bool:\n    import re\n    return isinstance(s, str) and bool(re.match(r'[\\w .-]+\\s*\\(\\s*[^\\s)]+\\)\\s*$', s))","tryCatchPattern":"from distlib.util import DistlibException\ntry:\n    parse_name_and_version(s)\nexcept DistlibException:\n    name, ver, _ = split_filename(s)\n    parse_name_and_version('%s (%s)' % (name, ver))","preventionTips":["Use split_filename() for filename-derived strings.","Pre-format to 'Name (Version)' before parse_name_and_version."],"tags":["parsing","metadata","versioning","distlib","name-version"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}