pypa/pip · error · TypeError

Cannot restore Tag from {state!r}

Error message

Cannot restore Tag from {state!r}

What it means

Raised as TypeError (tags.py:190) inside Tag.__setstate__ when unpickling the legacy 26.0-26.1 slot-dict format and the slot dict is missing one of _interpreter, _abi, or _platform (KeyError is caught and re-raised as this TypeError). It signals a corrupt or partial pickle in the old format.

Source

Thrown at src/pip/_vendor/packaging/tags.py:190

        # Cache member _hash is excluded and will be recomputed.
        return (self._interpreter, self._abi, self._platform)

    def __setstate__(self, state: object) -> None:
        if isinstance(state, tuple):
            if len(state) == 3 and all(isinstance(s, str) for s in state):
                # New format (26.2+): (interpreter, abi, platform)
                self._interpreter, self._abi, self._platform = state
                self._hash = hash((self._interpreter, self._abi, self._platform))
                return
            if len(state) == 2 and isinstance(state[1], dict):
                # Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
                _, slots = state
                try:
                    interpreter = slots["_interpreter"]
                    abi = slots["_abi"]
                    platform = slots["_platform"]
                except KeyError:
                    raise TypeError(f"Cannot restore Tag from {state!r}") from None
                if not all(
                    isinstance(value, str) for value in (interpreter, abi, platform)
                ):
                    raise TypeError(f"Cannot restore Tag from {state!r}")
                self._interpreter = interpreter.lower()
                self._abi = abi.lower()
                self._platform = platform.lower()
                self._hash = hash((self._interpreter, self._abi, self._platform))
                return
        raise TypeError(f"Cannot restore Tag from {state!r}")


def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]:
    """
    Parses the provided tag (e.g. `py3-none-any`) into a frozenset of
    :class:`Tag` instances.

    Returning a set is required due to the possibility that the tag is a

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Discard the corrupt pickle and rebuild the Tag via Tag(interpreter, abi, platform).
  2. Re-serialize Tags on the current packaging version using the new 3-tuple format.
  3. Prefer storing tags as their string form ('py3-none-any') and parsing via parse_tag for cross-version durability.

Example fix

# before: old-format pickle missing a slot
tag = pickle.load(open("t.pkl", "rb"))  # TypeError

# after
from pip._vendor.packaging.tags import Tag
tag = Tag("cp312", "cp312", "manylinux_2_17_x86_64")
Defensive patterns

Strategy: try-catch

Validate before calling

def is_intact_legacy_tag_state(state):
    if (isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict)):
        slots = state[1]
        return all(k in slots for k in ('_interpreter', '_abi', '_platform'))
    return False

Type guard

null

Try / catch

import pickle
from pip._vendor.packaging.tags import Tag
try:
    t = pickle.load(open(path, 'rb'))
except TypeError as e:
    if 'Cannot restore Tag' in str(e):
        t = Tag(interp_str, abi_str, platform_str)

Prevention

When it happens

Trigger: pickle.load on a Tag pickled by packaging 26.0-26.1 whose (None, {...}) state dict lacks one of the three slot keys. The except KeyError branch at tags.py:189 converts the lookup failure into this explicit restore error.

Common situations: Truncated pickle bytes, a pickle from a Tag subclass that overrode __slots__, or manual mutation of the state dict before unpickling.

Related errors


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