pypa/pip · error · TypeError
Cannot restore Tag from
Error message
Cannot restore Tag from {state!r} What it means
Raised as TypeError by Tag.__setstate__ (tags.py:220) on the KeyError branch when unpickling a Tag whose pickle uses the legacy <=26.1 slot-dict format (None, {slot: value}) but the dict is missing one of the required keys _interpreter, _abi, or _platform. The except KeyError ... raise TypeError converts the missing-slot case into the standard 'Cannot restore Tag' message.
Solutions
- Do not pickle Tag objects across versions; store the canonical string f'{interpreter}-{abi}-{platform}' and rebuild with Tag(*parts).
- Regenerate the tag cache/pickle store from scratch after corruption.
- Ensure any custom __reduce__/__getstate__ on a Tag subclass emits all three slot keys.
- Catch TypeError on unpickle and fall back to Tag(str_form) parsing.
Example fix
# before - pickle the object
import pickle
blob = pickle.dumps(Tag('cp310','cp310','linux_x86_64'))
tag = pickle.loads(blob) # TypeError if slot dict is incomplete
# after - persist the canonical string form
from packaging.tags import Tag
s = str(Tag('cp310','cp310','linux_x86_64')) # 'cp310-cp310-linux_x86_64'
tag = Tag(*s.split('-')) Defensive patterns
Strategy: fallback
Validate before calling
# Persist tags as strings, not pickles.
from packaging.tags import Tag
def serialize_tag(t: Tag) -> str:
return str(t)
def deserialize_tag(s: str) -> Tag:
return Tag(*s.split('-')) Type guard
from packaging.tags import Tag
def has_all_slots(d: dict) -> bool:
return all(k in d for k in ('_interpreter', '_abi', '_platform')) Try / catch
import pickle
try:
tag = pickle.loads(blob)
except TypeError:
tag = Tag(*blob.decode().split('-')) Prevention
- Do not pickle Tag across packaging versions.
- Store the canonical 'interp-abi-platform' string and rebuild.
- Invalidate corrupt tag caches and regenerate.
When it happens
Trigger: Unpickling a Tag whose state was tampered with or produced by a custom __reduce__ that omitted a slot; partial/corrupt pickle data; a subclass or proxy that serialized only some slots.
Common situations: Corrupted pickle cache of resolved wheel tags; a monkeypatched/custom Tag subclass serialized before the packaging upgrade; cross-process queue corruption; manual pickle manipulation in tests.
Related errors
- Cannot restore Requirement from
- Cannot restore Specifier from
- Cannot restore SpecifierSet from
- Cannot restore Version from
- Cannot restore Marker from
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/be335a8c5ccdb6d9.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/packaging/tags.py:220
# 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, limit: int | None = None
) -> frozenset[Tag]:
"""
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of
:class:`Tag` instances.View on GitHub (pinned to f399c37189)