pypa/pip · error · PylockValidationError

At least one hash must be provided

Error message

At least one hash must be provided

What it means

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.

Source

Thrown at src/pip/_vendor/packaging/pylock.py:263

    # for portability
    if "/" in path:
        return path.rsplit("/", 1)[-1]
    elif "\\" in path:
        return path.rsplit("\\", 1)[-1]
    else:
        return path


def _url_name(url: str | None) -> str | None:
    if not url:
        return None
    url_path = urlparse(url).path
    return url_path.rsplit("/", 1)[-1]


def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
    if not hashes:
        raise PylockValidationError("At least one hash must be provided")
    if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
        raise PylockValidationError("Hash values must be strings")
    return hashes


class PylockValidationError(Exception):
    """Raised when when input data is not spec-compliant."""

    context: str | None = None
    message: str

    def __init__(
        self,
        cause: str | Exception,
        *,
        context: str | None = None,
    ) -> None:
        if isinstance(cause, PylockValidationError):

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Populate hashes with at least one strong algorithm: hashes = { sha256 = \"<hex>\" }.
  2. Regenerate the lockfile with a resolver that records hashes (pip, uv).
  3. If you control generation, compute sha256 of the artifact and write it.
  4. Catch the error and re-resolve the offending package.

Example fix

# before
[packages.sdist]
url = \"https://x/pkg-1.0.tar.gz\"
hashes = {}
# after
[packages.sdist]
url = \"https://x/pkg-1.0.tar.gz\"
hashes = { sha256 = \"abc123...\" }
Defensive patterns

Strategy: validation

Validate before calling

def has_at_least_one_hash(hashes: dict) -> bool:
    return bool(hashes) and len(hashes) >= 1

Type guard

def is_non_empty_hashes(h) -> bool:
    return isinstance(h, dict) and len(h) >= 1

Try / catch

try:
    PylockFile.from_dict(data)
except PylockValidationError as e:
    if 'At least one hash' in str(e):
        re_resolve_and_add_hash(e.context)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/3e839ec1ef446130.json. Report an issue: GitHub.