{"record":{"id":"7cbfa0df22038088","repo":"pypa/pip","slug":"algorithm-hash-algorithm-r-used-in-hash-field-is","errorCode":null,"errorMessage":"Algorithm {hash_algorithm!r} used in hash field is not present in hashes field","messagePattern":"Algorithm (.+?) used in hash field is not present in hashes field","errorType":"validation","errorClass":"DirectUrlValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/direct_url.py","lineNumber":218,"sourceCode":"        if hashes is not None and not all(isinstance(h, str) for h in hashes.values()):\n            raise DirectUrlValidationError(\n                \"Hash values must be strings\", context=\"hashes\"\n            )\n        legacy_hash = _get(d, str, \"hash\")\n        if legacy_hash is not None:\n            if \"=\" not in legacy_hash:\n                raise DirectUrlValidationError(\n                    \"Invalid hash format (expected '<algorithm>=<hash>')\",\n                    context=\"hash\",\n                )\n            hash_algorithm, hash_value = legacy_hash.split(\"=\", 1)\n            if hashes is None:\n                # if `hashes` are not present, we can derive it from the legacy `hash`\n                hashes = {hash_algorithm: hash_value}\n            else:\n                # if `hashes` are present, the legacy `hash` must match one of them\n                if hash_algorithm not in hashes:\n                    raise DirectUrlValidationError(\n                        f\"Algorithm {hash_algorithm!r} used in hash field \"\n                        f\"is not present in hashes field\",\n                        context=\"hashes\",\n                    )\n                if hashes[hash_algorithm] != hash_value:\n                    raise DirectUrlValidationError(\n                        f\"Algorithm {hash_algorithm!r} used in hash field \"\n                        f\"has different value in hashes field\",\n                        context=\"hash\",\n                    )\n        return cls(hashes=hashes)\n\n\n@dataclasses.dataclass(frozen=True, init=False)\nclass DirInfo:\n    \"\"\"The local directory information of a :class:`DirectUrl`.\"\"\"\n\n    editable: bool | None = None","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/direct_url.py#L200-L236","documentation":"When both the legacy hash field and the newer hashes mapping are present in archive_info, they must be consistent. The algorithm extracted from the legacy hash (the part before =) must also be a key in the hashes mapping. If it is missing, DirectUrlValidationError is raised because the two hash representations disagree about which algorithms are available.","triggerScenarios":"An archive_info block with both hash and hashes where the algorithm in hash does not appear in hashes: e.g. {'hash': 'md5=abc', 'hashes': {'sha256': 'def'}}.","commonSituations":"Metadata generated by tools that write both fields with different algorithms. Migration artifacts where the legacy field was not updated alongside the new field.","solutions":["Ensure the algorithm in the legacy hash matches one of the keys in hashes","Remove the legacy hash field and rely solely on hashes","Regenerate the metadata from the correct source using to_dict()"],"exampleFix":"# before\ndata = {\n    \"url\": \"https://example.com/pkg.tar.gz\",\n    \"archive_info\": {\n        \"hash\": \"md5=abc123\",\n        \"hashes\": {\"sha256\": \"def456\"}  # md5 not in hashes\n    }\n}\n\n# after\ndata = {\n    \"url\": \"https://example.com/pkg.tar.gz\",\n    \"archive_info\": {\"hashes\": {\"sha256\": \"def456\"}}  # drop legacy hash\n}","handlingStrategy":"validation","validationCode":"def validate_hash_consistency(archive_info: dict) -> None:\n    legacy = archive_info.get(\"hash\")\n    hashes = archive_info.get(\"hashes\")\n    if legacy and hashes:\n        algo = legacy.split(\"=\", 1)[0]\n        if algo not in hashes:\n            raise ValueError(f\"Algorithm {algo!r} from hash not in hashes\")","typeGuard":"def are_hashes_consistent(archive_info: dict) -> bool:\n    legacy = archive_info.get(\"hash\")\n    hashes = archive_info.get(\"hashes\")\n    if not legacy or not hashes:\n        return True\n    return legacy.split(\"=\", 1)[0] in hashes","tryCatchPattern":"from packaging.direct_url import DirectUrl, DirectUrlValidationError\n\ntry:\n    du = DirectUrl.from_dict(data)\nexcept DirectUrlValidationError as e:\n    if \"not present in hashes\" in str(e):\n        data[\"archive_info\"].pop(\"hash\", None)\n        du = DirectUrl.from_dict(data)","preventionTips":["Do not mix legacy hash and hashes fields","Regenerate metadata from a single source of truth","Validate cross-field consistency before parsing"],"tags":["packaging","pep610","json-validation","hashes","vendored"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}