pypa/pip · error · DirectUrlValidationError

Hash values must be strings

Error message

Hash values must be strings

What it means

Raised by `ArchiveInfo._from_dict` when the `hashes` mapping is present but at least one of its values is not a string. Per PEP 610, `hashes` is `Mapping[str, str]` (algorithm → hex digest); non-string values violate the schema and are rejected.

Source

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

        )


@dataclasses.dataclass(frozen=True, init=False)
class ArchiveInfo:
    hashes: Mapping[str, str] | None = None

    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 "

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Regenerate the `direct_url.json` via pip reinstall
  2. Ensure hash producers call `.hexdigest()` (str), not `.digest()` (bytes), and never numeric hashes
  3. Validate `all(isinstance(v, str) for v in hashes.values())` before constructing ArchiveInfo

Example fix

// before
import hashlib
hashes = {'sha256': hashlib.sha256(data).digest()}  # bytes
// after
hashes = {'sha256': hashlib.sha256(data).hexdigest()}  # str
Defensive patterns

Strategy: type-guard

Validate before calling

def validate_hashes(hashes: dict) -> None:
    if hashes is not None and not all(isinstance(v, str) for v in hashes.values()):
        raise TypeError('all hash values must be str (hex digest)')

Type guard

def is_str_value_hashes(h: object) -> bool:
    return h is None or (isinstance(h, dict) and all(isinstance(v, str) for v in h.values()))

Try / catch

from packaging.direct_url import ArchiveInfo, DirectUrlValidationError
try:
    ai = ArchiveInfo._from_dict(d)
except DirectUrlValidationError as e:
    if 'Hash values must be strings' in str(e):
        d['archive_info']['hashes'] = {k: str(v) for k, v in d['archive_info']['hashes'].items()}
    raise

Prevention

When it happens

Trigger: A `direct_url.json` where `archive_info.hashes` contains a value that is a number, list, or nested object, e.g. `{"sha256": 12345}` or `{"md5": ["abc"]}`.

Common situations: A custom installer or build backend that serializes hashes with the wrong JSON type; a JSON produced by code that wrote `hashlib.sha256(...).digest()` (bytes) instead of `.hexdigest()` (str); schema drift between tools.

Related errors


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