{"id":"49ab93a002014db4","repo":"pypa/pip","slug":"expected-url-after","errorCode":null,"errorMessage":"Expected URL after @","messagePattern":"Expected URL after @","errorType":"validation","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":140,"sourceCode":"\ndef _parse_requirement_details(\n    tokenizer: Tokenizer,\n) -> tuple[str, str, MarkerList | None]:\n    \"\"\"\n    requirement_details = AT URL (WS requirement_marker?)?\n                        | specifier WS? (requirement_marker)?\n    \"\"\"\n\n    specifier = \"\"\n    url = \"\"\n    marker = None\n\n    if tokenizer.check(\"AT\"):\n        tokenizer.read()\n        tokenizer.consume(\"WS\")\n\n        url_start = tokenizer.position\n        url = tokenizer.expect(\"URL\", expected=\"URL after @\").text\n        if tokenizer.check(\"END\", peek=True):\n            return (url, specifier, marker)\n\n        tokenizer.expect(\"WS\", expected=\"whitespace after URL\")\n\n        # The input might end after whitespace.\n        if tokenizer.check(\"END\", peek=True):\n            return (url, specifier, marker)\n\n        marker = _parse_requirement_marker(\n            tokenizer,\n            span_start=url_start,\n            expected=\"semicolon (after URL and whitespace)\",\n        )\n    else:\n        specifier_start = tokenizer.position\n        specifier = _parse_specifier(tokenizer)\n        tokenizer.consume(\"WS\")","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/_parser.py#L122-L158","documentation":"Raised at src/pip/_vendor/packaging/_parser.py:140 inside _parse_requirement_details: once an AT token ('@') is matched and consumed (lines 135-136), the grammar requires a URL token next. tokenizer.expect(\"URL\", expected=\"URL after @\") fails because the URL rule ([^ \\t]+, _tokenizer.py:82) cannot match — the position is at end-of-string or at a whitespace-only run. This is the direct-URL requirement syntax (PEP 508 'name @ url') missing its URL.","triggerScenarios":"Requirement('mypkg @'), Requirement('mypkg @ '), or pip install 'pkg @' where nothing follows the '@'. Also when the '@' was intended as part of a version/extras but the parser interpreted it as the direct-URL introducer.","commonSituations":"Typing a direct-URL requirement and forgetting the URL, a templated/CI-generated requirement string where the URL variable expanded to empty, or confusing the legacy 'pkg==1.0' form with the 'pkg @ <url>' form.","solutions":["Supply a concrete URL after '@', e.g. Requirement('mypkg @ https://example.com/mypkg-1.0.tar.gz').","If you did not mean a direct-URL install, remove the '@' and use a version specifier instead (Requirement('mypkg==1.0')).","If building the string programmatically, assert the URL component is non-empty before concatenating."],"exampleFix":"# before\nRequirement('mypkg @')\n\n# after\nRequirement('mypkg @ https://example.com/mypkg-1.0.tar.gz')","handlingStrategy":"validation","validationCode":"def has_url_after_at(s: str) -> bool:\n    if '@' not in s:\n        return True  # not a direct-url requirement\n    head, _, tail = s.partition('@')\n    return bool(tail.strip())  # something non-space must follow '@'","typeGuard":"from typing import TypeGuard\nfrom pip._vendor.packaging.requirements import Requirement, InvalidRequirement\n\ndef is_valid_requirement(s: str) -> TypeGuard[str]:\n    try:\n        Requirement(s)\n    except InvalidRequirement:\n        return False\n    return True","tryCatchPattern":"from pip._vendor.packaging.requirements import Requirement, InvalidRequirement\n\ntry:\n    req = Requirement(req_str)\nexcept InvalidRequirement as e:\n    if 'URL after @' in str(e):\n        raise ValueError(f'missing URL after @ in {req_str!r}') from e\n    raise","preventionTips":["When generating direct-URL requirements, assert the URL variable is truthy before interpolating: f'{name} @ {url}'.","Prefer 'name @ <url>' only when you actually have a URL; otherwise use a version specifier."],"tags":["packaging","parsing","requirements","direct-url","pip"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}