pypa/pip · error · TypeError

Cannot restore value from

Error message

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

What it means

Node is the base class for AST nodes in packaging's dependency-specifier parser (Variable, Value, Op). The _restore_value method is called during unpickling (__setstate__). If the value being restored is not a str, it raises TypeError. This guards against reconstructing parser nodes from invalid data. __setstate__ supports three pickle formats: new compact string (26.2+), old __slots__ tuple (<=26.0), and old __dict__ (<=25.0).

Solutions

  1. Store serialized strings (via node.serialize()) and re-parse on load instead of pickling Node objects directly
  2. Clear pickle caches when upgrading packaging versions
  3. Catch TypeError on unpickle and fall back to re-parsing from the original requirement string

Example fix

# before
import pickle
node = pickle.loads(cached_data)  # may raise TypeError

# after
# Store the original requirement/marker string, not pickled Node objects
from packaging.requirements import Requirement
cached_str = "django>=4.0; python_version >= '3.8'"
# On load, always re-parse from string (version-independent):
req = Requirement(cached_str)
Defensive patterns

Strategy: try-catch

Validate before calling

def safe_unpickle_node(data, original_source=None):
    import pickle
    try:
        return pickle.loads(data)
    except TypeError:
        return None  # signal re-parse needed

Type guard

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

Try / catch

try:
    node = pickle.loads(cached_data)
except TypeError as e:
    if "Cannot restore" in str(e):
        from packaging._parser import parse_marker
        node = parse_marker(original_marker_string)

Prevention

When it happens

Trigger: Unpickling a Variable, Value, or Op node where the stored value is not a string: a corrupted pickle, a hand-crafted pickle, or an incompatible serialization from a fork or modified version of packaging.

Common situations: Loading cached requirement/marker parse results from disk across packaging version upgrades where the pickle format changed. Deserializing pickled marker data from an untrusted or third-party source.

Related errors


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

Appendix: source

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

    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 f399c37189)