{"id":"c09d1847cb436890","repo":"pypa/pip","slug":"path-or-url-must-be-provided","errorCode":null,"errorMessage":"path or url must be provided","messagePattern":"path or url must be provided","errorType":"validation","errorClass":"PylockValidationError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/pylock.py","lineNumber":238,"sourceCode":"    d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str\n) -> Sequence[_FromMappingProtocolT]:\n    \"\"\"Get a required list value from the dictionary and convert its items to a\n    dataclass.\"\"\"\n    if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:\n        raise _PylockRequiredKeyError(key)\n    return result\n\n\ndef _validate_normalized_name(name: str) -> NormalizedName:\n    \"\"\"Validate that a string is a NormalizedName.\"\"\"\n    if not is_normalized_name(name):\n        raise PylockValidationError(f\"Name {name!r} is not normalized\")\n    return NormalizedName(name)\n\n\ndef _validate_path_url(path: str | None, url: str | None) -> None:\n    if not path and not url:\n        raise PylockValidationError(\"path or url must be provided\")\n\n\ndef _path_name(path: str | None) -> str | None:\n    if not path:\n        return None\n    # If the path is relative it MAY use POSIX-style path separators explicitly\n    # for portability\n    if \"/\" in path:\n        return path.rsplit(\"/\", 1)[-1]\n    elif \"\\\\\" in path:\n        return path.rsplit(\"\\\\\", 1)[-1]\n    else:\n        return path\n\n\ndef _url_name(url: str | None) -> str | None:\n    if not url:\n        return None","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/pylock.py#L220-L256","documentation":"Raised as PylockValidationError by _validate_path_url in packaging.pylock when both 'path' and 'url' are missing/empty for an archive, sdist, wheel, vcs, or directory entry. At least one locator is required so the artifact can be resolved.","triggerScenarios":"A pylock package entry with an [[packages.sdist]] table that omits both path and url; an archive = {} inline table with neither key; a wheel entry where both fields were accidentally deleted.","commonSituations":"Hand-editing the lockfile and removing the locator; a buggy resolver that wrote an entry before computing the URL; TOML where the key was misspelled (e.g. 'Url' vs 'url').","solutions":["Add a 'url' (preferred for published artifacts) or 'path' (for local) to the offending table.","Regenerate the lockfile so the resolver fills in the locator.","Check for case/spelling mistakes in the key name.","Catch the error and report the package name to the user for fixing."],"exampleFix":"# before\n[packages.sdist]\nhashes = { sha256 = \\\"...\\\" }\n# after\n[packages.sdist]\nurl = \\\"https://files.pypi.org/.../pkg-1.0.tar.gz\\\"\nhashes = { sha256 = \\\"...\\\" }","handlingStrategy":"validation","validationCode":"def has_locator(entry: dict) -> bool:\n    return bool(entry.get('path')) or bool(entry.get('url'))","typeGuard":"def has_path_or_url(d) -> bool:\n    return bool(d.get('path')) or bool(d.get('url'))","tryCatchPattern":"from packaging.pylock import PylockValidationError\ntry:\n    PylockFile.from_dict(data)\nexcept PylockValidationError as e:\n    if 'path or url' in str(e):\n        add_url_to_entry(e.context)","preventionTips":["Always set url (or path for local) on sdist/wheel/archive entries.","Regenerate lockfiles with a resolver that fills locators.","Check spelling/case of 'path' and 'url' keys.","Catch and report the offending package."],"tags":["pylock","pep771","validation","locator"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}