pypa/pip · error · TypeError

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

Error message

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

What it means

Raised by `packaging._parser.Node._restore_value` during unpickling/deepcopy when the persisted value is not a `str`. AST nodes (`Variable`, `Value`, `Op`, etc.) carry a single string `value`; any non-string passed to restore is rejected as a type error. This guard enforces the new compact pickle format introduced in packaging 26.2.

Source

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

    def __init__(self, value: str) -> None:
        self.value = value

    def __str__(self) -> str:
        return self.value

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}({self.value!r})>"

    def serialize(self) -> str:
        raise NotImplementedError

    def __getstate__(self) -> str:
        # Return just the value string for compactness and stability.
        return self.value

    def _restore_value(self, value: object) -> None:
        if not isinstance(value, str):
            raise TypeError(
                f"Cannot restore {self.__class__.__name__} value from {value!r}"
            )
        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"])

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Ensure the object being unpickled was created by the same packaging version — re-parse from the source string instead
  2. If you must rebuild state, pass a str: `node.__setstate__('>=1.0')`
  3. Avoid pickling marker/specifier AST nodes; serialize the source specifier string and re-parse on load

Example fix

// before
node.__setstate__(123)
// after
node.__setstate__('>=1.0')
Defensive patterns

Strategy: type-guard

Validate before calling

def restore_node_value(node, value):
    if not isinstance(value, str):
        raise TypeError(f'value must be str, got {type(value).__name__}')
    node._restore_value(value)

Type guard

def is_str_state(value: object) -> bool:
    return isinstance(value, str)

Try / catch

try:
    node.__setstate__(payload)
except TypeError as e:
    if 'Cannot restore' in str(e):
        raise ValueError(f'invalid pickle payload for node: {payload!r}') from e
    raise

Prevention

When it happens

Trigger: Calling `__setstate__` directly with a non-string, unpickling a marker node from a pickle produced by tampering or by a different class hierarchy, or deepcopy when `__reduce__` was monkeypatched to return a non-string.

Common situations: Loading a pickle (e.g. cached parsed marker/specifier) from an older packaging version with a monkeypatch; serialization frameworks (dill, cloudpickle) that reconstruct state from non-str; test fixtures that hand-craft `__setstate__` payloads.

Related errors


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