{"id":"2caffe286d0817d1","repo":"pypa/pip","slug":"none-of-vcs-directory-archive-must-be-set-if-sdi","errorCode":null,"errorMessage":"None of vcs, directory, archive must be set if sdist or wheels are set","messagePattern":"None of vcs, directory, archive must be set if sdist or wheels are set","errorType":"validation","errorClass":"PylockValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/pylock.py","lineNumber":589,"sourceCode":"            version=_get_as(d, str, Version, \"version\"),\n            requires_python=_get_as(d, str, SpecifierSet, \"requires-python\"),\n            dependencies=_get_sequence(d, Mapping, \"dependencies\"),  # type: ignore[type-abstract]\n            marker=_get_as(d, str, Marker, \"marker\"),\n            vcs=_get_object(d, PackageVcs, \"vcs\"),\n            directory=_get_object(d, PackageDirectory, \"directory\"),\n            archive=_get_object(d, PackageArchive, \"archive\"),\n            index=_get(d, str, \"index\"),\n            sdist=_get_object(d, PackageSdist, \"sdist\"),\n            wheels=_get_sequence_of_objects(d, PackageWheel, \"wheels\"),\n            attestation_identities=_get_sequence(d, Mapping, \"attestation-identities\"),  # type: ignore[type-abstract]\n            tool=_get(d, Mapping, \"tool\"),  # type: ignore[type-abstract]\n        )\n        distributions = bool(package.sdist) + len(package.wheels or [])\n        direct_urls = (\n            bool(package.vcs) + bool(package.directory) + bool(package.archive)\n        )\n        if distributions > 0 and direct_urls > 0:\n            raise PylockValidationError(\n                \"None of vcs, directory, archive must be set if sdist or wheels are set\"\n            )\n        if distributions == 0 and direct_urls != 1:\n            raise PylockValidationError(\n                \"Exactly one of vcs, directory, archive must be set \"\n                \"if sdist and wheels are not set\"\n            )\n        for i, wheel in enumerate(package.wheels or []):\n            try:\n                (name, version, _, _) = parse_wheel_filename(wheel.filename)\n            except Exception as e:\n                raise PylockValidationError(\n                    f\"Invalid wheel filename {wheel.filename!r}\",\n                    context=f\"wheels[{i}]\",\n                ) from e\n            if name != package.name:\n                raise PylockValidationError(\n                    f\"Name in {wheel.filename!r} is not consistent with \"","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/pylock.py#L571-L607","documentation":"Raised as PylockValidationError by Package._from_dict in packaging.pylock when a package entry specifies both a built distribution (sdist and/or wheels) and a direct source locator (vcs, directory, or archive). pylock treats these as mutually exclusive: a package is either pinned to artifacts or pinned to a source, not both.","triggerScenarios":"A [[packages]] table with both sdist = {...} (or wheels = [...]) and vcs = {...}; an entry with directory and wheels; an entry combining archive with sdist. The check counts distributions = bool(sdist)+len(wheels) and direct_urls = bool(vcs)+bool(directory)+bool(archive) and fires when both > 0.","commonSituations":"Merging two lockfile fragments (one pinned to artifacts, one to a Git URL) for the same package; hand-editing a lockfile to add a vcs override to an already-pinned package; tooling that emits both forms.","solutions":["Decide whether the package is artifact-pinned or source-pinned, then keep only that side.","Remove the vcs/directory/archive block when sdist/wheels are present, or vice versa.","Regenerate the lockfile with a single resolution strategy per package.","Split into two package entries if both forms are genuinely needed (note: pylock forbids this; reconsider)."],"exampleFix":"# before\n[packages]\nname = \\\"foo\\\"\nwheels = [{ name = \\\"foo-1.0.whl\\\", hashes = {...} }]\nvcs = { url = \\\"https://github.com/x/foo\\\", revision = \\\"v1.0\\\" }\n# after\n[packages]\nname = \\\"foo\\\"\nwheels = [{ name = \\\"foo-1.0.whl\\\", hashes = {...} }]","handlingStrategy":"validation","validationCode":"def is_exclusive_resolution(pkg: dict) -> bool:\n    has_dist = bool(pkg.get('sdist')) or bool(pkg.get('wheels'))\n    has_src = bool(pkg.get('vcs')) or bool(pkg.get('directory')) or bool(pkg.get('archive'))\n    return not (has_dist and has_src)","typeGuard":"def has_no_dist_source_mix(pkg: dict) -> bool:\n    dists = bool(pkg.get('sdist')) + len(pkg.get('wheels') or [])\n    srcs = bool(pkg.get('vcs')) + bool(pkg.get('directory')) + bool(pkg.get('archive'))\n    return not (dists > 0 and srcs > 0)","tryCatchPattern":"try:\n    PylockFile.from_dict(data)\nexcept PylockValidationError as e:\n    if 'None of vcs, directory, archive' in str(e):\n        drop_source_locator_from_pkg(e.context)","preventionTips":["Pick one resolution strategy per package: artifacts OR source.","Do not merge lockfile fragments from different strategies.","Validate exclusivity before loading.","Regenerate with a single resolver pass."],"tags":["pylock","pep771","validation","lockfile-structure"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}