pypa/pip · error · ParserSyntaxError
Expected whitespace after 'not'
Error message
Expected whitespace after 'not'
What it means
Raised as a ParserSyntaxError (surfacing as InvalidMarker) at line 410 inside _parse_marker_op: after consuming a 'not' (NOT token) at line 409, tokenizer.expect("WS", expected="whitespace after 'not'") fails because the token immediately following 'not' is not whitespace. The grammar (line 403-404) requires 'not in' as two whitespace-separated keywords.
Solutions
- Always write the operator as two words with a space: 'not in', e.g. 'os_name not in "Windows"'.
- Do not strip internal whitespace from marker strings — only trim leading/trailing whitespace.
- Validate the marker with packaging.markers.Marker() and read the precise span reported by the error.
Example fix
// before
Marker('os_name notin "Windows"')
// after
Marker('os_name not in "Windows"') Defensive patterns
Strategy: validation
Validate before calling
import re
def normalize_not_in(marker_str):
# collapse accidental 'notin' into 'not in'
return re.sub(r'\bnotin\b', 'not in', marker_str) Try / catch
from packaging.markers import Marker, InvalidMarker
try:
m = Marker(raw)
except InvalidMarker as e:
if "whitespace after 'not'" in str(e):
raw = normalize_not_in(raw)
m = Marker(raw)
raise Prevention
- Always write 'not in' as two words separated by a space.
- Avoid stripping internal whitespace from marker strings.
When it happens
Trigger: A marker using 'not' immediately followed by a non-whitespace token, e.g. 'os_name notin "Linux"' where 'notin' is not tokenized as NOT+WS, or 'python_version not)'. The NOT keyword is matched but no WS follows before the expected 'in'.
Common situations: Typing 'notin' as one word instead of 'not in'; stripping/normalizing whitespace out of marker strings aggressively so the required space disappears; minified marker strings.
Related errors
- Expected end of marker expression
- Expected end of dependency specifier
- Expected extra name after comma
- Expected URL after @
- Expected whitespace after URL
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/d521b401b4ee00a6.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/_parser.py:410
else:
return Variable(env_var)
def process_python_str(python_str: str) -> Value:
value = ast.literal_eval(python_str)
return Value(str(value))
def _parse_marker_op(tokenizer: Tokenizer) -> Op:
"""
marker_op = IN | NOT IN | OP
"""
if tokenizer.check("IN"):
tokenizer.read()
return Op("in")
elif tokenizer.check("NOT"):
tokenizer.read()
tokenizer.expect("WS", expected="whitespace after 'not'")
tokenizer.expect("IN", expected="'in' after 'not'")
return Op("not in")
elif tokenizer.check("OP"):
return Op(tokenizer.read().text)
else:
return tokenizer.raise_syntax_error(
"Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in"
)
View on GitHub (pinned to f399c37189)