{"id":"d521b401b4ee00a6","repo":"pypa/pip","slug":"expected-whitespace-after-not","errorCode":null,"errorMessage":"Expected whitespace after 'not'","messagePattern":"Expected whitespace after 'not'","errorType":"validation","errorClass":"ParserSyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/_parser.py","lineNumber":385,"sourceCode":"    else:\n        return Variable(env_var)\n\n\ndef process_python_str(python_str: str) -> Value:\n    value = ast.literal_eval(python_str)\n    return Value(str(value))\n\n\ndef _parse_marker_op(tokenizer: Tokenizer) -> Op:\n    \"\"\"\n    marker_op = IN | NOT IN | OP\n    \"\"\"\n    if tokenizer.check(\"IN\"):\n        tokenizer.read()\n        return Op(\"in\")\n    elif tokenizer.check(\"NOT\"):\n        tokenizer.read()\n        tokenizer.expect(\"WS\", expected=\"whitespace after 'not'\")\n        tokenizer.expect(\"IN\", expected=\"'in' after 'not'\")\n        return Op(\"not in\")\n    elif tokenizer.check(\"OP\"):\n        return Op(tokenizer.read().text)\n    else:\n        return tokenizer.raise_syntax_error(\n            \"Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in\"\n        )\n","sourceCodeStart":367,"sourceCodeEnd":394,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/_parser.py#L367-L394","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","solutions":["Write the operator as two whitespace-separated words: Marker('sys_platform not in \"win32\"').","If you meant plain inequality, use '!=' instead: Marker('sys_platform != \"win32\"').","Ensure the marker string is not truncated — 'not' must be followed by whitespace and then 'in'."],"exampleFix":"# before\nMarker('sys_platform not\"win32\"')\n\n# after\nMarker('sys_platform not in \"win32\"')","handlingStrategy":"validation","validationCode":"import re\n\ndef normalize_not_in(s: str) -> str:\n    # Ensure 'not' is followed by whitespace then 'in'.\n    return re.sub(r'\\bnot\\s*in\\b', 'not in', s)","typeGuard":"from typing import TypeGuard\nfrom pip._vendor.packaging.markers import Marker, InvalidMarker\n\ndef is_valid_marker(s: str) -> TypeGuard[str]:\n    try:\n        Marker(s)\n    except InvalidMarker:\n        return False\n    return True","tryCatchPattern":"from pip._vendor.packaging.markers import Marker, InvalidMarker\n\ntry:\n    m = Marker(marker_str)\nexcept InvalidMarker as e:\n    if \"whitespace after 'not'\" in str(e):\n        m = Marker(normalize_not_in(marker_str))\n    else:\n        raise","preventionTips":["Always write the membership operator as two words: 'not in'.","Prefer '!=' when you simply mean inequality; reserve 'not in' for substring/set membership tests.","Avoid truncating marker strings mid-operator."],"tags":["packaging","parsing","markers","pep508","whitespace"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}