pypa/pip · error · ParserSyntaxError
Expected whitespace after URL
Error message
Expected whitespace after URL
What it means
Raised at src/pip/_vendor/packaging/_parser.py:144. After a URL is read, if the input is not at END (line 141), the grammar requires whitespace before an optional marker clause: tokenizer.expect("WS", expected="whitespace after URL"). WS only matches ASCII space/tab ([ \t]+, _tokenizer.py:86). It fires when the character immediately after the URL token is neither end-of-input nor an ASCII space/tab.
Source
Thrown at src/pip/_vendor/packaging/_parser.py:144
"""
requirement_details = AT URL (WS requirement_marker?)?
| specifier WS? (requirement_marker)?
"""
specifier = ""
url = ""
marker = None
if tokenizer.check("AT"):
tokenizer.read()
tokenizer.consume("WS")
url_start = tokenizer.position
url = tokenizer.expect("URL", expected="URL after @").text
if tokenizer.check("END", peek=True):
return (url, specifier, marker)
tokenizer.expect("WS", expected="whitespace after URL")
# The input might end after whitespace.
if tokenizer.check("END", peek=True):
return (url, specifier, marker)
marker = _parse_requirement_marker(
tokenizer,
span_start=url_start,
expected="semicolon (after URL and whitespace)",
)
else:
specifier_start = tokenizer.position
specifier = _parse_specifier(tokenizer)
tokenizer.consume("WS")
if tokenizer.check("END", peek=True):
return (url, specifier, marker)
View on GitHub (pinned to d7d0d0a394)
Solutions
- Insert a single ASCII space between the URL and any trailing marker: 'pkg @ https://x.org/w ; python_version>="3.8"'.
- Sanitize the string by replacing non-ASCII whitespace (\u00a0, \u2007, \u202f, \ufeff) with a regular space before parsing.
- If no marker is intended, ensure nothing follows the URL.
Example fix
# before
Requirement('mypkg @ https://example.com/x\u00a0;python_version>="3.8"')
# after
Requirement('mypkg @ https://example.com/x ; python_version>="3.8"') Defensive patterns
Strategy: validation
Validate before calling
import re
def normalize_requirement_ws(s: str) -> str:
# Replace exotic Unicode spaces with a normal space so the WS rule matches.
return re.sub(r'[\u00a0\u2007\u202f\ufeff\u2003\u2002]', ' ', s) Type guard
from typing import TypeGuard
from pip._vendor.packaging.requirements import Requirement, InvalidRequirement
def is_valid_requirement(s: str) -> TypeGuard[str]:
try:
Requirement(s)
except InvalidRequirement:
return False
return True Try / catch
from pip._vendor.packaging.requirements import Requirement, InvalidRequirement
try:
req = Requirement(req_str)
except InvalidRequirement as e:
if 'whitespace after URL' in str(e):
req = Requirement(normalize_requirement_ws(req_str)) # retry once
else:
raise Prevention
- Always put one ASCII space between a URL and a trailing ';marker'.
- Sanitize requirement strings copied from documents/web pages to strip non-breaking spaces before parsing.
When it happens
Trigger: A direct-URL requirement where content is glued to the URL with a non-ASCII separator (e.g. a non-breaking space U+00A0 or a form-feed) that the WS rule does not recognize, or a programmatically-constructed string that places a token directly after the URL. Note the URL token ([^ \t]+) is greedy, so in normal prose this branch is rarely reached; when it is, it almost always indicates an unusual/invisible separator character.
Common situations: Requirements copy-pasted from rich-text/word processors that insert non-breaking spaces, CSV/templating output that joins fields without a literal space, or test harnesses feeding crafted bytes.
Related errors
- Expected URL after @
- Expected end of dependency specifier
- Expected extra name after comma
- Expected whitespace after 'not'
- {editable_req} is not a valid editable requirement. It shoul
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/51729fa729048d1a.json.
Report an issue: GitHub.