pypa/pip · error · DirectUrlValidationError
Hash values must be strings
Error message
Hash values must be strings
What it means
In ArchiveInfo._from_dict(), the hashes field is expected to be a mapping of algorithm names to hash digest strings (e.g. {'sha256': 'abc...'}). If any value in the hashes mapping is not a string, DirectUrlValidationError is raised with context 'hashes'. This enforces PEP 610 / direct URL spec compliance for hash integrity data.
Solutions
- Ensure all hash values in the hashes mapping are strings
- Validate the hashes dict structure before parsing
- Regenerate the direct_url.json using the standard to_dict() method
Example fix
# before
data = {
"url": "https://example.com/pkg.tar.gz",
"archive_info": {"hashes": {"sha256": 123456}} # int
}
# after
data = {
"url": "https://example.com/pkg.tar.gz",
"archive_info": {"hashes": {"sha256": "123456abcdef..."}} # str
} Defensive patterns
Strategy: validation
Validate before calling
def validate_hashes_field(archive_info: dict) -> None:
hashes = archive_info.get("hashes")
if hashes is not None:
if not isinstance(hashes, dict):
raise TypeError("hashes must be a dict")
for algo, digest in hashes.items():
if not isinstance(digest, str):
raise TypeError(f"hash value for {algo!r} must be a string, got {type(digest).__name__}") Type guard
def is_valid_hashes(hashes) -> bool:
return hashes is None or (
isinstance(hashes, dict)
and all(isinstance(k, str) and isinstance(v, str) for k, v in hashes.items())
) Try / catch
from packaging.direct_url import DirectUrl, DirectUrlValidationError
try:
du = DirectUrl.from_dict(data)
except DirectUrlValidationError as e:
if "Hash values must be strings" in str(e):
# fix hash types in data and retry
pass Prevention
- Always store hash digests as strings in JSON metadata
- Validate hash mappings before serialization
- Use standard packaging tools to generate metadata
When it happens
Trigger: Parsing an archive_info block from direct_url.json where hashes values are non-string: e.g. {'hashes': {'sha256': 12345}} or {'hashes': {'sha256': {'digest': 'abc'}}}.
Common situations: Incorrectly generated metadata by custom build tools, hand-crafted JSON, or schema drift from non-standard packaging tooling that stores hash digests as numbers or nested objects.
Related errors
- Algorithm used in hash field has different value in hashes…
- Algorithm used in hash field is not present in hashes field
- Invalid hash format (expected ' = ')
- Exactly one of vcs_info, archive_info, dir_info must be…
- File URL must be absolute when dir_info is present
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/37abd6f72a5834bc.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/direct_url.py:201
@dataclasses.dataclass(frozen=True, init=False)
class ArchiveInfo:
"""The archive information of a :class:`DirectUrl`."""
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 f399c37189)