Skip to content

Commit a198332

Browse files
authored
Merge pull request #280 from Point72/ac/add_postprocess_dict_hook
Add postprocess_to_dict hook for to_dict method in structs
2 parents b3385f2 + 5deccad commit a198332

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

cpp/csp/python/PyStructToDict.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ PyObjectPtr parseStructToDictRecursive( const StructPtr& self, PyObject * callab
108108
} );
109109
PyDict_SetItemString( new_dict.get(), key.c_str(), py_obj.get() );
110110
}
111+
112+
// Optional postprocess hook in python to allow caller to customize to_dict behavior for struct
113+
PyObject * py_type = ( PyObject * ) meta -> pyType();
114+
if( PyObject_HasAttrString( py_type, "postprocess_to_dict" ) )
115+
{
116+
auto postprocess_dict_callable = PyObjectPtr::own( PyObject_GetAttrString( py_type, "postprocess_to_dict" ) );
117+
new_dict = PyObjectPtr::check( PyObject_CallFunction( postprocess_dict_callable.get(), "(O)", new_dict.get() ) );
118+
}
111119
return new_dict;
112120
}
113121

csp/impl/struct.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ def to_dict_depr(self):
165165
res = self._obj_to_python(self)
166166
return res
167167

168+
@classmethod
169+
def postprocess_to_dict(self, obj):
170+
"""Postprocess hook for to_dict method
171+
172+
This method is invoked by to_dict after converting a struct to a dict
173+
as an additional hook for users to modify the dict before it is returned
174+
by the to_dict method
175+
"""
176+
return obj
177+
168178
def to_dict(self, callback=None):
169179
"""Create a dictionary representation of the struct
170180

csp/tests/impl/test_struct.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,84 @@ class A(csp.Struct):
13851385
r = repr(a)
13861386
self.assertTrue(repr(raw) in r)
13871387

1388+
def test_to_dict_recursion(self):
1389+
class MyStruct(csp.Struct):
1390+
l1: list
1391+
l2: list
1392+
d1: dict
1393+
d2: dict
1394+
t1: tuple
1395+
t2: tuple
1396+
1397+
test_struct = MyStruct(l1=[1], l2=[2])
1398+
result_dict = {"l1": [1], "l2": [2]}
1399+
self.assertEqual(test_struct.to_dict(), result_dict)
1400+
1401+
test_struct = MyStruct(l1=[1], l2=[2])
1402+
test_struct.l1.append(test_struct.l2)
1403+
test_struct.l2.append(test_struct.l1)
1404+
with self.assertRaises(RecursionError):
1405+
test_struct.to_dict()
1406+
1407+
test_struct = MyStruct(l1=[1])
1408+
test_struct.l1.append(test_struct.l1)
1409+
with self.assertRaises(RecursionError):
1410+
test_struct.to_dict()
1411+
1412+
test_struct = MyStruct(l1=[1])
1413+
test_struct.l1.append(test_struct)
1414+
with self.assertRaises(RecursionError):
1415+
test_struct.to_dict()
1416+
1417+
test_struct = MyStruct(d1={1: 1}, d2={2: 2})
1418+
result_dict = {"d1": {1: 1}, "d2": {2: 2}}
1419+
self.assertEqual(test_struct.to_dict(), result_dict)
1420+
1421+
test_struct = MyStruct(d1={1: 1}, d2={2: 2})
1422+
test_struct.d1["d2"] = test_struct.d2
1423+
test_struct.d2["d1"] = test_struct.d1
1424+
with self.assertRaises(RecursionError):
1425+
test_struct.to_dict()
1426+
1427+
test_struct = MyStruct(d1={1: 1}, d2={2: 2})
1428+
test_struct.d1["d1"] = test_struct.d1
1429+
with self.assertRaises(RecursionError):
1430+
test_struct.to_dict()
1431+
1432+
test_struct = MyStruct(d1={1: 1}, d2={2: 2})
1433+
test_struct.d1["d1"] = test_struct
1434+
with self.assertRaises(RecursionError):
1435+
test_struct.to_dict()
1436+
1437+
test_struct = MyStruct(t1=(1, 1), t2=(2, 2))
1438+
result_dict = {"t1": (1, 1), "t2": (2, 2)}
1439+
self.assertEqual(test_struct.to_dict(), result_dict)
1440+
1441+
test_struct = MyStruct(t1=(1, 1))
1442+
test_struct.t1 = (1, 2, test_struct)
1443+
with self.assertRaises(RecursionError):
1444+
test_struct.to_dict()
1445+
1446+
def test_to_dict_postprocess(self):
1447+
class MySubStruct(csp.Struct):
1448+
i: int = 0
1449+
1450+
def postprocess_to_dict(obj):
1451+
obj["postprocess_called"] = True
1452+
return obj
1453+
1454+
class MyStruct(csp.Struct):
1455+
i: int = 1
1456+
mss: MySubStruct = MySubStruct()
1457+
1458+
def postprocess_to_dict(obj):
1459+
obj["postprocess_called"] = True
1460+
return obj
1461+
1462+
test_struct = MyStruct()
1463+
result_dict = {"i": 1, "postprocess_called": True, "mss": {"i": 0, "postprocess_called": True}}
1464+
self.assertEqual(test_struct.to_dict(), result_dict)
1465+
13881466
def test_to_json_primitives(self):
13891467
class MyStruct(csp.Struct):
13901468
b: bool = True

0 commit comments

Comments
 (0)