Skip to content

Commit 5d7eeb7

Browse files
authored
Merge pull request #188 from Point72/bug/struct-list
Override Python list modifying methods to capture and preserve changes to Struct list fields
2 parents 8ec9a4a + 8141192 commit 5d7eeb7

File tree

8 files changed

+966
-10
lines changed

8 files changed

+966
-10
lines changed

cpp/csp/python/CMakeLists.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ set(CSPTYPESIMPL_PUBLIC_HEADERS
33
CspTypeFactory.h
44
PyCspEnum.h
55
PyCspType.h
6-
PyStruct.h)
6+
PyStruct.h
7+
PyStructList.h)
78

89
add_library(csptypesimpl
910
csptypesimpl.cpp
1011
CspTypeFactory.cpp
1112
PyCspEnum.cpp
1213
PyCspType.cpp
1314
PyStruct.cpp
14-
PyStructToJson.cpp)
15+
PyStructToJson.cpp
16+
PyStructList.hi)
1517
set_target_properties(csptypesimpl PROPERTIES PUBLIC_HEADER "${CSPTYPESIMPL_PUBLIC_HEADERS}")
1618
target_compile_definitions(csptypesimpl PUBLIC RAPIDJSON_HAS_STDSTRING=1)
1719
target_link_libraries(csptypesimpl csp_core csp_types)
@@ -39,7 +41,8 @@ set(CSPIMPL_PUBLIC_HEADERS
3941
PyOutputAdapterWrapper.h
4042
PyOutputProxy.h
4143
PyConstants.h
42-
PyStructToJson.h)
44+
PyStructToJson.h
45+
PyStructList.h)
4346

4447
add_library(cspimpl SHARED
4548
cspimpl.cpp
@@ -70,6 +73,7 @@ add_library(cspimpl SHARED
7073
PyManagedSimInputAdapter.cpp
7174
PyTimerAdapter.cpp
7275
PyConstants.cpp
76+
PyStructList.hi
7377
${CSPIMPL_PUBLIC_HEADERS})
7478

7579
set_target_properties(cspimpl PROPERTIES PUBLIC_HEADER "${CSPIMPL_PUBLIC_HEADERS}")

cpp/csp/python/Conversions.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <csp/python/PyCspType.h>
1313
#include <csp/python/PyObjectPtr.h>
1414
#include <csp/python/PyStruct.h>
15+
#include <csp/python/PyStructList.h>
1516
#include <datetime.h>
1617
#include <Python.h>
1718
#include <string>
@@ -762,6 +763,29 @@ inline PyObject * toPython( const std::vector<StorageT> & v, const CspType & typ
762763
return list.release();
763764
}
764765

766+
template<typename StorageT>
767+
inline PyObject * toPython( const std::vector<StorageT> & v, const CspType & type, const PyStruct * pystruct )
768+
{
769+
assert( type.type() == CspType::Type::ARRAY );
770+
771+
const CspTypePtr elemType = static_cast<const CspArrayType &>( type ).elemType();
772+
using ElemT = typename CspType::Type::toCArrayElemType<StorageT>::type;
773+
size_t sz = v.size();
774+
775+
// TODO: Implement more efficient list allocation by pre-allocating the space and filling it using PyList_SET_ITEM.
776+
// As of now, the problem is that Python is not allowing to resize the list via API, and it cannot allocate the list at the base of PyStructList, it can only allocate it somewhere in memory not under control.
777+
PyObject * psl = PyStructList<StorageT>::PyType.tp_alloc( &PyStructList<StorageT>::PyType, 0 );
778+
new ( psl ) PyStructList<StorageT>( const_cast<PyStruct *>( pystruct ), const_cast<std::vector<StorageT> &>( v ), *elemType );
779+
780+
for( size_t index = 0; index < sz; ++index )
781+
{
782+
PyObjectPtr element = PyObjectPtr::own( toPython<ElemT>( v[ index ], *elemType ) );
783+
PyList_Append( ( PyObject * ) psl, element.get() );
784+
}
785+
786+
return psl;
787+
}
788+
765789
template<typename StorageT>
766790
struct FromPython<std::vector<StorageT>>
767791
{

cpp/csp/python/InitHelper.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class __attribute__ ((visibility ("hidden"))) InitHelper
2020

2121
bool registerCallback( InitCallback cb );
2222

23-
static InitCallback typeInitCallback( PyTypeObject * pyType, std::string name );
23+
static InitCallback typeInitCallback( PyTypeObject * pyType, std::string name, PyTypeObject * baseType = nullptr );
2424
static InitCallback moduleMethodsCallback( PyMethodDef * methods );
2525
static InitCallback moduleMethod( const char * name, PyCFunction func, int flags, const char * doc );
2626

@@ -50,9 +50,11 @@ inline InitHelper & InitHelper::instance()
5050
return s_instance;
5151
}
5252

53-
inline InitHelper::InitCallback InitHelper::typeInitCallback( PyTypeObject * pyType, std::string name )
53+
inline InitHelper::InitCallback InitHelper::typeInitCallback( PyTypeObject * pyType, std::string name, PyTypeObject * baseType )
5454
{
55-
InitCallback cb = [pyType,name]( PyObject * module ) {
55+
InitCallback cb = [pyType,name,baseType]( PyObject * module ) {
56+
if( baseType )
57+
pyType -> tp_base = baseType;
5658
if( PyType_Ready( pyType ) < 0 )
5759
return false;
5860

cpp/csp/python/PyStruct.cpp

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#include <csp/python/InitHelper.h>
44
#include <csp/python/PyObjectPtr.h>
55
#include <csp/python/PyStruct.h>
6+
#include <csp/python/PyStructList.hi>
67
#include <csp/python/PyStructToJson.h>
7-
88
#include <unordered_set>
99
#include <type_traits>
1010

@@ -395,8 +395,10 @@ PyTypeObject PyStructMeta::PyType = {
395395

396396

397397
//PyStruct
398-
PyObject * getattr_( const StructField* field, const Struct * struct_ )
398+
PyObject * getattr_( const StructField * field, const Struct * struct_ )
399399
{
400+
assert( field -> type() -> type() != CspType::Type::ARRAY );
401+
400402
PyObject *v = switchCspType( field -> type(), [ field, struct_ ]( auto tag )
401403
{
402404
using CType = typename decltype(tag)::type;
@@ -407,6 +409,21 @@ PyObject * getattr_( const StructField* field, const Struct * struct_ )
407409
return v;
408410
}
409411

412+
PyObject * getarrayattr_( const StructField * field, const PyStruct * pystruct )
413+
{
414+
assert( field -> type() -> type() == CspType::Type::ARRAY );
415+
416+
const CspArrayType * arrayType = static_cast<const CspArrayType *>( field -> type().get() );
417+
PyObject *v = ArraySubTypeSwitch::invoke( arrayType -> elemType(), [ field, pystruct ]( auto tag )
418+
{
419+
using StorageT = typename CspType::Type::toCArrayStorageType<typename decltype(tag)::type>::type;
420+
using ArrayT = typename StructField::upcast<std::vector<StorageT>>::type;
421+
auto * typedField = static_cast<const ArrayT *>( field );
422+
return toPython( typedField -> value( pystruct -> struct_.get() ), *field -> type(), pystruct );
423+
} );
424+
return v;
425+
}
426+
410427
PyObject * PyStruct::getattr( PyObject * attr )
411428
{
412429
auto * field = structMeta() -> field( attr );
@@ -424,7 +441,9 @@ PyObject * PyStruct::getattr( PyObject * attr )
424441
return nullptr;
425442
}
426443

427-
return getattr_( field, ( const Struct *)struct_.get() );
444+
if( field -> type() -> type() == CspType::Type::ARRAY )
445+
return getarrayattr_( field, this );
446+
return getattr_( field, ( const Struct * ) struct_.get() );
428447
}
429448

430449
void PyStruct::setattr( Struct * s, PyObject * attr, PyObject * value )
@@ -997,4 +1016,24 @@ PyTypeObject PyStruct::PyType = {
9971016
REGISTER_TYPE_INIT( &PyStructMeta::PyType, "PyStructMeta" )
9981017
REGISTER_TYPE_INIT( &PyStruct::PyType, "PyStruct" )
9991018

1019+
// Instantiate all templates for PyStructList class
1020+
template struct PyStructList<bool>;
1021+
template struct PyStructList<int8_t>;
1022+
template struct PyStructList<uint8_t>;
1023+
template struct PyStructList<int16_t>;
1024+
template struct PyStructList<uint16_t>;
1025+
template struct PyStructList<int32_t>;
1026+
template struct PyStructList<uint32_t>;
1027+
template struct PyStructList<int64_t>;
1028+
template struct PyStructList<uint64_t>;
1029+
template struct PyStructList<double>;
1030+
template struct PyStructList<DateTime>;
1031+
template struct PyStructList<TimeDelta>;
1032+
template struct PyStructList<Date>;
1033+
template struct PyStructList<Time>;
1034+
template struct PyStructList<std::string>;
1035+
template struct PyStructList<DialectGenericType>;
1036+
template struct PyStructList<StructPtr>;
1037+
template struct PyStructList<CspEnum>;
1038+
10001039
}

cpp/csp/python/PyStructList.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#ifndef _IN_CSP_PYTHON_PYSTRUCTLIST_H
2+
#define _IN_CSP_PYTHON_PYSTRUCTLIST_H
3+
4+
#include <csp/python/InitHelper.h>
5+
#include <csp/python/PyStruct.h>
6+
#include <Python.h>
7+
#include <vector>
8+
9+
namespace csp::python
10+
{
11+
12+
template<typename StorageT>
13+
struct PyStructList : public PyObject
14+
{
15+
using ElemT = typename CspType::Type::toCArrayElemType<StorageT>::type;
16+
17+
PyStructList<StorageT>( PyStruct * p, std::vector<StorageT> & v, const CspType & type ) : pystruct( p ), vector( v ), field_type( type )
18+
{
19+
Py_INCREF( pystruct );
20+
}
21+
22+
PyListObject base; // Inherit from PyListObject
23+
PyStruct * pystruct; // Pointer to PyStruct for proper reference counting
24+
std::vector<StorageT> & vector; // Reference to field value for modifying
25+
26+
const CspType & field_type; // We require the type information of any non-primitive type, i.e. Struct or Enum, since they contain a meta
27+
static PyTypeObject PyType;
28+
static bool s_typeRegister;
29+
};
30+
31+
template<typename StorageT> bool PyStructList<StorageT>::s_typeRegister = InitHelper::instance().registerCallback(
32+
InitHelper::typeInitCallback( &PyStructList<StorageT>::PyType, "", &PyList_Type ) );
33+
34+
}
35+
36+
#endif

0 commit comments

Comments
 (0)