pypa/pip · error · SyntaxError

invalid version: %s

Error message

invalid version: %s

What it means

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.

Source

Thrown at src/pip/_vendor/distlib/util.py:210

                raise SyntaxError('Invalid URL: %s' % uri)
            remaining = remaining[m.end():].lstrip()
        else:

            def get_versions(ver_remaining):
                """
                Return a list of operator, version tuples if any are
                specified, else None.
                """
                m = COMPARE_OP.match(ver_remaining)
                versions = None
                if m:
                    versions = []
                    while True:
                        op = m.groups()[0]
                        ver_remaining = ver_remaining[m.end():]
                        m = VERSION_IDENTIFIER.match(ver_remaining)
                        if not m:
                            raise SyntaxError('invalid version: %s' % ver_remaining)
                        v = m.groups()[0]
                        versions.append((op, v))
                        ver_remaining = ver_remaining[m.end():]
                        if not ver_remaining or ver_remaining[0] != ',':
                            break
                        ver_remaining = ver_remaining[1:].lstrip()
                        # Some packages have a trailing comma which would break things
                        # See issue #148
                        if not ver_remaining:
                            break
                        m = COMPARE_OP.match(ver_remaining)
                        if not m:
                            raise SyntaxError('invalid constraint: %s' % ver_remaining)
                    if not versions:
                        versions = None
                return versions, ver_remaining

            if remaining[0] != '(':

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. 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'.
  2. If building the requirement dynamically, validate each version segment with packaging.version.Version or distlib's VERSION_IDENTIFIER regex before assembling the string.
  3. Strip stray whitespace/unicode (non-breaking spaces, tabs) from the operator and version before parsing.

Example fix

// before
parse_requirement('requests ==')
parse_requirement('flask >= 1..0')
// after
parse_requirement('requests == 2.31.0')
parse_requirement('flask >= 1.0')
Defensive patterns

Strategy: validation

Validate before calling

from distlib.util import VERSION_IDENTIFIER
def safe_parse(req):
    # crude pre-check: every compare operator is followed by a version token
    import re
    parts = re.split(r'(==|!=|<=|>=|~=|<|>)', req)
    for op, ver in zip(parts[1::2], parts[2::2]):
        if not VERSION_IDENTIFIER.match(ver.strip()):
            raise ValueError('bad version after %r: %r' % (op, ver))
    from distlib.util import parse_requirement
    return parse_requirement(req)

Try / catch

from distlib.util import parse_requirement
try:
    spec = parse_requirement(user_input)
except SyntaxError as e:
    if 'invalid version' in str(e):
        # log and reject the offending requirement line
        report_bad_requirement(user_input, e)
    else:
        raise

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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