{"record":{"id":"37abd6f72a5834bc","repo":"pypa/pip","slug":"hash-values-must-be-strings","errorCode":null,"errorMessage":"Hash values must be strings","messagePattern":"Hash values must be strings","errorType":"validation","errorClass":"DirectUrlValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/direct_url.py","lineNumber":201,"sourceCode":"\n@dataclasses.dataclass(frozen=True, init=False)\nclass ArchiveInfo:\n    \"\"\"The archive information of a :class:`DirectUrl`.\"\"\"\n\n    hashes: Mapping[str, str] | None = None\n\n    def __init__(\n        self,\n        *,\n        hashes: Mapping[str, str] | None = None,\n    ) -> None:\n        object.__setattr__(self, \"hashes\", hashes)\n\n    @classmethod\n    def _from_dict(cls, d: Mapping[str, Any]) -> Self:\n        hashes = _get(d, Mapping, \"hashes\")  # type: ignore[type-abstract]\n        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 \"","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/direct_url.py#L183-L219","documentation":"In ArchiveInfo._from_dict(), the hashes field is expected to be a mapping of algorithm names to hash digest strings (e.g. {'sha256': 'abc...'}). If any value in the hashes mapping is not a string, DirectUrlValidationError is raised with context 'hashes'. This enforces PEP 610 / direct URL spec compliance for hash integrity data.","triggerScenarios":"Parsing an archive_info block from direct_url.json where hashes values are non-string: e.g. {'hashes': {'sha256': 12345}} or {'hashes': {'sha256': {'digest': 'abc'}}}.","commonSituations":"Incorrectly generated metadata by custom build tools, hand-crafted JSON, or schema drift from non-standard packaging tooling that stores hash digests as numbers or nested objects.","solutions":["Ensure all hash values in the hashes mapping are strings","Validate the hashes dict structure before parsing","Regenerate the direct_url.json using the standard to_dict() method"],"exampleFix":"# before\ndata = {\n    \"url\": \"https://example.com/pkg.tar.gz\",\n    \"archive_info\": {\"hashes\": {\"sha256\": 123456}}  # int\n}\n\n# after\ndata = {\n    \"url\": \"https://example.com/pkg.tar.gz\",\n    \"archive_info\": {\"hashes\": {\"sha256\": \"123456abcdef...\"}}  # str\n}","handlingStrategy":"validation","validationCode":"def validate_hashes_field(archive_info: dict) -> None:\n    hashes = archive_info.get(\"hashes\")\n    if hashes is not None:\n        if not isinstance(hashes, dict):\n            raise TypeError(\"hashes must be a dict\")\n        for algo, digest in hashes.items():\n            if not isinstance(digest, str):\n                raise TypeError(f\"hash value for {algo!r} must be a string, got {type(digest).__name__}\")","typeGuard":"def is_valid_hashes(hashes) -> bool:\n    return hashes is None or (\n        isinstance(hashes, dict)\n        and all(isinstance(k, str) and isinstance(v, str) for k, v in hashes.items())\n    )","tryCatchPattern":"from packaging.direct_url import DirectUrl, DirectUrlValidationError\n\ntry:\n    du = DirectUrl.from_dict(data)\nexcept DirectUrlValidationError as e:\n    if \"Hash values must be strings\" in str(e):\n        # fix hash types in data and retry\n        pass","preventionTips":["Always store hash digests as strings in JSON metadata","Validate hash mappings before serialization","Use standard packaging tools to generate metadata"],"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"}