pypa/pip · error · InvalidSpecifier

Invalid specifier

Error message

Invalid specifier: {spec!r}

What it means

Raised as InvalidSpecifier (a ValueError subclass) by Specifier.__init__ at specifiers.py:368 when the spec string fails a fullmatch against the PEP 440 specifier regex (_specifier_regex). The regex requires a recognized operator (~=, ==, !=, <=, >=, <, >, ===) followed by a version-like token; any string that does not match is rejected before any parsing happens. This is the primary entry-point validation for every Specifier and SpecifierSet.

Solutions

  1. Inspect the exact spec string in the error message ({spec!r}) and correct the operator/version syntax to PEP 440 (~=, ==, !=, <=, >=, <, >, ===).
  2. If the value is user/config supplied, wrap construction in try/except InvalidSpecifier and report a clear validation error back to the user.
  3. Strip whitespace and verify the string starts with a known operator before constructing, or pre-validate with packaging.specifiers.Specifier only after a sanity regex.
  4. For empty/optional constraints, pass an empty string to SpecifierSet('') rather than constructing a Specifier with a blank version.

Example fix

# before
from packaging.specifiers import Specifier
spec = Specifier('^1.2.3')  # InvalidSpecifier: Invalid specifier: '^1.2.3'

# after
from packaging.specifiers import Specifier, InvalidSpecifier
try:
    spec = Specifier('>=1.2.3')
except InvalidSpecifier:
    spec = Specifier('>=1.0.0')
Defensive patterns

Strategy: validation

Validate before calling

from packaging.specifiers import Specifier, InvalidSpecifier

def safe_specifier(s: str):
    try:
        return Specifier(s)
    except InvalidSpecifier as e:
        raise ValueError(f'Bad PEP 440 specifier {s!r}: {e}') from e

Try / catch

from packaging.specifiers import InvalidSpecifier
try:
    spec = Specifier(user_input)
except InvalidSpecifier:
    spec = None  # or a safe default

Prevention

When it happens

Trigger: Specifier('lolwat'); Specifier('>>1.0'); Specifier('=1.0'); Specifier('=='); Specifier('>= 1.0.0!'); passing a specifier with an unknown operator, no version, or stray characters. Also fires indirectly when SpecifierSet('bad spec') parses each comma-delimited token.

Common situations: Reading requirements from a hand-edited requirements.txt/extra_requires with a typo; user-supplied version constraint strings from a config file or CLI; a dependency-pinning tool that concatenates operator and version incorrectly (e.g. '>=' + '' when version is blank); copy-paste of npm-style '^1.0' or cargo-style specifiers into a Python project.

Related errors


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

Appendix: source

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

        ">": "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

        # Version range cache (populated by _to_ranges)

View on GitHub (pinned to f399c37189)