pypa/pip · error · DirectUrlValidationError
Exactly one of vcs_info, archive_info, dir_info must be…
Error message
Exactly one of vcs_info, archive_info, dir_info must be present
What it means
Per PEP 610, a direct_url.json must describe exactly one source type: a VCS checkout (vcs_info), an archive download (archive_info), or a local directory (dir_info). DirectUrl._from_dict validates that exactly one of these three fields is present (not zero, not two or more). If the count is not exactly 1, DirectUrlValidationError is raised.
Solutions
- Include exactly one info block based on the actual source type of the package
- Remove extra info blocks if multiple are present
- Add the appropriate info block if none is present (use empty dict {} for minimal archive_info)
Example fix
# before (no info block)
data = {"url": "https://github.com/org/repo.git"}
# or (multiple info blocks)
data = {"url": "...", "archive_info": {}, "vcs_info": {"vcs": "git", "commit_id": "abc"}}
# after (exactly one)
data = {"url": "https://example.com/pkg.tar.gz", "archive_info": {"hashes": {"sha256": "..."}}} Defensive patterns
Strategy: validation
Validate before calling
def validate_single_info_block(data: dict) -> None:
info_keys = [k for k in ("vcs_info", "archive_info", "dir_info") if k in data and data[k] is not None]
if len(info_keys) != 1:
raise ValueError(f"Exactly one of vcs_info, archive_info, dir_info required, found: {info_keys}") Type guard
def has_exactly_one_info_block(data: dict) -> bool:
present = [k for k in ("vcs_info", "archive_info", "dir_info") if data.get(k) is not None]
return len(present) == 1 Try / catch
from packaging.direct_url import DirectUrl, DirectUrlValidationError
try:
du = DirectUrl.from_dict(data)
except DirectUrlValidationError as e:
if "Exactly one" in str(e):
# determine correct info type from URL scheme and add it
pass Prevention
- Always set the appropriate info block based on source type
- Use the DirectUrl constructor with named parameters instead of raw dicts
- Schema-validate PEP 610 JSON before parsing
When it happens
Trigger: Calling DirectUrl.from_dict(d) where none of the three info blocks are present (just a bare URL), or where two or more are present simultaneously (e.g. both vcs_info and archive_info).
Common situations: Hand-crafted or programmatically generated metadata missing the required info block. Metadata from buggy tools that include multiple info types. Schema drift after metadata format changes.
Related errors
- File URL must be absolute when dir_info is present
- Unexpected type (expected )
- URL scheme must be file:// when dir_info is present
- Algorithm used in hash field has different value in hashes…
- Algorithm used in hash field is not present in hashes field
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/d775e8e0a8a454eb.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/direct_url.py:294
object.__setattr__(self, "vcs_info", vcs_info)
object.__setattr__(self, "dir_info", dir_info)
object.__setattr__(self, "subdirectory", subdirectory)
@classmethod
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
direct_url = cls(
url=_get_required(d, str, "url"),
archive_info=_get_object(d, ArchiveInfo, "archive_info"),
vcs_info=_get_object(d, VcsInfo, "vcs_info"),
dir_info=_get_object(d, DirInfo, "dir_info"),
subdirectory=_get(d, str, "subdirectory"),
)
if (
bool(direct_url.vcs_info)
+ bool(direct_url.archive_info)
+ bool(direct_url.dir_info)
) != 1:
raise DirectUrlValidationError(
"Exactly one of vcs_info, archive_info, dir_info must be present"
)
if direct_url.dir_info is not None:
parsed_url = urllib.parse.urlsplit(direct_url.url)
if parsed_url.scheme != "file":
raise DirectUrlValidationError(
"URL scheme must be file:// when dir_info is present",
context="url",
)
if not _file_url_has_absolute_path(parsed_url):
raise DirectUrlValidationError(
"File URL must be absolute when dir_info is present",
context="url",
)
# XXX subdirectory must be relative, can we, should we validate that here?
return direct_url
@classmethodView on GitHub (pinned to f399c37189)