pypa/pip · error · PylockValidationError

None of vcs, directory, archive must be set if sdist or…

Error message

None of vcs, directory, archive must be set if sdist or wheels are set

What it means

Raised by Package._from_dict in pylock.py:604-607. A package is EITHER a set of built distributions (sdist and/or wheels) OR a single direct-URL source (vcs/directory/archive), never both. If at least one distribution and at least one direct-url source are present together, PylockValidationError is raised.

Solutions

  1. Split into separate package entries (one for the built distribution, one for the source), or
  2. Drop the vcs/directory/archive fields so only sdist/wheels remain (or vice-versa).

Example fix

# before
[[packages]]
name = "x"
wheels = [{ name = "x-1.0-py3-none-any.whl", hashes = {...} }]
  [packages.directory]
  path = "./x"
# after
[[packages]]
name = "x"
wheels = [{ name = "x-1.0-py3-none-any.whl", hashes = {...} }]
Defensive patterns

Strategy: validation

Validate before calling

def package_source_consistent(pkg) -> bool:
    distributions = bool(pkg.get("sdist")) + len(pkg.get("wheels") or [])
    direct = bool(pkg.get("vcs")) + bool(pkg.get("directory")) + bool(pkg.get("archive"))
    return not (distributions > 0 and direct > 0)

Try / catch

from packaging.pylock import Pylock, PylockValidationError

try:
    Pylock.from_dict(d)
except PylockValidationError as e:
    ...

Prevention

When it happens

Trigger: A [[packages]] entry with both wheels = [...] and directory = {...}; a package with both sdist and vcs; any combination where distributions > 0 and direct_urls > 0.

Common situations: Mixing a built artifact with a source location in one package entry; a merge/union tool combining two package records into one.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/2caffe286d0817d1. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/pylock.py:605

            version=_get_as(d, str, Version, "version"),
            requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
            dependencies=_get_sequence(d, Mapping, "dependencies"),  # type: ignore[type-abstract]
            marker=_get_as(d, str, Marker, "marker"),
            vcs=_get_object(d, PackageVcs, "vcs"),
            directory=_get_object(d, PackageDirectory, "directory"),
            archive=_get_object(d, PackageArchive, "archive"),
            index=_get(d, str, "index"),
            sdist=_get_object(d, PackageSdist, "sdist"),
            wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
            attestation_identities=_get_sequence(d, Mapping, "attestation-identities"),  # type: ignore[type-abstract]
            tool=_get(d, Mapping, "tool"),  # type: ignore[type-abstract]
        )
        distributions = bool(package.sdist) + len(package.wheels or [])
        direct_urls = (
            bool(package.vcs) + bool(package.directory) + bool(package.archive)
        )
        if distributions > 0 and direct_urls > 0:
            raise PylockValidationError(
                "None of vcs, directory, archive must be set if sdist or wheels are set"
            )
        if distributions == 0 and direct_urls != 1:
            raise PylockValidationError(
                "Exactly one of vcs, directory, archive must be set "
                "if sdist and wheels are not set"
            )
        for i, wheel in enumerate(package.wheels or []):
            try:
                (name, version, _, _) = parse_wheel_filename(wheel.filename)
            except Exception as e:
                raise PylockValidationError(
                    f"Invalid wheel filename {wheel.filename!r}",
                    context=f"wheels[{i}]",
                ) from e
            if name != package.name:
                raise PylockValidationError(
                    f"Name in {wheel.filename!r} is not consistent with "

View on GitHub (pinned to f399c37189)