{"record":{"id":"51729fa729048d1a","repo":"pypa/pip","slug":"expected-whitespace-after-url","errorCode":null,"errorMessage":"Expected whitespace after URL","messagePattern":"Expected whitespace after URL","errorType":"exception","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":152,"sourceCode":"    \"\"\"\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\")\n\n        if tokenizer.check(\"END\", peek=True):\n            return (url, specifier, marker)\n","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/_parser.py#L134-L170","documentation":"Raised as a ParserSyntaxError (surfacing as InvalidRequirement) at line 152 when, after reading the URL in a direct-URL requirement, the next token is neither END nor whitespace (WS). The grammar requires whitespace separating the URL from any following marker clause (line 152); only end-of-input is exempt (line 149-150).","triggerScenarios":"A direct-URL requirement with a marker clause attached directly to the URL with no space, e.g. 'pkg @ https://x/y.tar.gz;python_version >= \"3.0\"'. The ';' (or other token) follows the URL immediately with no WS, so tokenizer.expect(\"WS\", ...) fails.","commonSituations":"Programmatically building 'name @ ' + url + ';' + marker without inserting a space; minified/compressed requirement strings; copy-paste losing the separating space.","solutions":["Insert a space between the URL and the semicolon: 'pkg @ https://x/y.tar.gz ; python_version >= \"3.0\"'.","When concatenating fragments, always join URL and marker with ' ' rather than '' or ';'.","Round-trip the constructed string through Requirement(...).str() to normalize spacing."],"exampleFix":"// before\nRequirement('pkg @ https://x/y.tar.gz;python_version >= \"3.0\"')\n\n// after\nRequirement('pkg @ https://x/y.tar.gz ; python_version >= \"3.0\"')","handlingStrategy":"validation","validationCode":"import re\ndef normalized_url_req(name, url, marker=None):\n    parts = [f'{name} @ {url}']\n    if marker:\n        parts.append(f'; {marker}')\n    return ' '.join(parts)","typeGuard":null,"tryCatchPattern":"from packaging.requirements import Requirement, InvalidRequirement\ntry:\n    req = Requirement(raw)\nexcept InvalidRequirement as e:\n    if 'whitespace after URL' in str(e):\n        raw = raw.replace(';', ' ;', 1)  # attempt space insertion\n        req = Requirement(raw)\n    raise","preventionTips":["Always separate the URL from the marker semicolon with whitespace.","Build URL requirements from structured parts rather than string concatenation."],"tags":["packaging","pep508","requirement","url","whitespace","parser"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}