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

  1. Always write the operator as two words with a space: 'not in', e.g. 'os_name not in "Windows"'.
  2. Do not strip internal whitespace from marker strings — only trim leading/trailing whitespace.
  3. 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

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


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)