-
Notifications
You must be signed in to change notification settings - Fork 134
Open
Description
Description
Hey,
I saw this behaviour in msgspec 0.20.0 and Python 3.14.1.
NamedTuples have hash changes after being serialised, and deserialised with msgpack- the hash becomes 0.
This means for msgspec structs where NamedTuples are used as dictionary keys, there is unexpected KeyErrors when accessing data
Here is a minimal examples of the issue- the following code throws no AssertionErrors in python 3.11-3.13
from typing import NamedTuple
import msgspec
class _TestKey(NamedTuple):
"""Test NamedTuple for demonstrating the msgspec Python 3.14 bug."""
value: int
class _TestStruct(msgspec.Struct, array_like=True):
"""Test Struct that contains a dict with NamedTuple keys."""
data: dict[_TestKey, str]
def test_msgspec_namedtuple_hash_bug():
original = _TestKey(value=42)
original_hash = hash(original)
# Round-trip through msgspec - hash is broken in Python 3.14
encoded = msgspec.msgpack.encode(original)
decoded = msgspec.msgpack.decode(encoded, type=_TestKey)
# These should be equal
assert original == decoded, "NamedTuples should be equal after round-trip"
# This is the bug: hash should be equal but isn't in Python 3.14
decoded_hash = hash(decoded)
assert decoded_hash == original_hash, (
f"Hash mismatch after msgspec round-trip: original={original_hash}, decoded={decoded_hash}. "
f"In Python 3.14, msgspec-created NamedTuples have hash=0."
)
# Dictionary lookup fails due to hash mismatch
test_dict = {original: "value"}
assert decoded in test_dict, "Decoded key should be found in dictionary (requires matching hash)"
def test_msgspec_struct_with_namedtuple_dict_key_bug():
"""
Demonstrates the msgspec Python 3.14 bug when a NamedTuple is used as a dictionary
key inside a msgspec.Struct - this mirrors the real-world usage in CalculationMetadata
where SourceMarketKey is used as a key in base_inputs.
After msgpack deserialization:
- The Struct is correctly deserialized
- The dict appears to contain the expected key
- But dict equality fails because the deserialized keys have hash=0
- Looking up the key also fails due to hash mismatch
"""
key = _TestKey(value=42)
original = _TestStruct(data={key: "test_value"})
# Round-trip through msgspec
encoded = msgspec.msgpack.encode(original)
decoded = msgspec.msgpack.decode(encoded, type=_TestStruct)
# Get the decoded key for hash comparison
decoded_key = next(iter(decoded.data.keys()))
original_key_hash = hash(key)
decoded_key_hash = hash(decoded_key)
# The struct should be equal - this is the assertion that fails
assert original == decoded, (
f"Struct with NamedTuple dict keys should be equal after round-trip. "
f"This fails because dict comparison fails due to hash mismatch on keys. "
f"original_key_hash={original_key_hash}, decoded_key_hash={decoded_key_hash}"
)
# Verify the key can be looked up in the decoded struct's dict
assert key in decoded.data, (
f"Original key should be found in decoded dict. "
f"original_key_hash={original_key_hash}, decoded_key_hash={decoded_key_hash}"
)
assert decoded.data[key] == "test_value", "Value should be retrievable with original key"Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels