pypa/pip · error · ParserSyntaxError

Expected whitespace after 'not'

Error message

Expected whitespace after 'not'

What it means

Raised at src/pip/_vendor/packaging/_parser.py:385 inside _parse_marker_op when handling the 'not in' operator. After matching the NOT token (lines 383-384), the grammar requires whitespace before 'in': tokenizer.expect("WS", expected="whitespace after 'not'"). NOT is matched via \bnot\b (_tokenizer.py:59), so the 'not' must be followed by ASCII whitespace; if the next character is anything else (a quote, '(', end-of-input), this fires. It propagates as packaging.markers.InvalidMarker.

Source

Thrown at src/pip/_vendor/packaging/_parser.py:385

    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 d7d0d0a394)

Solutions

  1. Write the operator as two whitespace-separated words: Marker('sys_platform not in "win32"').
  2. If you meant plain inequality, use '!=' instead: Marker('sys_platform != "win32"').
  3. Ensure the marker string is not truncated — 'not' must be followed by whitespace and then 'in'.

Example fix

# before
Marker('sys_platform not"win32"')

# after
Marker('sys_platform not in "win32"')
Defensive patterns

Strategy: validation

Validate before calling

import re

def normalize_not_in(s: str) -> str:
    # Ensure 'not' is followed by whitespace then 'in'.
    return re.sub(r'\bnot\s*in\b', 'not in', s)

Type guard

from typing import TypeGuard
from pip._vendor.packaging.markers import Marker, InvalidMarker

def is_valid_marker(s: str) -> TypeGuard[str]:
    try:
        Marker(s)
    except InvalidMarker:
        return False
    return True

Try / catch

from pip._vendor.packaging.markers import Marker, InvalidMarker

try:
    m = Marker(marker_str)
except InvalidMarker as e:
    if "whitespace after 'not'" in str(e):
        m = Marker(normalize_not_in(marker_str))
    else:
        raise

Prevention

When it happens

Trigger: Marker('sys_platform not"win32"') (quote glued to 'not'), Marker('sys_platform not') ('not' at end of string), Marker('sys_platform not("win32")'), or any marker using 'not in' where the two words are not separated by whitespace.

Common situations: Typing 'notin' as one word (though that usually fails differently as an unknown operator), omitting the space in 'not in', or a truncated marker string ending at 'not'.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/d521b401b4ee00a6.json. Report an issue: GitHub.