pypa/pip · error · DirectUrlValidationError

Invalid hash format (expected '<algorithm>=<hash>')

Error message

Invalid hash format (expected '<algorithm>=<hash>')

What it means

Raised by `ArchiveInfo._from_dict` when the legacy `hash` field is present but does not contain an `=` separator. PEP 610's legacy `hash` field must follow the form `<algorithm>=<hexdigest>` (e.g. `sha256=abc123...`). Without the separator, the algorithm and digest cannot be split.

Source

Thrown at src/pip/_vendor/packaging/direct_url.py:195

    def __init__(
        self,
        *,
        hashes: Mapping[str, str] | None = None,
    ) -> None:
        object.__setattr__(self, "hashes", hashes)

    @classmethod
    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
        hashes = _get(d, Mapping, "hashes")  # type: ignore[type-abstract]
        if hashes is not None and not all(isinstance(h, str) for h in hashes.values()):
            raise DirectUrlValidationError(
                "Hash values must be strings", context="hashes"
            )
        legacy_hash = _get(d, str, "hash")
        if legacy_hash is not None:
            if "=" not in legacy_hash:
                raise DirectUrlValidationError(
                    "Invalid hash format (expected '<algorithm>=<hash>')",
                    context="hash",
                )
            hash_algorithm, hash_value = legacy_hash.split("=", 1)
            if hashes is None:
                # if `hashes` are not present, we can derive it from the legacy `hash`
                hashes = {hash_algorithm: hash_value}
            else:
                # if `hashes` are present, the legacy `hash` must match one of them
                if hash_algorithm not in hashes:
                    raise DirectUrlValidationError(
                        f"Algorithm {hash_algorithm!r} used in hash field "
                        f"is not present in hashes field",
                        context="hashes",
                    )
                if hashes[hash_algorithm] != hash_value:
                    raise DirectUrlValidationError(
                        f"Algorithm {hash_algorithm!r} used in hash field "

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Reformat the value as `algorithm=digest`, e.g. `sha256=<hexdigest>`
  2. Prefer the modern `hashes` mapping (`{"sha256": "<hexdigest>"}`) over the legacy single `hash` field
  3. Reinstall the package to regenerate a correct record

Example fix

// before
{'archive_info': {'hash': 'abc123def456'}}
// after
{'archive_info': {'hash': 'sha256=abc123def456'}}
Defensive patterns

Strategy: validation

Validate before calling

def format_legacy_hash(algorithm: str, digest: str) -> str:
    if '=' in algorithm or '=' in digest:
        raise ValueError('algorithm/digest must not contain =')
    return f'{algorithm}={digest}'

Type guard

import re
def is_valid_legacy_hash(s: object) -> bool:
    return isinstance(s, str) and bool(re.match(r'^[A-Za-z0-9_-]+=[0-9a-fA-F]+$', s))

Try / catch

from packaging.direct_url import ArchiveInfo, DirectUrlValidationError
try:
    ArchiveInfo._from_dict(d)
except DirectUrlValidationError as e:
    if 'Invalid hash format' in str(e):
        h = d['archive_info'].pop('hash')
        d['archive_info']['hash'] = f'sha256={h}'  # repair if algorithm known
    raise

Prevention

When it happens

Trigger: A `direct_url.json` with `archive_info.hash` set to a bare hex digest like `"abc123def456"` or `"sha256:abc123"` (colon instead of equals).

Common situations: Older tooling that wrote the digest without the algorithm prefix; copy-paste from a `--hash` line that used a different separator; hand-crafted install records.

Related errors


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