{"record":{"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":275,"sourceCode":"    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    # The last path component is percent-encoded, so decode it to the file name\n    return unquote(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":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/pylock.py#L257-L293","documentation":"Raised by _validate_hashes in pylock.py:274-275. After confirming the hashes mapping is non-empty, every value is checked to be a str; any non-string value (int, bool, float, array) raises PylockValidationError. Hash digests must be string-encoded.","triggerScenarios":"In pylock TOML: hashes = { sha256 = 12345 } (int), hashes = { md5 = [1, 2, 3] } (array), hashes = { sha256 = true } (bool).","commonSituations":"Writing a numeric-looking hex digest as a bare number; a tool emitting binary or list-form digests; forgetting quotes around the hex string.","solutions":["Quote every hash digest so it is a TOML string."],"exampleFix":"# before\nhashes = { sha256 = 12345 }\n# after\nhashes = { sha256 = \"12345...\" }","handlingStrategy":"type-guard","validationCode":"def hashes_are_strings(hashes) -> bool:\n    return all(isinstance(v, str) for v in hashes.values())\n","typeGuard":"def hashes_all_str(hashes) -> bool:\n    return isinstance(hashes, dict) and all(isinstance(v, str) for v in hashes.values())\n","tryCatchPattern":"from packaging.pylock import Pylock, PylockValidationError\n\ntry:\n    Pylock.from_dict(d)\nexcept PylockValidationError as e:\n    ...\n","preventionTips":["Always quote hex digests in TOML.","Reject numeric/list digests at the hashing boundary."],"tags":["pylock","validation","hashes","type"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}