{"id":"88f9088495d98640","repo":"pypa/pip","slug":"hash-values-must-be-strings-88f908","errorCode":null,"errorMessage":"Hash values must be strings","messagePattern":"Hash values must be strings","errorType":"validation","errorClass":"PylockValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/pylock.py","lineNumber":265,"sourceCode":"        return path.rsplit(\"/\", 1)[-1]\n    elif \"\\\\\" in path:\n        return path.rsplit(\"\\\\\", 1)[-1]\n    else:\n        return path\n\n\ndef _url_name(url: str | None) -> str | None:\n    if not url:\n        return None\n    url_path = urlparse(url).path\n    return url_path.rsplit(\"/\", 1)[-1]\n\n\ndef _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:\n    if not hashes:\n        raise PylockValidationError(\"At least one hash must be provided\")\n    if not all(isinstance(hash_val, str) for hash_val in hashes.values()):\n        raise PylockValidationError(\"Hash values must be strings\")\n    return hashes\n\n\nclass PylockValidationError(Exception):\n    \"\"\"Raised when when input data is not spec-compliant.\"\"\"\n\n    context: str | None = None\n    message: str\n\n    def __init__(\n        self,\n        cause: str | Exception,\n        *,\n        context: str | None = None,\n    ) -> None:\n        if isinstance(cause, PylockValidationError):\n            if cause.context:\n                self.context = (","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/pylock.py#L247-L283","documentation":"Raised as PylockValidationError by _validate_hashes in packaging.pylock when the hashes table is non-empty but at least one value is not a string. pylock requires every hash value to be a hex digest string; an int, list, or inline table value is rejected.","triggerScenarios":"hashes = { sha256 = 12345 } (int), hashes = { sha256 = [\\\"a\\\", \\\"b\\\"] } (list), or hashes = { sha256 = { digest = \\\"...\\\" } } (table). The error fires after the 'at least one hash' check has passed.","commonSituations":"Tooling that emits hash byte-length instead of the digest; a TOML converter that quoted only keys; copy-pasting a hash object instead of its hex attribute.","solutions":["Ensure each hash value is a hex string, e.g. hashes = { sha256 = \\\"abcdef...\\\" }.","Regenerate the lockfile with a resolver that writes hex digests.","If generating programmatically, call hashlib.sha256(data).hexdigest().","Validate the hashes mapping types before calling from_dict."],"exampleFix":"# before\nhashes = { sha256 = 123 }\n# after\nhashes = { sha256 = \\\"e3b0c44298fc1c149afbf4c8996fb924...\\\" }","handlingStrategy":"type-guard","validationCode":"def hashes_are_strings(h: dict) -> bool:\n    return all(isinstance(v, str) for v in h.values())","typeGuard":"from typing import Mapping\n\ndef is_string_valued_hashes(h: Mapping) -> bool:\n    return all(isinstance(v, str) for v in h.values())","tryCatchPattern":"try:\n    PylockFile.from_dict(data)\nexcept PylockValidationError as e:\n    if 'Hash values must be strings' in str(e):\n        coerce_hash_values_to_hex_strings(e.context)","preventionTips":["Write hex digests via hashlib.<alg>.hexdigest().","Validate hash value types before loading.","Use a resolver that emits hex strings.","Do not store hash lengths or nested objects."],"tags":["pylock","pep771","hashes","type-checking"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}