pypa/pip · error · SyntaxError
invalid constraint: %s
Error message
invalid constraint: %s
What it means
Raised by get_versions() inside parse_requirement() after a comma separating multiple version constraints. Once a comma is consumed, the next token must begin with a comparison operator (matched by COMPARE_OP); if it does not, the constraint list is considered invalid and a SyntaxError is raised. It guards the 'op ver, op ver' chaining syntax of PEP 508 version specifiers.
Source
Thrown at src/pip/_vendor/distlib/util.py:223
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] != '(':
versions, remaining = get_versions(remaining)
else:
i = remaining.find(')', 1)
if i < 0:
raise SyntaxError('unterminated parenthesis: %s' % remaining)
s = remaining[1:i]
remaining = remaining[i + 1:].lstrip()
# As a special diversion from PEP 508, allow a version number
# a.b.c in parentheses as a synonym for ~= a.b.c (because this
# is allowed in earlier PEPs)
if COMPARE_OP.match(s):
versions, _ = get_versions(s)
else:View on GitHub (pinned to d7d0d0a394)
Solutions
- Repeat the operator before each comma-separated version: 'pkg >= 1.0, < 2.0'.
- Check the substring after 'invalid constraint:' to find the offending token and fix or remove it.
- When constructing compound constraints programmatically, always emit '<op> <ver>' pairs joined by ', '.
Example fix
// before
parse_requirement('django >= 4.0, 5.0')
// after
parse_requirement('django >= 4.0, < 5.0') Defensive patterns
Strategy: validation
Validate before calling
import re
def validate_compound_constraint(spec):
# ensure each comma-separated piece starts with a compare operator
body = spec.split(';', 1)[0]
m = re.search(r'\((.*)\)', body)
if not m:
return True
for piece in m.group(1).split(','):
if not re.match(r'\s*(==|!=|<=|>=|~=|<|>)\s*\S', piece):
return False
return True Try / catch
from distlib.util import parse_requirement
try:
parse_requirement(line)
except SyntaxError as e:
if 'invalid constraint' in str(e):
reject_line(line, e)
else:
raise Prevention
- Always emit an operator before each comma-separated version.
- Validate compound constraints with a regex before parsing.
- Generate requirements from dependency data structures, not string templates.
When it happens
Trigger: parse_requirement('pkg >= 1.0 2.0'), parse_requirement('pkg >= 1.0, 2.0'), or any specifier list where a comma-separated element omits the leading operator or contains junk between constraints.
Common situations: Authors writing 'pkg >= 1.0, 2.0' (forgetting the second operator), or automated tools that join version filters with commas without re-emitting operators.
Related errors
- invalid version: %s
- invalid requirement: %s
- unexpected trailing data: %s
- Invalid requirement: {req_as_string!r}: {exc}
- Invalid requirement: {req_string!r}: {exc}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/8a6fa6e008db6744.json.
Report an issue: GitHub.