Skip to content

Commit

Permalink
Maintain the type of a list-derived object when converting a struct i…
Browse files Browse the repository at this point in the history
…n to_dict (Point72#199)

Signed-off-by: Adam Glustein <Adam.Glustein@Point72.com>
  • Loading branch information
AdamGlustein committed Apr 17, 2024
1 parent 3871e4a commit fc239d5
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 4 deletions.
12 changes: 11 additions & 1 deletion cpp/csp/python/PyStructList.hi
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,16 @@ static PyMappingMethods py_struct_list_as_mapping = {
py_struct_list_ass_subscript<StorageT>
};

static PyObject *
PyStructList_new( PyTypeObject *type, PyObject *args, PyObject *kwds )
{
// Since the PyStructList has no real meaning when created from Python, we can reconstruct the PSL's value
// by just treating it as a list. Thus, we simply override the tp_new behaviour to return a list object here.
// Again, since we don't have tp_init for the PSL, we need to rely on the Python list's tp_init function.

return PyObject_Call( ( PyObject * ) &PyList_Type, args, kwds ); // Calls both tp_new and tp_init for a Python list
}

template<typename StorageT>
static int
PyStructList_tp_clear( PyStructList<StorageT> * self )
Expand Down Expand Up @@ -437,7 +447,7 @@ template<typename StorageT> PyTypeObject PyStructList<StorageT>::PyType = {
.tp_clear = ( inquiry ) PyStructList_tp_clear<StorageT>,
.tp_methods = PyStructList_methods<StorageT>,
.tp_alloc = PyType_GenericAlloc,
.tp_new = PyType_GenericNew,
.tp_new = PyStructList_new,
.tp_free = PyObject_GC_Del,
};

Expand Down
4 changes: 1 addition & 3 deletions csp/impl/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ def _obj_to_python(cls, obj):
)
elif isinstance(obj, dict):
return {k: cls._obj_to_python(v) for k, v in obj.items()}
elif isinstance(obj, list):
return list(cls._obj_to_python(v) for v in obj)
elif isinstance(obj, (tuple, set)):
elif isinstance(obj, (list, tuple, set)):
return type(obj)(cls._obj_to_python(v) for v in obj)
elif isinstance(obj, csp.Enum):
return obj.name # handled in _obj_from_python
Expand Down
13 changes: 13 additions & 0 deletions csp/tests/impl/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,19 @@ def test_from_dict_with_enum(self):
struct = StructWithDefaults.from_dict({"e": MyEnum.A})
self.assertEqual(MyEnum.A, getattr(struct, "e"))

def test_from_dict_with_list_derived_type(self):
class ListDerivedType(list):
def __init__(self, iterable=None):
super().__init__(iterable)

class StructWithListDerivedType(csp.Struct):
ldt: ListDerivedType

s1 = StructWithListDerivedType(ldt=ListDerivedType([1,2]))
self.assertTrue(isinstance(s1.to_dict()['ldt'], ListDerivedType))
s2 = StructWithListDerivedType.from_dict(s1.to_dict())
self.assertEqual(s1, s2)

def test_from_dict_loop_no_defaults(self):
looped = StructNoDefaults.from_dict(StructNoDefaults(a1=[9, 10]).to_dict())
self.assertEqual(looped, StructNoDefaults(a1=[9, 10]))
Expand Down

0 comments on commit fc239d5

Please sign in to comment.