{"record":{"id":"49ab93a002014db4","repo":"pypa/pip","slug":"expected-url-after","errorCode":null,"errorMessage":"Expected URL after @","messagePattern":"Expected URL after @","errorType":"exception","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":148,"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":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/_parser.py#L130-L166","documentation":"Raised as a ParserSyntaxError (surfacing as InvalidRequirement) at line 148 when, after consuming the '@' (AT token) for a direct-URL requirement at line 143-144, the next token is not a URL. The grammar at line 135 requires `AT URL (...)`, so anything that is not a valid URL token after '@' triggers this.","triggerScenarios":"A requirement like 'pkg @' with nothing after, or 'pkg @ not-a-url', or 'pkg @ 2.0' (a version where a URL is expected). The tokenizer's URL rule did not match the token following '@'.","commonSituations":"Confusing a direct URL reference ('pkg @ https://...') with a version specifier ('pkg >= 2.0'); a typo dropping the URL scheme; copying a requirement and truncating it; using '@' as if it were a version operator.","solutions":["If you meant a version constraint, replace '@' with a proper operator like '>=' or '==' — '@' is only for direct URL references.","If you meant a direct reference, supply a full URL including scheme, e.g. 'pkg @ https://example.com/pkg-1.0.tar.gz'.","Validate the URL with urllib.parse first and confirm it starts with a known scheme (http/https/file/git+https)."],"exampleFix":"// before\nRequirement('mypkg @ 2.0')\n\n// after\nRequirement('mypkg @ https://example.com/mypkg-2.0.tar.gz')","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\ndef looks_like_url_req(s):\n    # requirement of form 'name @ url'\n    if '@' not in s.split('[')[0]:\n        return True  # not a direct-url form\n    url = s.split('@', 1)[1].split(';')[0].strip()\n    return bool(urlparse(url).scheme)","typeGuard":null,"tryCatchPattern":"from packaging.requirements import Requirement, InvalidRequirement\ntry:\n    req = Requirement(raw)\nexcept InvalidRequirement as e:\n    if 'URL after @' in str(e):\n        # user likely used '@' as a version operator\n        ...\n    raise","preventionTips":["Use '@' only for direct URL references with a full scheme; use '>=', '==' etc. for versions.","Validate the URL portion with urlparse before constructing the requirement string."],"tags":["packaging","pep508","requirement","url","parser","syntax-error"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}