pypa/pip · error · InvalidSpecifier

Invalid specifier: {spec!r}

Error message

Invalid specifier: {spec!r}

What it means

Raised as InvalidSpecifier (specifiers.py:587), a ValueError subclass, by Specifier.__init__ when the input string does not fully match the operator+version regex (_regex.fullmatch). It is the primary PEP 440 specifier-syntax error, thrown before any operator/version splitting happens.

Source

Thrown at src/pip/_vendor/packaging/specifiers.py:587

        ">": "greater_than",
        "===": "arbitrary",
    }

    def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
        """Initialize a Specifier instance.

        :param spec:
            The string representation of a specifier which will be parsed and
            normalized before use.
        :param prereleases:
            This tells the specifier if it should accept prerelease versions if
            applicable or not. The default of ``None`` will autodetect it from the
            given specifiers.
        :raises InvalidSpecifier:
            If the given specifier is invalid (i.e. bad syntax).
        """
        if not self._regex.fullmatch(spec):
            raise InvalidSpecifier(f"Invalid specifier: {spec!r}")

        spec = spec.strip()
        if spec.startswith("==="):
            operator, version = spec[:3], spec[3:].strip()
        elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
            operator, version = spec[:2], spec[2:].strip()
        else:
            operator, version = spec[:1], spec[1:].strip()

        self._spec: tuple[str, str] = (operator, version)

        # Store whether or not this Specifier should accept prereleases
        self._prereleases = prereleases

        # Specifier version cache
        self._spec_version: tuple[str, Version] | None = None

        # Populated on first wildcard (==X.*) comparison

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Rewrite the specifier using a PEP 440 operator: ==, !=, <=, >=, <, >, ~=, or ===.
  2. Strip whitespace/unicode and ensure the version segment is a valid PEP 440 version (or arbitrary string after ===).
  3. If accepting user input, validate with Specifier() inside a try/except InvalidSpecifier and report a friendly message.

Example fix

# before
from pip._vendor.packaging.specifiers import Specifier
Specifier("^1.2.3")   # npm-style, not PEP 440
Specifier("==1.0 beta")

# after
Specifier(">=1.2.3")
Specifier("==1.0")
Defensive patterns

Strategy: try-catch

Validate before calling

from pip._vendor.packaging.specifiers import Specifier, InvalidSpecifier
def is_valid_specifier(s):
    try:
        Specifier(s)
        return True
    except InvalidSpecifier:
        return False

Type guard

null

Try / catch

from pip._vendor.packaging.specifiers import InvalidSpecifier
try:
    spec = Specifier(user_input)
except InvalidSpecifier as e:
    report_to_user(f'Not a valid PEP 440 specifier: {e}')

Prevention

When it happens

Trigger: Specifier('==>1.0'), Specifier('1.0'), Specifier('==1.0 beta'), Specifier('~>1.0'), Specifier(''), or any specifier missing a recognized operator (== != <= >= < > ~= ===). Also reached transitively when SpecifierSet('bad spec') parses each comma-separated piece.

Common situations: User-typed requirement strings from a config file or CLI. Tools forwarding npm-style ('^1.0') or gem-style ('~>1.0') specifiers into packaging. A trailing space, unicode lookalike operator, or missing operator.

Related errors


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