pypa/pip · error · InstallationError

Package URL {url!r} cannot contain fragments in combination

Error message

Package URL {url!r} cannot contain fragments in combination with subdirectory field (in {pylock_path_or_url!r})

What it means

InstallationError from package_vcs_requirement_url when a VCS package entry in pylock.toml has both a subdirectory field and a URL that already contains a '#' fragment. pip builds the VCS URL as vcs+url@commit and appends '#subdirectory=...'; a pre-existing fragment would make the URL ambiguous/invalid, so it refuses.

Source

Thrown at src/pip/_internal/utils/pylock.py:196

            if _is_url(pylock_path_or_url):
                raise InstallationError(
                    f"Absolute paths are not supported in pylock files obtained "
                    f"from a URL: {path!r} in {pylock_path_or_url!r}"
                )
            return path_to_url(path)
    else:
        assert url is not None  # guaranteed by packaging.pylock validation
        return url


def package_vcs_requirement_url(
    pylock_path_or_url: str, package_vcs: PackageVcs
) -> str:
    dist_url = _package_dist_url(pylock_path_or_url, package_vcs.path, package_vcs.url)
    url = f"{package_vcs.type}+{dist_url}@{package_vcs.commit_id}"
    if package_vcs.subdirectory:
        if "#" in url:
            raise InstallationError(
                f"Package URL {url!r} cannot contain fragments in combination "
                f"with subdirectory field (in {pylock_path_or_url!r})"
            )
        url += "#subdirectory=" + package_vcs.subdirectory
    return url


def package_archive_requirement_url(
    pylock_path_or_url: str, package_archive: PackageArchive
) -> str:
    url = _package_dist_url(
        pylock_path_or_url, package_archive.path, package_archive.url
    )
    if package_archive.subdirectory:
        if "#" in url:
            raise InstallationError(
                f"Package URL {url!r} cannot contain fragments in combination "
                f"with subdirectory field (in {pylock_path_or_url!r})"

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Remove the '#...' fragment from the VCS url field; express the subdirectory only via the subdirectory field.
  2. Drop the subdirectory field if you intend to keep the fragment (not recommended — prefer the field).
  3. Regenerate the lock with a current tool that emits only the first-class fields.

Example fix

# before
[[packages.vcs]]
type = "git"
url  = "https://github.com/o/r.git#subdirectory=pkg"
subdirectory = "pkg"

# after
[[packages.vcs]]
type = "git"
url  = "https://github.com/o/r.git"
subdirectory = "pkg"
Defensive patterns

Strategy: validation

Validate before calling

def pylock_vcs_entry_is_clean(url, subdirectory):
    return not (subdirectory and '#' in url)
# assert before writing/serving a pylock.toml with a VCS entry

Try / catch

try:
    pip_install('-r', lock_path)
except InstallationError as e:
    if 'cannot contain fragments in combination with subdirectory' in str(e):
        strip_fragment_from_vcs_urls(lock_path)
        pip_install('-r', lock_path)
    else:
        raise

Prevention

When it happens

Trigger: package_vcs.subdirectory is set and the constructed `url` (vcs+dist_url@commit_id) already contains '#'. E.g. a VCS url field ending in '#egg=x' or '#subdirectory=pkg' combined with a subdirectory field on the same entry.

Common situations: Migrating an older requirements-style VCS URL (with #egg= or #subdirectory= fragments) into a pylock.toml but also filling the first-class subdirectory field; tooling that emits both redundantly.

Related errors


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