pypa/pip · error · TypeError

Cannot restore {self.__class__.__name__} from {state!r}

Error message

Cannot restore {self.__class__.__name__} from {state!r}

What it means

Raised by `Node.__setstate__` when the persisted state is neither a plain string (new 26.2+ format), nor the old `(None, {slot: value})` tuple (packaging ≤26.0 with `__slots__`), nor a dict with a `value` key (packaging ≤25.0). It is a backwards-compatibility guard so that pickles produced by older packaging can still be loaded, while genuinely unrecognized formats are rejected.

Source

Thrown at src/pip/_vendor/packaging/_parser.py:56

            )
        self.value = value

    def __setstate__(self, state: object) -> None:
        if isinstance(state, str):
            # New format (26.2+): just the value string.
            self._restore_value(state)
            return
        if isinstance(state, tuple) and len(state) == 2:
            # Old format (packaging <= 26.0, __slots__): (None, {slot: value}).
            _, slot_dict = state
            if isinstance(slot_dict, dict) and "value" in slot_dict:
                self._restore_value(slot_dict["value"])
                return
        if isinstance(state, dict) and "value" in state:
            # Old format (packaging <= 25.0, no __slots__): plain __dict__.
            self._restore_value(state["value"])
            return
        raise TypeError(f"Cannot restore {self.__class__.__name__} from {state!r}")


class Variable(Node):
    __slots__ = ()

    def serialize(self) -> str:
        return str(self)


class Value(Node):
    __slots__ = ()

    def serialize(self) -> str:
        return f'"{self}"'


class Op(Node):
    __slots__ = ()

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Clear the stale cache (pip cache purge, remove `__pycache__`/wheel cache dirs) so objects are re-parsed from source
  2. Pin matching `packaging` versions across the environments exchanging pickles
  3. Serialize the specifier/marker as a string and re-parse via `Specifier()`/`Marker()` on load instead of pickling AST nodes

Example fix

// before
obj = pickle.load(open('cache.pkl','rb'))  # raises
// after
# do not pickle AST nodes; store the source string
import pickle
from packaging.specifiers import Specifier
src = '>=1.0'  # stored in cache instead
obj = Specifier(src)
Defensive patterns

Strategy: try-catch

Validate before calling

def is_recognized_node_state(state) -> bool:
    if isinstance(state, str):
        return True
    if isinstance(state, tuple) and len(state) == 2:
        _, sd = state
        return isinstance(sd, dict) and 'value' in sd
    if isinstance(state, dict) and 'value' in state:
        return True
    return False

Type guard

from typing import Any
def is_loadable_node_state(state: Any) -> bool:
    return (
        isinstance(state, str)
        or (isinstance(state, dict) and 'value' in state)
        or (isinstance(state, tuple) and len(state) == 2
            and isinstance(state[1], dict) and 'value' in state[1])
    )

Try / catch

try:
    obj = pickle.load(fh)
except TypeError as e:
    if 'Cannot restore' in str(e):
        log.warning('stale pickle cache, clearing')
        os.remove(cache_path)
        obj = Specifier(src_string)  # re-parse
    else:
        raise

Prevention

When it happens

Trigger: Unpickling a `Specifier`/`Marker` AST node whose pickle was produced by a much older or future packaging version; a pickle file shared across machines with mismatched packaging versions; a hand-constructed state tuple of the wrong shape.

Common situations: Cached build artifacts (pip's wheel cache, build backend caches) persisted on disk and reloaded after a packaging upgrade; copying pickled objects between Python environments; CI using a stale cache volume.

Related errors


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