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
- Regenerate the `direct_url.json` via pip reinstall
- Ensure hash producers call `.hexdigest()` (str), not `.digest()` (bytes), and never numeric hashes
- 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
- Always produce hashes via .hexdigest(), never .digest() or numeric
- Type-check hashes.values() before serialization
- Reinstall packages to regenerate clean direct_url.json
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
- Invalid hash format (expected '<algorithm>=<hash>')
- Algorithm {hash_algorithm!r} used in hash field is not prese
- Algorithm {hash_algorithm!r} used in hash field has differen
- Unexpected type {type(value).__name__} (expected {expected_t
- Exactly one of vcs_info, archive_info, dir_info must be pres
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/37abd6f72a5834bc.json.
Report an issue: GitHub.