{"record":{"id":"9c558818c12188a6","repo":"pypa/pip","slug":"multiple-dist-info-directories-found","errorCode":null,"errorMessage":"multiple .dist-info directories found: {}","messagePattern":"multiple \\.dist-info directories found: (.+?)","errorType":"exception","errorClass":"UnsupportedWheel","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/utils/wheel.py","lineNumber":51,"sourceCode":"    return info_dir, metadata\n\n\ndef wheel_dist_info_dir(source: ZipFile, name: str) -> str:\n    \"\"\"Returns the name of the contained .dist-info directory.\n\n    Raises AssertionError or UnsupportedWheel if not found, >1 found, or\n    it doesn't match the provided name.\n    \"\"\"\n    # Zip file path separators must be /\n    subdirs = {p.split(\"/\", 1)[0] for p in source.namelist()}\n\n    info_dirs = [s for s in subdirs if s.endswith(\".dist-info\")]\n\n    if not info_dirs:\n        raise UnsupportedWheel(\".dist-info directory not found\")\n\n    if len(info_dirs) > 1:\n        raise UnsupportedWheel(\n            \"multiple .dist-info directories found: {}\".format(\", \".join(info_dirs))\n        )\n\n    info_dir = info_dirs[0]\n\n    info_dir_name = canonicalize_name(info_dir)\n    canonical_name = canonicalize_name(name)\n    if not info_dir_name.startswith(canonical_name):\n        raise UnsupportedWheel(\n            f\".dist-info directory {info_dir!r} does not start with {canonical_name!r}\"\n        )\n\n    return info_dir\n\n\ndef read_wheel_metadata_file(source: ZipFile, path: str) -> bytes:\n    try:\n        return source.read(path)","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_internal/utils/wheel.py#L33-L69","documentation":"Raised by wheel_dist_info_dir() when the wheel zip contains more than one top-level '.dist-info' directory. A valid wheel must contain exactly one .dist-info directory; multiple indicate a malformed or merged archive.","triggerScenarios":"Calling wheel_dist_info_dir(source, name) where the set of top-level namelist entries contains two or more names ending in '.dist-info'. Reached during any wheel installation via parse_wheel().","commonSituations":"Two wheels manually zipped together. A build tool bug that emits metadata for multiple distributions into one wheel. A wheel repackaging tool that concatenated archives. A corrupted index serving a merged artifact.","solutions":["List the wheel contents: 'unzip -l <file>.whl' and identify the multiple .dist-info entries","Rebuild the wheel cleanly from source with 'python -m build --wheel'","Re-download the wheel from the official PyPI index","If you control the build, ensure only one distribution's metadata is emitted per wheel"],"exampleFix":"// before\npip install ./merged-1.0-py3-none-any.whl  # contains two .dist-info dirs\n// after\n# rebuild a single clean wheel\npython -m build --wheel --no-isolation\npip install dist/merged-1.0-py3-none-any.whl","handlingStrategy":"validation","validationCode":"import zipfile\n\ndef wheel_has_single_dist_info(path: str) -> bool:\n    with zipfile.ZipFile(path) as zf:\n        dirs = {n.split('/')[0] for n in zf.namelist() if '/' in n}\n        return sum(1 for d in dirs if d.endswith('.dist-info')) == 1","typeGuard":"import zipfile\n\ndef has_exactly_one_dist_info(zf: zipfile.ZipFile) -> bool:\n    dirs = {n.split('/')[0] for n in zf.namelist() if '/' in n}\n    return sum(1 for d in dirs if d.endswith('.dist-info')) == 1","tryCatchPattern":"from pip._internal.exceptions import UnsupportedWheel\n\ntry:\n    wheel_dist_info_dir(wheel_zip, name)\nexcept UnsupportedWheel as e:\n    if 'multiple .dist-info' in str(e):\n        raise PackagingError(f'wheel contains multiple distributions: {e}')\n    raise","preventionTips":["Build one wheel per distribution with a standard backend","Never manually merge wheel archives","Verify with 'unzip -l <file>.whl' that only one .dist-info exists","Re-download from the official index"],"tags":["wheel","metadata","dist-info","packaging","installation"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}