pypa/pip · error · DirectUrlValidationError
Invalid hash format (expected ' = ')
Error message
Invalid hash format (expected '<algorithm>=<hash>')
What it means
The legacy hash field in archive_info (from older PEP 610 implementations) must follow the format <algorithm>=<hash_digest>, e.g. sha256=abc123.... If the string lacks an = sign, DirectUrlValidationError is raised. This is separate from the newer hashes mapping field.
Solutions
- Format the legacy hash as <algorithm>=<digest> with an equals sign separator
- Prefer the newer hashes mapping field over the legacy hash string
- Remove the legacy hash field if hashes is already present and correct
Example fix
# before
data = {
"url": "https://example.com/pkg.tar.gz",
"archive_info": {"hash": "abc123def456"} # missing algorithm=
}
# after
data = {
"url": "https://example.com/pkg.tar.gz",
"archive_info": {"hashes": {"sha256": "abc123def456..."}}
} Defensive patterns
Strategy: validation
Validate before calling
def validate_legacy_hash(archive_info: dict) -> None:
legacy = archive_info.get("hash")
if legacy is not None:
if "=" not in legacy:
raise ValueError(f"Legacy hash must be 'algorithm=digest', got: {legacy!r}")
algo, digest = legacy.split("=", 1)
if not algo or not digest:
raise ValueError(f"Invalid legacy hash: {legacy!r}") Type guard
def is_valid_legacy_hash(h) -> bool:
if not isinstance(h, str):
return False
parts = h.split("=", 1)
return len(parts) == 2 and all(parts) Try / catch
from packaging.direct_url import DirectUrl, DirectUrlValidationError
try:
du = DirectUrl.from_dict(data)
except DirectUrlValidationError as e:
if "Invalid hash format" in str(e):
data["archive_info"].pop("hash", None)
du = DirectUrl.from_dict(data) Prevention
- Use the hashes mapping field instead of legacy hash
- Format legacy hashes as 'algorithm=digest' with equals sign
- Validate metadata before parsing
When it happens
Trigger: Parsing an archive_info block that has a legacy hash field without an = separator: e.g. {'hash': 'abc123'} or {'hash': 'sha256:'} (colon instead of equals).
Common situations: Migrating from older metadata formats. Custom build scripts that write hash fields with the wrong separator. Tooling that uses a colon instead of equals sign.
Related errors
- Algorithm used in hash field has different value in hashes…
- Algorithm used in hash field is not present in hashes field
- Hash values must be strings
- 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/55c70406a7bd8c92.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/direct_url.py:207
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 f399c37189)