{"record":{"id":"d93995f322538bd7","repo":"pypa/pip","slug":"in-require-hashes-mode-all-requirements-must-ha","errorCode":null,"errorMessage":"In --require-hashes mode, all requirements must have their versions pinned with ==. These do not:","messagePattern":"In --require-hashes mode, all requirements must have their versions pinned with ==\\. These do not:","errorType":"exception","errorClass":"HashUnpinned","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/operations/prepare.py","lineNumber":484,"sourceCode":"        if not self.require_hashes:\n            return req.hashes(trust_internet=True)\n\n        # We could check these first 2 conditions inside unpack_url\n        # and save repetition of conditions, but then we would\n        # report less-useful error messages for unhashable\n        # requirements, complaining that there's no hash provided.\n        if req.link.is_vcs:\n            raise VcsHashUnsupported()\n        if req.link.is_existing_dir():\n            raise DirectoryUrlHashUnsupported()\n\n        # Unpinned packages are asking for trouble when a new version\n        # is uploaded.  This isn't a security check, but it saves users\n        # a surprising hash mismatch in the future.\n        # file:/// URLs aren't pinnable, so don't complain about them\n        # not being pinned.\n        if not req.is_direct and not req.is_pinned:\n            raise HashUnpinned()\n\n        # If known-good hashes are missing for this requirement,\n        # shim it with a facade object that will provoke hash\n        # computation and then raise a HashMissing exception\n        # showing the user what the hash should be.\n        return req.hashes(trust_internet=False) or MissingHashes()\n\n    def _fetch_metadata_only(\n        self,\n        req: InstallRequirement,\n    ) -> BaseDistribution | None:\n        if self.legacy_resolver:\n            logger.debug(\n                \"Metadata-only fetching is not used in the legacy resolver\",\n            )\n            return None\n        if self.require_hashes:\n            logger.debug(","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_internal/operations/prepare.py#L466-L502","documentation":"Raised by _get_linked_req_hashes (prepare.py:483) as HashUnpinned. In --require-hashes mode every (non-direct, non-file) requirement must be pinned with an exact '==' version so a hash can be meaningfully attached. If a requirement is not pinned, an unpinned version could later resolve to a different file and produce a confusing hash mismatch, so pip fails fast instead.","triggerScenarios":"A requirements file passed with --require-hashes containing a line without an == specifier (e.g. 'requests' or 'requests>=2.0'). Checked via req.is_pinned being False for indirect requirements.","commonSituations":"Adopting --require-hashes partway through a project; a requirements file that mixes pinned-and-hashed lines with loose dependency lines; transitive deps pulled in without pins when hash mode is on.","solutions":["Generate a fully pinned+hashed requirements file with 'pip-compile --generate-hashes' (pip-tools).","Pin each unpinned requirement to an exact version with == and add its sha256 hash.","Run 'pip install --require-hashes <pkg>==<ver>' to let pip print the expected hash, then add it to the file."],"exampleFix":"# before (requirements.txt)\nrequests\nflask>=1.0\n# after\npip-compile --generate-hashes requirements.in -o requirements.txt\n# yields: requests==2.31.0 --hash=sha256:... flask==2.3.2 --hash=sha256:...","handlingStrategy":"validation","validationCode":"import re\nfrom pip._vendor.packaging.requirements import Requirement\n\ndef all_pinned_for_hashes(req_file: str) -> tuple[bool, list[str]]:\n    bad = []\n    for line in open(req_file):\n        line = line.split('#')[0].strip()\n        if not line:\n            continue\n        try:\n            r = Requirement(line)\n        except Exception:\n            continue\n        if r.url:  # direct/VCS URLs are exempt as direct requirements\n            continue\n        pinned = any(op == '==' and not spec.version.endswith('.*')\n                     for op, spec in zip(r.specifier.operator, r.specifier.version) for _ in [0])\n        # Simpler robust check: look for == in the specifier\n        pinned = any(s.operator == '==' for s in r.specifier)\n        if not pinned:\n            bad.append(line)\n    return (not bad, bad)","typeGuard":"from pip._vendor.packaging.requirements import Requirement\n\ndef is_pinned_exact(line: str) -> bool:\n    try:\n        r = Requirement(line.split(' --')[0].strip())\n    except Exception:\n        return False\n    if r.url:\n        return True  # direct URLs are exempt\n    return any(s.operator == '==' for s in r.specifier)","tryCatchPattern":"# Use pip-compile to produce a fully pinned+hashed file rather than hand-editing.\nfrom subprocess import run\nrun([\"pip-compile\", \"--generate-hashes\", \"requirements.in\", \"-o\", \"requirements.txt\"], check=True)","preventionTips":["Generate hashed requirements files with pip-compile --generate-hashes.","Audit requirement files for unpinned (non-==) lines before enabling --require-hashes.","Treat --require-hashes as all-or-nothing across the install set."],"tags":["require-hashes","hashing","pinning","security","pip"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}