{"record":{"id":"d775e8e0a8a454eb","repo":"pypa/pip","slug":"exactly-one-of-vcs-info-archive-info-dir-info-mu","errorCode":null,"errorMessage":"Exactly one of vcs_info, archive_info, dir_info must be present","messagePattern":"Exactly one of vcs_info, archive_info, dir_info must be present","errorType":"validation","errorClass":"DirectUrlValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/direct_url.py","lineNumber":294,"sourceCode":"        object.__setattr__(self, \"vcs_info\", vcs_info)\n        object.__setattr__(self, \"dir_info\", dir_info)\n        object.__setattr__(self, \"subdirectory\", subdirectory)\n\n    @classmethod\n    def _from_dict(cls, d: Mapping[str, Any]) -> Self:\n        direct_url = cls(\n            url=_get_required(d, str, \"url\"),\n            archive_info=_get_object(d, ArchiveInfo, \"archive_info\"),\n            vcs_info=_get_object(d, VcsInfo, \"vcs_info\"),\n            dir_info=_get_object(d, DirInfo, \"dir_info\"),\n            subdirectory=_get(d, str, \"subdirectory\"),\n        )\n        if (\n            bool(direct_url.vcs_info)\n            + bool(direct_url.archive_info)\n            + bool(direct_url.dir_info)\n        ) != 1:\n            raise DirectUrlValidationError(\n                \"Exactly one of vcs_info, archive_info, dir_info must be present\"\n            )\n        if direct_url.dir_info is not None:\n            parsed_url = urllib.parse.urlsplit(direct_url.url)\n            if parsed_url.scheme != \"file\":\n                raise DirectUrlValidationError(\n                    \"URL scheme must be file:// when dir_info is present\",\n                    context=\"url\",\n                )\n            if not _file_url_has_absolute_path(parsed_url):\n                raise DirectUrlValidationError(\n                    \"File URL must be absolute when dir_info is present\",\n                    context=\"url\",\n                )\n        # XXX subdirectory must be relative, can we, should we validate that here?\n        return direct_url\n\n    @classmethod","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/direct_url.py#L276-L312","documentation":"Per PEP 610, a direct_url.json must describe exactly one source type: a VCS checkout (vcs_info), an archive download (archive_info), or a local directory (dir_info). DirectUrl._from_dict validates that exactly one of these three fields is present (not zero, not two or more). If the count is not exactly 1, DirectUrlValidationError is raised.","triggerScenarios":"Calling DirectUrl.from_dict(d) where none of the three info blocks are present (just a bare URL), or where two or more are present simultaneously (e.g. both vcs_info and archive_info).","commonSituations":"Hand-crafted or programmatically generated metadata missing the required info block. Metadata from buggy tools that include multiple info types. Schema drift after metadata format changes.","solutions":["Include exactly one info block based on the actual source type of the package","Remove extra info blocks if multiple are present","Add the appropriate info block if none is present (use empty dict {} for minimal archive_info)"],"exampleFix":"# before (no info block)\ndata = {\"url\": \"https://github.com/org/repo.git\"}\n# or (multiple info blocks)\ndata = {\"url\": \"...\", \"archive_info\": {}, \"vcs_info\": {\"vcs\": \"git\", \"commit_id\": \"abc\"}}\n\n# after (exactly one)\ndata = {\"url\": \"https://example.com/pkg.tar.gz\", \"archive_info\": {\"hashes\": {\"sha256\": \"...\"}}}","handlingStrategy":"validation","validationCode":"def validate_single_info_block(data: dict) -> None:\n    info_keys = [k for k in (\"vcs_info\", \"archive_info\", \"dir_info\") if k in data and data[k] is not None]\n    if len(info_keys) != 1:\n        raise ValueError(f\"Exactly one of vcs_info, archive_info, dir_info required, found: {info_keys}\")","typeGuard":"def has_exactly_one_info_block(data: dict) -> bool:\n    present = [k for k in (\"vcs_info\", \"archive_info\", \"dir_info\") if data.get(k) is not None]\n    return len(present) == 1","tryCatchPattern":"from packaging.direct_url import DirectUrl, DirectUrlValidationError\n\ntry:\n    du = DirectUrl.from_dict(data)\nexcept DirectUrlValidationError as e:\n    if \"Exactly one\" in str(e):\n        # determine correct info type from URL scheme and add it\n        pass","preventionTips":["Always set the appropriate info block based on source type","Use the DirectUrl constructor with named parameters instead of raw dicts","Schema-validate PEP 610 JSON before parsing"],"tags":["packaging","pep610","json-validation","direct-url","vendored"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}