pypa/pip · error · TypeError

Cannot restore SpecifierSet from

Error message

Cannot restore SpecifierSet from {state!r}

What it means

Raised as TypeError by SpecifierSet.__setstate__ (specifiers.py:931) during unpickling when none of the recognized state formats validate. It tries the 26.2+ tuple ((Specifier, ...), prereleases), the 26.0-26.1 slot dict (converting a legacy frozenset of specs to a tuple), and the <=25.x plain dict; failure of all raises this. Like the Specifier case, it signals a packaging-version mismatch in pickle state or corrupt hand-built state.

Solutions

  1. Store SpecifierSet as str(specifier_set) (round-trippable) rather than pickling the object; reconstruct with SpecifierSet(s) on load.
  2. Ensure producer and consumer run the same packaging version across the pickle boundary.
  3. Invalidate and rebuild any cached/pickled SpecifierSet objects after upgrading packaging.
  4. When loading legacy state, catch TypeError and reconstruct from the string form if available.

Example fix

# before - fragile pickle across packaging versions
import pickle
blob = pickle.dumps(SpecifierSet('>=1.0,!=1.0.1'))
ss = pickle.loads(blob)  # TypeError: Cannot restore SpecifierSet

# after - persist the canonical string
blob = str(SpecifierSet('>=1.0,!=1.0.1')).encode()
ss = SpecifierSet(blob.decode())
Defensive patterns

Strategy: fallback

Validate before calling

from packaging.specifiers import SpecifierSet
# Persist and restore the canonical string, not the pickle.
def load_set(s: str) -> 'SpecifierSet':
    return SpecifierSet(s)

Type guard

from packaging.specifiers import Specifier, SpecifierSet
def is_specifierset_state(state) -> bool:
    return (isinstance(state, tuple) and len(state) == 2
            and isinstance(state[0], tuple)
            and all(isinstance(s, Specifier) for s in state[0])
            and (state[1] is None or isinstance(state[1], bool)))

Try / catch

import pickle
try:
    ss = pickle.loads(blob)
except TypeError:
    ss = SpecifierSet(blob.decode())

Prevention

When it happens

Trigger: Unpickling a SpecifierSet produced by packaging 26.0 (which stored _specs as a frozenset) in a version that no longer accepts that shape after the frozenset-conversion branch was removed; cross-process pickle transfer between mismatched packaging versions; a spec stored in a frozenset but elements not all Specifier instances.

Common situations: Long-lived persistent requirement cache upgraded across a packaging 25->26 or 26.0->26.2 boundary; CI matrix where one job pickles on packaging 26.1 and another loads on 26.3; vendored pip packaging differing from an application's packaging dependency.

Related errors


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

Appendix: source

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

        if isinstance(state, dict):
            # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
            specs = state.get("_specs", ())
            prereleases = state.get("_prereleases")
            # Convert frozenset to tuple (26.0 stored as frozenset)
            if isinstance(specs, frozenset):
                specs = tuple(sorted(specs, key=str))
            if (
                isinstance(specs, tuple)
                and all(isinstance(s, Specifier) for s in specs)
                and _validate_pre(prereleases)
            ):
                self._specs = specs
                self._prereleases = prereleases
                self._canonicalized = len(self._specs) <= 1
                self._has_arbitrary = any("===" in str(s) for s in self._specs)
                return

        raise TypeError(f"Cannot restore SpecifierSet from {state!r}")

    def __repr__(self) -> str:
        """A representation of the specifier set that shows all internal state.

        Note that the ordering of the individual specifiers within the set may not
        match the input string.

        >>> SpecifierSet('>=1.0.0,!=2.0.0')
        <SpecifierSet('!=2.0.0,>=1.0.0')>
        >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
        <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
        >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
        <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
        """
        pre = (
            f", prereleases={self.prereleases!r}"
            if self._prereleases is not None
            else ""

View on GitHub (pinned to f399c37189)