{"id":"f1bd124edc930be4","repo":"pypa/pip","slug":"invalid-version-s","errorCode":null,"errorMessage":"invalid version: %s","messagePattern":"invalid version: (.+?)","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":210,"sourceCode":"                raise SyntaxError('Invalid URL: %s' % uri)\n            remaining = remaining[m.end():].lstrip()\n        else:\n\n            def get_versions(ver_remaining):\n                \"\"\"\n                Return a list of operator, version tuples if any are\n                specified, else None.\n                \"\"\"\n                m = COMPARE_OP.match(ver_remaining)\n                versions = None\n                if m:\n                    versions = []\n                    while True:\n                        op = m.groups()[0]\n                        ver_remaining = ver_remaining[m.end():]\n                        m = VERSION_IDENTIFIER.match(ver_remaining)\n                        if not m:\n                            raise SyntaxError('invalid version: %s' % ver_remaining)\n                        v = m.groups()[0]\n                        versions.append((op, v))\n                        ver_remaining = ver_remaining[m.end():]\n                        if not ver_remaining or ver_remaining[0] != ',':\n                            break\n                        ver_remaining = ver_remaining[1:].lstrip()\n                        # Some packages have a trailing comma which would break things\n                        # See issue #148\n                        if not ver_remaining:\n                            break\n                        m = COMPARE_OP.match(ver_remaining)\n                        if not m:\n                            raise SyntaxError('invalid constraint: %s' % ver_remaining)\n                    if not versions:\n                        versions = None\n                return versions, ver_remaining\n\n            if remaining[0] != '(':","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L192-L228","documentation":"Raised by the nested get_versions() helper inside distlib's parse_requirement() while parsing a PEP 508 requirement string. After a comparison operator (==, !=, <=, >=, <, >, ~=) is matched, the remaining text must satisfy the VERSION_IDENTIFIER regex; if it does not, the version segment is malformed and a Python SyntaxError is thrown. It signals that the requirement specifier contains an operator with no legal PEP 440 version following it.","triggerScenarios":"Calling parse_requirement('foo == ') or parse_requirement('bar >= abc123') or any requirement where text after a compare operator fails VERSION_IDENTIFIER (e.g. 'pkg < =1.0', 'pkg ~= 1.2.3.4.5.dev' in some edge forms, or a stray token after the operator).","commonSituations":"Hand-written requirements.txt entries with typos, programmatically-built requirement strings that concatenate an operator and an empty/non-version value, or copy-paste of version specifiers from docs that include non-breaking spaces or extra characters.","solutions":["Inspect the exact substring reported after 'invalid version:' and retype the version specifier using a valid PEP 440 version and operator, e.g. 'pkg == 1.2.3'.","If building the requirement dynamically, validate each version segment with packaging.version.Version or distlib's VERSION_IDENTIFIER regex before assembling the string.","Strip stray whitespace/unicode (non-breaking spaces, tabs) from the operator and version before parsing."],"exampleFix":"// before\nparse_requirement('requests ==')\nparse_requirement('flask >= 1..0')\n// after\nparse_requirement('requests == 2.31.0')\nparse_requirement('flask >= 1.0')","handlingStrategy":"validation","validationCode":"from distlib.util import VERSION_IDENTIFIER\ndef safe_parse(req):\n    # crude pre-check: every compare operator is followed by a version token\n    import re\n    parts = re.split(r'(==|!=|<=|>=|~=|<|>)', req)\n    for op, ver in zip(parts[1::2], parts[2::2]):\n        if not VERSION_IDENTIFIER.match(ver.strip()):\n            raise ValueError('bad version after %r: %r' % (op, ver))\n    from distlib.util import parse_requirement\n    return parse_requirement(req)","typeGuard":null,"tryCatchPattern":"from distlib.util import parse_requirement\ntry:\n    spec = parse_requirement(user_input)\nexcept SyntaxError as e:\n    if 'invalid version' in str(e):\n        # log and reject the offending requirement line\n        report_bad_requirement(user_input, e)\n    else:\n        raise","preventionTips":["Lint requirements files with 'pip check' / packaging.version before parsing.","Build requirement strings from structured data (name + Version) instead of string concatenation.","Never trust user-typed requirement strings without try/except around parse_requirement()."],"tags":["requirement","parsing","pep508","packaging","validation"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}