pypa/pip · error · DirectUrlValidationError

URL scheme must be file:// when dir_info is present

Error message

URL scheme must be file:// when dir_info is present

What it means

Raised by `DirectUrl._from_dict` when `dir_info` is present (indicating a local-directory install) but the `url` does not start with `file://`. A directory install must reference a local path via the `file://` scheme per PEP 610; any other scheme (https, git+ssh, etc.) is inconsistent with `dir_info`.

Source

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

    @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 and not direct_url.url.startswith("file://"):
            raise DirectUrlValidationError(
                "URL scheme must be file:// when dir_info is present",
                context="url",
            )
        # XXX subdirectory must be relative, can we, should we validate that here?
        return direct_url

    @classmethod
    def from_dict(cls, d: Mapping[str, Any], /) -> Self:
        """Create and validate a DirectUrl instance from a JSON dictionary."""
        return cls._from_dict(d)

    def to_dict(
        self,
        *,
        generate_legacy_hash: bool = False,
        strip_user_password: bool = True,
        safe_user_passwords: Collection[str] = ("git",),
    ) -> Mapping[str, Any]:

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Convert the URL to a `file://` path pointing at the local directory, e.g. `file:///abs/path/to/pkg`
  2. If the install is genuinely remote, remove `dir_info` and use `vcs_info` or `archive_info` instead
  3. Reinstall the package (`pip install -e <path>` for editable) to regenerate the record

Example fix

// before
{'url': '/home/me/pkg', 'dir_info': {'editable': true}}
// after
{'url': 'file:///home/me/pkg', 'dir_info': {'editable': true}}
Defensive patterns

Strategy: validation

Validate before calling

def coerce_dir_url(url: str) -> str:
    from urllib.parse import urlsplit
    p = urlsplit(url)
    if p.scheme != 'file':
        raise ValueError(f'dir_info requires file:// URL, got scheme {p.scheme!r}')
    return url

Type guard

def is_file_url(url: object) -> bool:
    return isinstance(url, str) and url.startswith('file://')

Try / catch

from packaging.direct_url import DirectUrl, DirectUrlValidationError
try:
    du = DirectUrl.from_dict(d)
except DirectUrlValidationError as e:
    if 'file://' in str(e):
        d['url'] = 'file://' + os.path.abspath(d['url'].replace('file://',''))
    raise

Prevention

When it happens

Trigger: A record like `{"url": "https://example.com/pkg", "dir_info": {"editable": true}}` — dir_info implies local but the URL is remote. Also hit when a `pip install -e ./pkg` recorded an incorrect URL scheme.

Common situations: Hand-edited install metadata where someone changed the URL but kept `dir_info`; a buggy installer that wrote the project URL instead of the local file URL; symlinked or relocated editable installs whose path changed.

Related errors


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