pypa/pip · error · SyntaxError

invalid requirement

Error message

invalid requirement: %s

What it means

Raised as SyntaxError inside parse_requirement when, after consuming the distribution name, extras, URL and version constraints, there is leftover text that does not begin with ';' (the PEP 508 marker separator). It signals that the requirement contains unrecognized trailing structure that is neither a version constraint nor a valid marker clause. The parser expects either an empty remainder or a ';' introducing an environment marker.

Solutions

  1. Inspect the reported trailing fragment and remove or reformat it (e.g. add a missing comparison operator).
  2. Use packaging.requirements.Requirement(req_str) to validate the string and surface a clearer PEP 508 diagnostic first.
  3. Rebuild the requirement from a known-good template rather than string concatenation.

Example fix

// before
parse_requirement('requests 2.25.1 extras')
// after
parse_requirement('requests >= 2.25.1')
Defensive patterns

Strategy: validation

Validate before calling

from packaging.requirements import Requirement

def safe_parse(req):
    Requirement(req)  # raises clear PEP 508 error first
    return parse_requirement(req)

Type guard

def looks_like_valid_requirement(s: str) -> bool:
    try:
        Requirement(s)
        return True
    except Exception:
        return False

Try / catch

try:
    parse_requirement(req)
except SyntaxError as e:
    if 'invalid requirement' in str(e):
        log_and_skip(req, e)

Prevention

When it happens

Trigger: parse_requirement('pkg >=1.0 extra_stuff'), parse_requirement('pkg[extra] 1.0'), or any requirement string with a stray token where a comparison operator or ';' is expected. Also produced by requirement strings that accidentally include a second bare version after the first constraint.

Common situations: Hand-constructed requirement strings with typos; concatenating name + version without an operator; metadata from non-conformant packages; build tools joining fragments incorrectly.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/99ed8217502d0423. Report an issue: GitHub.

Appendix: source

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

                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:
                    m = VERSION_IDENTIFIER.match(s)
                    if not m:
                        raise SyntaxError('invalid constraint: %s' % s)
                    v = m.groups()[0]
                    s = s[m.end():].lstrip()
                    if s:
                        raise SyntaxError('invalid constraint: %s' % s)
                    versions = [('~=', v)]

    if remaining:
        if remaining[0] != ';':
            raise SyntaxError('invalid requirement: %s' % remaining)
        remaining = remaining[1:].lstrip()

        mark_expr, remaining = parse_marker(remaining)

    if remaining and remaining[0] != '#':
        raise SyntaxError('unexpected trailing data: %s' % remaining)

    if not versions:
        rs = distname
    else:
        rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions]))
    return Container(name=distname, extras=extras, constraints=versions, marker=mark_expr, url=uri, requirement=rs)


def get_resources_dests(resources_root, rules):
    """Find destinations for resources files"""

    def get_rel_path(root, path):

View on GitHub (pinned to f399c37189)