Skip to content

Commit 5deccad

Browse files
committed
Add postprocess_to_dict hook for to_dict method in structs
Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
1 parent ab390db commit 5deccad

3 files changed

Lines changed: 96 additions & 0 deletions

File tree

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
@@ -1376,6 +1376,84 @@ class A(csp.Struct):
13761376
r = repr(a)
13771377
self.assertTrue(repr(raw) in r)
13781378

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

0 commit comments

Comments
 (0)