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
- Inspect the exact spec string in the error message ({spec!r}) and correct the operator/version syntax to PEP 440 (~=, ==, !=, <=, >=, <, >, ===).
- If the value is user/config supplied, wrap construction in try/except InvalidSpecifier and report a clear validation error back to the user.
- 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.
- 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
- Always build specifier strings from a known operator constant + a validated version, never by concatenating raw user input.
- Pre-validate requirement strings with the packaging.specifiers regex or by constructing SpecifierSet early so errors surface at the boundary.
- Treat empty/optional version fields as empty SpecifierSet('') rather than feeding blank versions into Specifier.
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
- set relations do not support === specifiers
- Cannot combine SpecifierSets with True and False prerelease…
- Cannot restore Specifier from
- Cannot restore SpecifierSet from
- Compressed tag set would generate
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)