Skip to content

Commit cc596aa

Browse files
committed
RDBC-1030: normalize Enum dict keys in Utils.entity_to_dict
json.dumps raises TypeError for non-string dict keys. Entities whose fields are dicts with Enum keys (e.g. {CheckType.ENGINE: CheckStatus.GOOD}) failed at session.store() time. Fix: wrap default_method to recursively convert Enum keys to their .value before json.dumps sees them. Regression test: test_store_enum_key_dict.py verifies that an entity with an Enum-keyed dict field stores and round-trips correctly.
1 parent 24032b6 commit cc596aa

2 files changed

Lines changed: 92 additions & 1 deletion

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
Serialization: entities whose dict fields use Enum keys store and round-trip
3+
correctly; Enum keys are serialized as their string values.
4+
5+
C# reference: FastTests/Issues/RavenDB_22084.cs
6+
StoreOnDictionaryWithEnumKeyShouldWork
7+
"""
8+
9+
from enum import Enum
10+
11+
from ravendb.tests.test_base import TestBase
12+
13+
14+
class CheckType(Enum):
15+
ENGINE = "Engine"
16+
GEARS = "Gears"
17+
18+
19+
class CheckStatus(Enum):
20+
GOOD = "Good"
21+
BAD = "Bad"
22+
23+
24+
class Machine:
25+
def __init__(self, checks=None):
26+
self.checks = checks or {}
27+
28+
29+
class TestRavenDB22084(TestBase):
30+
def setUp(self):
31+
super().setUp()
32+
33+
def test_store_entity_with_enum_keyed_dict(self):
34+
"""
35+
C# spec: StoreOnDictionaryWithEnumKeyShouldWork
36+
var m = new Machine { Checks = { [CheckType.Engine] = CheckStatus.Good, ... } }
37+
session.Store(m)
38+
session.SaveChanges() → succeeds; Enum keys are serialized as their string values.
39+
"""
40+
m = Machine(checks={CheckType.ENGINE: CheckStatus.GOOD, CheckType.GEARS: CheckStatus.GOOD})
41+
with self.store.open_session() as session:
42+
session.store(m, "machines/1")
43+
session.save_changes()
44+
45+
def test_round_trip_preserves_enum_dict_values(self):
46+
"""
47+
C# spec: after StoreOnDictionaryWithEnumKeyShouldWork, loading the document back
48+
gives Checks[CheckType.Engine] == CheckStatus.Good.
49+
"""
50+
m = Machine(checks={CheckType.ENGINE: CheckStatus.GOOD, CheckType.GEARS: CheckStatus.BAD})
51+
with self.store.open_session() as session:
52+
session.store(m, "machines/2")
53+
session.save_changes()
54+
55+
with self.store.open_session() as session:
56+
loaded = session.load("machines/2", Machine)
57+
self.assertIsNotNone(loaded, "Document should be loadable after store")
58+
# JSON deserializes back to string keys; verify the enum values were stored correctly.
59+
self.assertEqual("Good", loaded.checks.get("Engine"), "Engine check should be Good")
60+
self.assertEqual("Bad", loaded.checks.get("Gears"), "Gears check should be Bad")
61+
62+
# ------------------------------------------------------------------ #
63+
# Baseline: dict with string keys #
64+
# ------------------------------------------------------------------ #
65+
66+
def test_store_entity_with_string_keyed_dict_works(self):
67+
"""
68+
Baseline: when dict keys are plain strings, store() works without error.
69+
Confirms the serialization layer itself is functional.
70+
"""
71+
m = Machine(checks={"Engine": "Good", "Gears": "Good"})
72+
with self.store.open_session() as session:
73+
session.store(m, "machines/3")
74+
session.save_changes()
75+
76+
with self.store.open_session() as session:
77+
loaded = session.load("machines/3", Machine)
78+
self.assertIsNotNone(loaded)
79+
self.assertEqual(2, len(loaded.checks))

ravendb/tools/utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,19 @@ def dictionarize(obj: object) -> dict:
942942

943943
@staticmethod
944944
def entity_to_dict(entity, default_method) -> dict:
945-
return json.loads(json.dumps(entity, default=default_method))
945+
def _normalize_enum_keys(obj):
946+
"""Recursively convert Enum dict keys to their values so json.dumps accepts them."""
947+
if isinstance(obj, dict):
948+
return {(k.value if isinstance(k, Enum) else k): _normalize_enum_keys(v) for k, v in obj.items()}
949+
if isinstance(obj, (list, set, tuple)):
950+
return [_normalize_enum_keys(i) for i in obj]
951+
return obj
952+
953+
def _normalized_default(o):
954+
result = default_method(o)
955+
return _normalize_enum_keys(result)
956+
957+
return json.loads(json.dumps(entity, default=_normalized_default))
946958

947959
@staticmethod
948960
def add_hours(date: datetime, hours: int):

0 commit comments

Comments
 (0)