{"id":"3e839ec1ef446130","repo":"pypa/pip","slug":"at-least-one-hash-must-be-provided","errorCode":null,"errorMessage":"At least one hash must be provided","messagePattern":"At least one hash must be provided","errorType":"validation","errorClass":"PylockValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/pylock.py","lineNumber":263,"sourceCode":"    # for portability\n    if \"/\" in path:\n        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):","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/pylock.py#L245-L281","documentation":"Raised as PylockValidationError by _validate_hashes in packaging.pylock when the 'hashes' table for a sdist/wheel/archive is empty or absent. pylock mandates at least one hash per artifact so integrity can be verified on install.","triggerScenarios":"A wheel entry with hashes = {} (empty inline table), or omitting the hashes key when it is required (_get_required_as treats absence as empty). Also an archive table where the resolver failed to populate hashes.","commonSituations":"Lockfile generated with --no-hashes or by a resolver that does not support hashing; hand-edited entry where hashes were stripped; partial download interrupted before hash computation.","solutions":["Populate hashes with at least one strong algorithm: hashes = { sha256 = \\\"<hex>\\\" }.","Regenerate the lockfile with a resolver that records hashes (pip, uv).","If you control generation, compute sha256 of the artifact and write it.","Catch the error and re-resolve the offending package."],"exampleFix":"# before\n[packages.sdist]\nurl = \\\"https://x/pkg-1.0.tar.gz\\\"\nhashes = {}\n# after\n[packages.sdist]\nurl = \\\"https://x/pkg-1.0.tar.gz\\\"\nhashes = { sha256 = \\\"abc123...\\\" }","handlingStrategy":"validation","validationCode":"def has_at_least_one_hash(hashes: dict) -> bool:\n    return bool(hashes) and len(hashes) >= 1","typeGuard":"def is_non_empty_hashes(h) -> bool:\n    return isinstance(h, dict) and len(h) >= 1","tryCatchPattern":"try:\n    PylockFile.from_dict(data)\nexcept PylockValidationError as e:\n    if 'At least one hash' in str(e):\n        re_resolve_and_add_hash(e.context)","preventionTips":["Always compute at least one strong hash (sha256) per artifact.","Use a resolver that records hashes (pip, uv).","Validate hashes is non-empty before loading.","Catch the error and re-resolve the offending package."],"tags":["pylock","pep771","hashes","integrity"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}